From 358761fe3541eaade23fb3c20dcbd11ab46ef3de Mon Sep 17 00:00:00 2001 From: Bernd Schubert Date: Wed, 1 Jan 2025 23:02:52 +0100 Subject: [PATCH 001/241] 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 e8bf8ad2e33c15e7da72842639c5ddade6dcf546 Mon Sep 17 00:00:00 2001 From: Bernd Schubert Date: Wed, 1 Jan 2025 21:41:17 +0100 Subject: [PATCH 002/241] 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 94e269a8a5c50b6bb595261abb9d6cffe3380acb Mon Sep 17 00:00:00 2001 From: Bernd Schubert Date: Wed, 1 Jan 2025 22:53:38 +0100 Subject: [PATCH 003/241] 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 e156075b1566e085d8e56cc79c94b2f016628198 Mon Sep 17 00:00:00 2001 From: Bernd Schubert Date: Wed, 1 Jan 2025 22:58:45 +0100 Subject: [PATCH 004/241] 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 58e7e36deeb899146ecda71f2c2cda000269a305 Mon Sep 17 00:00:00 2001 From: Bernd Schubert <bernd@bsbernd.com> Date: Thu, 2 Jan 2025 21:10:11 +0100 Subject: [PATCH 005/241] 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 fb1168669875312155da984dd4915f130bed091e Mon Sep 17 00:00:00 2001 From: Bernd Schubert <bernd@bsbernd.com> Date: Thu, 2 Jan 2025 21:36:13 +0100 Subject: [PATCH 006/241] .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 fbc7b01e5e92c62fd8673c0b500803023d1e80f7 Mon Sep 17 00:00:00 2001 From: Bernd Schubert <bernd@bsbernd.com> Date: Thu, 2 Jan 2025 21:38:30 +0100 Subject: [PATCH 007/241] 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 9eb6d6236e523587ad2db5e4a679e929d422e6a9 Mon Sep 17 00:00:00 2001 From: Bernd Schubert <bernd@bsbernd.com> Date: Thu, 2 Jan 2025 22:45:43 +0100 Subject: [PATCH 008/241] 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 aca2557ba40c81c10244a06f29e364a3ece429b6 Mon Sep 17 00:00:00 2001 From: Amir Goldstein <amir73il@gmail.com> Date: Wed, 1 Jan 2025 18:16:43 +0100 Subject: [PATCH 009/241] 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 9b92c988eed637c8186360481b949ffa4397e942 Mon Sep 17 00:00:00 2001 From: Amir Goldstein <amir73il@gmail.com> Date: Fri, 3 Jan 2025 12:57:15 +0100 Subject: [PATCH 010/241] 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 52d4791e83852b485a3b22ab99f22cc37073aa70 Mon Sep 17 00:00:00 2001 From: Amir Goldstein <amir73il@gmail.com> Date: Thu, 2 Jan 2025 20:25:13 +0100 Subject: [PATCH 011/241] 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 8d34e367f76cb5d28671dd992e6a568b53845b3a Mon Sep 17 00:00:00 2001 From: Amir Goldstein <amir73il@gmail.com> Date: Thu, 2 Jan 2025 21:14:01 +0100 Subject: [PATCH 012/241] 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 0636f1fd5bf4c88f0fe04166c906a95a53837c35 Mon Sep 17 00:00:00 2001 From: Bernd Schubert <bernd@bsbernd.com> Date: Sat, 4 Jan 2025 16:08:30 +0100 Subject: [PATCH 013/241] 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 1c58dc6c0e4001334b20290d24d20cec9f81e638 Mon Sep 17 00:00:00 2001 From: Bernd Schubert <bernd@bsbernd.com> Date: Sat, 4 Jan 2025 20:29:18 +0100 Subject: [PATCH 014/241] 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 a1e5aded5942e78472e6d60846a3f4e769d13680 Mon Sep 17 00:00:00 2001 From: Bernd Schubert <bernd@bsbernd.com> Date: Sat, 4 Jan 2025 22:06:31 +0100 Subject: [PATCH 015/241] 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 fc95fd5076fd845e496bfbcec1ad9da16534b1c9 Mon Sep 17 00:00:00 2001 From: Bernd Schubert <bernd@bsbernd.com> Date: Sat, 4 Jan 2025 23:18:14 +0100 Subject: [PATCH 016/241] 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 33c0ce14cfee5a7251dc2542948f1a98d775edc4 Mon Sep 17 00:00:00 2001 From: Zegang <zegang.luo@qq.com> Date: Wed, 8 Jan 2025 14:50:30 +0800 Subject: [PATCH 017/241] 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 6f73db5e4adcb1eaaf9e22a8453090b1ff09ad80 Mon Sep 17 00:00:00 2001 From: Bernd Schubert <bschubert@ddn.com> Date: Mon, 13 Jan 2025 15:50:19 +0100 Subject: [PATCH 018/241] 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 0244e90a9f9d904b210ed26c04a4a3d8b69753b9 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 019/241] 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 cf56672c8a39db594949588277009a721ef7eeae Mon Sep 17 00:00:00 2001 From: Bernd Schubert <bernd@bsbernd.com> Date: Fri, 24 Jan 2025 11:15:41 +0100 Subject: [PATCH 020/241] 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 48d12f86e73e456b53defc4a8a98ed4db8a54b39 Mon Sep 17 00:00:00 2001 From: Bernd Schubert <bernd@bsbernd.com> Date: Fri, 24 Jan 2025 11:28:52 +0100 Subject: [PATCH 021/241] 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 da3c7e6216a929beac902991004c500993d87d95 Mon Sep 17 00:00:00 2001 From: Bernd Schubert <bernd@bsbernd.com> Date: Fri, 24 Jan 2025 11:58:44 +0100 Subject: [PATCH 022/241] 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 43ec53d6a16363184a95e59627c3f54d5568d8d1 Mon Sep 17 00:00:00 2001 From: Bernd Schubert <bernd@bsbernd.com> Date: Mon, 27 Jan 2025 21:33:43 +0100 Subject: [PATCH 023/241] 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 ba7beb21f63e91e22e877017ec6b359b5f8ece00 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 024/241] 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 64de51de4909eb7b68e982ab9922eb24dda4899e Mon Sep 17 00:00:00 2001 From: Bernd Schubert <bernd@bsbernd.com> Date: Wed, 29 Jan 2025 10:46:18 +0100 Subject: [PATCH 025/241] 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 999aee6c0d6bf1ac67c3b89472f32fd1d384cf44 Mon Sep 17 00:00:00 2001 From: Luis Henriques <luis@igalia.com> Date: Fri, 31 Jan 2025 15:26:20 +0000 Subject: [PATCH 026/241] 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 7f86f3be7148b15b71b63357813c66dd32177cf6 Mon Sep 17 00:00:00 2001 From: Luis Henriques <luis@igalia.com> Date: Fri, 31 Jan 2025 16:03:54 +0000 Subject: [PATCH 027/241] 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 32d84aaacd1a6020940598be0c792fc7e0369b67 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 028/241] 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 458f433b89f3a599ed90bced16cb1e3e8403a774 Mon Sep 17 00:00:00 2001 From: Bernd Schubert <bernd@bsbernd.com> Date: Sat, 8 Feb 2025 14:08:32 +0100 Subject: [PATCH 029/241] 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 b0f32a8b318d1a01f88eb37f09fd4bb8ac175bb4 Mon Sep 17 00:00:00 2001 From: Bernd Schubert <bernd@bsbernd.com> Date: Sat, 8 Feb 2025 14:14:43 +0100 Subject: [PATCH 030/241] 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 53eefce4e735dd1d8f77ceadfc8beb9679390e08 Mon Sep 17 00:00:00 2001 From: Bernd Schubert <bernd@bsbernd.com> Date: Sat, 8 Feb 2025 14:35:46 +0100 Subject: [PATCH 031/241] 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 38da521b762debd93910cea029a08328b6a198bb Mon Sep 17 00:00:00 2001 From: Bernd Schubert <bernd@bsbernd.com> Date: Mon, 10 Feb 2025 01:52:08 +0100 Subject: [PATCH 032/241] 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 65dfe9c2907157732a1346dd679f126bce86f84f Mon Sep 17 00:00:00 2001 From: Bernd Schubert <bernd@bsbernd.com> Date: Mon, 10 Feb 2025 01:50:01 +0100 Subject: [PATCH 033/241] 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 dca6e514c81e3ad0ec806f79b774b3707b313f63 Mon Sep 17 00:00:00 2001 From: Bernd Schubert <bschubert@ddn.com> Date: Mon, 10 Feb 2025 02:45:42 +0100 Subject: [PATCH 034/241] 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 805969f5284505e48ee87175476cf9df241ded42 Mon Sep 17 00:00:00 2001 From: Bernd Schubert <bernd@bsbernd.com> Date: Mon, 10 Feb 2025 02:36:39 +0100 Subject: [PATCH 035/241] 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 34c505316083fed363752676ae4b9056abd251c7 Mon Sep 17 00:00:00 2001 From: Bernd Schubert <bschubert@ddn.com> Date: Mon, 10 Feb 2025 15:55:20 +0100 Subject: [PATCH 036/241] 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 62c79d580ed9d89e6071c93986ea92c5d43aaeb9 Mon Sep 17 00:00:00 2001 From: Joanne Koong <joannelkoong@gmail.com> Date: Wed, 12 Feb 2025 11:00:11 -0800 Subject: [PATCH 037/241] 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 41904e501..1c8db6c4d 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 374892840a5767309905dcce1fcde5f80173eedf Mon Sep 17 00:00:00 2001 From: Bernd Schubert <bernd@bsbernd.com> Date: Thu, 13 Feb 2025 14:55:52 +0100 Subject: [PATCH 038/241] 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 5889ae6733298ab2f79c25b03b53f7a49255156e 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 039/241] 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 51cb3e2940317ce0cb409dee4f3d9e9bdde69da7 Mon Sep 17 00:00:00 2001 From: Bernd Schubert <bschubert@ddn.com> Date: Sun, 16 Feb 2025 22:40:41 +0100 Subject: [PATCH 040/241] 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 9971ec56d848519a1a5d4dc27ac709d90ff03e4b Mon Sep 17 00:00:00 2001 From: Bernd Schubert <bschubert@ddn.com> Date: Sun, 16 Feb 2025 22:56:46 +0100 Subject: [PATCH 041/241] 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 55f696228e5728cd89008e0af1ca5178bd4b9fb6 Mon Sep 17 00:00:00 2001 From: Bernd Schubert <bschubert@ddn.com> Date: Mon, 17 Feb 2025 19:23:44 +0100 Subject: [PATCH 042/241] 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 7e02c20c650c378729ab4999933f556c4bfb25c6 Mon Sep 17 00:00:00 2001 From: Bernd Schubert <bernd@bsbernd.com> Date: Sun, 16 Feb 2025 22:15:31 +0100 Subject: [PATCH 043/241] 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 d7d7f12d115b8853af0665c41e07d9f17d271a6f 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 044/241] 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 42045175964ca3f46dd8f49409ac0cc97e51289e 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 045/241] 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 84da6e08fb533bea595373e20b3b8a4edb4987cb 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 046/241] 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 42824c4afc7da568b3a1914c5e5954049d71f253 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 047/241] 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 3232d6e05fed808878ef8ae86af348a94f0da585 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 048/241] 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 124ceb7382d5d13436d9bf47a8df349880a40c52 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 049/241] 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 f665917e93509183eb798a1c9862f6cb851b7530 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 050/241] 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 06fef4f8c3873e43027b8221a745a5fdf63d867c 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 051/241] 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 619538e5d5cd19ee3e3919dea223464285d3cb8a 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 052/241] 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 f8bdca32b4dfbed4b21a69dcc751cdcbf8b2d58e Mon Sep 17 00:00:00 2001 From: Bernd Schubert <bschubert@ddn.com> Date: Mon, 17 Feb 2025 23:39:04 +0100 Subject: [PATCH 053/241] 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 38cfa386d5397e54817b684a60228a8ee1a3f48d Mon Sep 17 00:00:00 2001 From: Bernd Schubert <bschubert@ddn.com> Date: Tue, 18 Feb 2025 20:23:09 +0100 Subject: [PATCH 054/241] 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 21c87768e400d46c3f7c38edba2f338b7476b0c2 Mon Sep 17 00:00:00 2001 From: Bernd Schubert <bernd@bsbernd.com> Date: Tue, 18 Feb 2025 21:45:05 +0100 Subject: [PATCH 055/241] 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 c389e09f3564f52ac0cf10719915347e57053b00 Mon Sep 17 00:00:00 2001 From: Bernd Schubert <bschubert@ddn.com> Date: Tue, 18 Feb 2025 23:06:20 +0100 Subject: [PATCH 056/241] github checkpatch test: Fix for non pull requests It should only run for pull requests. Indentation for the branch to run on was also not right. Signed-off-by: Bernd Schubert <bschubert@ddn.com> --- .github/workflows/checkpatch.yml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/checkpatch.yml b/.github/workflows/checkpatch.yml index 622542c54..f6acb1bcb 100644 --- a/.github/workflows/checkpatch.yml +++ b/.github/workflows/checkpatch.yml @@ -1,14 +1,10 @@ name: Checkpatch on: - push: - branches: - - master pull_request: + types: [opened, synchronize, reopened] branches: - master -permissions: - contents: read jobs: checkpatch: @@ -23,6 +19,10 @@ jobs: sudo apt-get install -y perl - name: Run checkpatch.pl run: | + if [ -z "${{ github.base_ref }}" ]; then + echo "Not a pull request, skipping checkpatch" + exit 0 + fi git fetch origin ${{ github.base_ref }} base_commit=$(git merge-base origin/${{ github.base_ref }} HEAD) echo "Base commit: $base_commit" From 26b0e0ee2522d88a093204433aad72da0d44cb26 Mon Sep 17 00:00:00 2001 From: Bernd Schubert <bernd@bsbernd.com> Date: Tue, 18 Feb 2025 23:50:12 +0100 Subject: [PATCH 057/241] Update meson.build for master branch to 3.18 Signed-off-by: Bernd Schubert <bernd@bsbernd.com> --- meson.build | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/meson.build b/meson.build index e191fc62c..0487639ca 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.18.0-rc0', # Version with RC suffix meson_version: '>= 0.51.0', default_options: [ 'buildtype=debugoptimized', From 8ca2fe632de965c13d26e64dc1ff8517240a4078 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 058/241] 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 c516c643de27cc5fb7d30495f5c9047651a64447 Mon Sep 17 00:00:00 2001 From: Vassili Tchersky <vt+git@vbc.su> Date: Thu, 20 Feb 2025 13:55:03 +0100 Subject: [PATCH 059/241] fuse_log: simplify default log function fuse_log_level is guaranteed to be the same as libc as syslog is a network protocol and levels are numerical constants enforced in RFCs. Syslog is originally BSD-only and was imported by glibc and standardised in SUS and POSIX. Use vsyslog rather than formatting a new intermediate string. Signed-off-by: Vassili Tchersky <vt+git@vbc.su> --- lib/fuse_log.c | 47 +++++------------------------------------------ 1 file changed, 5 insertions(+), 42 deletions(-) diff --git a/lib/fuse_log.c b/lib/fuse_log.c index c1d16c148..5039b0454 100644 --- a/lib/fuse_log.c +++ b/lib/fuse_log.c @@ -10,58 +10,21 @@ #include "fuse_log.h" -#include <stdarg.h> #include <stdio.h> #include <stdbool.h> #include <syslog.h> +#include <stdarg.h> #define MAX_SYSLOG_LINE_LEN 512 static bool to_syslog = false; -static void default_log_func(__attribute__((unused)) enum fuse_log_level level, - const char *fmt, va_list ap) +static void default_log_func(enum fuse_log_level level, const char *fmt, va_list ap) { - if (to_syslog) { - int sys_log_level = LOG_ERR; - - /* - * with glibc fuse_log_level has identical values as - * syslog levels, but we also support BSD - better we convert to - * be sure. - */ - switch (level) { - case FUSE_LOG_DEBUG: - sys_log_level = LOG_DEBUG; - break; - case FUSE_LOG_INFO: - sys_log_level = LOG_INFO; - break; - case FUSE_LOG_NOTICE: - sys_log_level = LOG_NOTICE; - break; - case FUSE_LOG_WARNING: - sys_log_level = LOG_WARNING; - break; - case FUSE_LOG_ERR: - sys_log_level = LOG_ERR; - break; - case FUSE_LOG_CRIT: - sys_log_level = LOG_CRIT; - break; - case FUSE_LOG_ALERT: - sys_log_level = LOG_ALERT; - break; - case FUSE_LOG_EMERG: - sys_log_level = LOG_EMERG; - } - - char log[MAX_SYSLOG_LINE_LEN]; - vsnprintf(log, MAX_SYSLOG_LINE_LEN, fmt, ap); - syslog(sys_log_level, "%s", log); - } else { + if (to_syslog) + vsyslog(level, fmt, ap); + else vfprintf(stderr, fmt, ap); - } } static fuse_log_func_t log_func = default_log_func; From 2a1bd0b0c8b774d70bd2154a50db97fc8875accd Mon Sep 17 00:00:00 2001 From: Bernd Schubert <bernd@bsbernd.com> Date: Tue, 11 Mar 2025 14:09:29 +0100 Subject: [PATCH 060/241] checkpatch: More ignores: ENOSYS,FROM_SIGN_OFF_MISMATCH,QUOTED_COMMIT_ID ENOSYS is right for fuse, automatic github action updates persistently run into sign-off-mismatch and commit-id quoting persistently conflicts with long line warnings. 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 f6acb1bcb..5bf92621e 100644 --- a/.github/workflows/checkpatch.yml +++ b/.github/workflows/checkpatch.yml @@ -30,7 +30,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,ENOSYS_SYSCALL -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,ENOSYS,FROM_SIGN_OFF_MISMATCH,QUOTED_COMMIT_ID -g $commit; then echo "checkpatch.pl found issues in commit $commit - $subject" exit 1 fi From f466ce3c91e33427f1387cde47a19006d4144553 Mon Sep 17 00:00:00 2001 From: Bernd Schubert <bernd@bsbernd.com> Date: Tue, 11 Mar 2025 14:29:25 +0100 Subject: [PATCH 061/241] checkpatch: 100 chars per line Accept up to 100 chars per line to silence more github action updates. 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 5bf92621e..45133eb87 100644 --- a/.github/workflows/checkpatch.yml +++ b/.github/workflows/checkpatch.yml @@ -30,7 +30,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,ENOSYS_SYSCALL,ENOSYS,FROM_SIGN_OFF_MISMATCH,QUOTED_COMMIT_ID -g $commit; then + if ! ./checkpatch.pl --max-line-length=100 --no-tree --ignore MAINTAINERS,SPDX_LICENSE_TAG,COMMIT_MESSAGE,FILE_PATH_CHANGES,EMAIL_SUBJECT,AVOID_EXTERNS,GIT_COMMIT_ID,ENOSYS_SYSCALL,ENOSYS,FROM_SIGN_OFF_MISMATCH,QUOTED_COMMIT_ID -g $commit; then echo "checkpatch.pl found issues in commit $commit - $subject" exit 1 fi From 622dca0b37c6dda09e406f39fe12f71a55682d78 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 10 Mar 2025 22:14:36 +0000 Subject: [PATCH 062/241] build(deps): bump github/codeql-action from 3.28.9 to 3.28.11 Bumps [github/codeql-action](https://github.com/github/codeql-action) from 3.28.9 to 3.28.11. - [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/9e8d0789d4a0fa9ceb6b1738f7e269594bdd67f0...6bb031afdd8eb862ea3fc1848194185e076637e5) --- 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 474f2c8d2..c2cb051a7 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@9e8d0789d4a0fa9ceb6b1738f7e269594bdd67f0 # v3.28.9 + uses: github/codeql-action/init@6bb031afdd8eb862ea3fc1848194185e076637e5 # v3.28.11 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@9e8d0789d4a0fa9ceb6b1738f7e269594bdd67f0 # v3.28.9 + uses: github/codeql-action/analyze@6bb031afdd8eb862ea3fc1848194185e076637e5 # v3.28.11 with: category: "/language:${{matrix.language}}" From 431d69a789932d0cc612d541d1e8e3c084df950f Mon Sep 17 00:00:00 2001 From: Luis Henriques <luis@igalia.com> Date: Tue, 4 Mar 2025 10:21:32 +0000 Subject: [PATCH 063/241] doc: Add new README about notification operations The main goal of this new file is to warn about the deadlocking risks when using the out-of-band notify operations while replying to other requests. Signed-off-by: Luis Henriques <luis@igalia.com> --- doc/README.notifications | 37 +++++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) create mode 100644 doc/README.notifications diff --git a/doc/README.notifications b/doc/README.notifications new file mode 100644 index 000000000..2ca6204b1 --- /dev/null +++ b/doc/README.notifications @@ -0,0 +1,37 @@ +During the life-cycle of a user-space filesystem the usual flow is: + + 1. User-space application does a filesystem-related syscall + 2. Kernel VFS calls into the FUSE kernel driver + 3. FUSE kernel redirects request to the user-space filesystem + 4. User-space server replies to request + 5. FUSE returns reply to VFS + 6. User-space application gets reply from the kernel + +However, there are occasions where the filesystem needs to send notifications to +the kernel that are not in reply to any particular request. If, for example, +when a READ request of 4096 bytes results in the filesystem having more data +available for the specific inode, it may be useful to provide this extra data to +the kernel cache so that future read operations will be faster. + +FUSE provides mechanisms for a user-space server to send the kernel certain +types of asynchronous notifications. Currently, these are the available +notifications: + +|-------------+----------------------------------| +| Operation | libfuse function | +|-------------+----------------------------------| +| POLL | fuse_lowlevel_notify_poll | +| INVAL_INODE | fuse_lowlevel_notify_inval_inode | +| ENTRY | fuse_lowlevel_notify_inval_entry | +| STORE | fuse_lowlevel_notify_store | +| RETRIEVE | fuse_lowlevel_notify_retrieve | +| DELETE | fuse_lowlevel_notify_delete | +| RESEND | - | +|-------------+----------------------------------| + +One important restriction is that these asynchronous operations SHALL NOT be +performed while executing other FUSE requests. Doing so will likely result in +deadlocking the user-space filesystem server. In the example above, if the +server is replying to a READ request and has extra data to add to the kernel +cache, it needs to reply to the READ request first, and, e.g., signal a +different thread to do the STORE. From 3a291c355f3a4966ff3ebb6841b31da0d3010f52 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 064/241] 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> --- 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 3ae5ca7443348aabad9bc71b9d5b0999f8292379 Mon Sep 17 00:00:00 2001 From: Bernd Schubert <bschubert@ddn.com> Date: Thu, 13 Mar 2025 23:52:01 +0100 Subject: [PATCH 065/241] 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> --- 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 065272a23392011ea1728c4f2d20e63cf6579e7d Mon Sep 17 00:00:00 2001 From: Bernd Schubert <bschubert@ddn.com> Date: Fri, 7 Mar 2025 22:43:50 +0100 Subject: [PATCH 066/241] 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 ea04c34f4..69ca15917 100644 --- a/lib/fuse_i.h +++ b/lib/fuse_i.h @@ -72,14 +72,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 d650944a3..06425723f 100644 --- a/lib/fuse_lowlevel.c +++ b/lib/fuse_lowlevel.c @@ -3042,11 +3042,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; @@ -3091,6 +3094,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 1b86fe4c4de96daa4e766425193595f1c6b88a73 Mon Sep 17 00:00:00 2001 From: Bernd Schubert <bschubert@ddn.com> Date: Tue, 11 Mar 2025 22:21:09 +0100 Subject: [PATCH 067/241] 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 69ca15917..bf8410f33 100644 --- a/lib/fuse_i.h +++ b/lib/fuse_i.h @@ -79,7 +79,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 06425723f..1e2491bbe 100644 --- a/lib/fuse_lowlevel.c +++ b/lib/fuse_lowlevel.c @@ -3136,8 +3136,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; @@ -3177,13 +3175,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()*/ @@ -3197,9 +3191,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, @@ -3208,8 +3203,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; } @@ -3251,6 +3245,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 74ce74845..a5c5463a5 100644 --- a/lib/util.h +++ b/lib/util.h @@ -1,3 +1,6 @@ #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 b9fc5a0ff8905a27e48cdac1ae26a8964fe87727 Mon Sep 17 00:00:00 2001 From: Bernd Schubert <bschubert@ddn.com> Date: Sun, 23 Feb 2025 00:17:39 +0100 Subject: [PATCH 068/241] 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 0487639ca..cbcd70d02 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 bdddba72c2a0a9e941960656ad476ef6d29684bf Mon Sep 17 00:00:00 2001 From: Bernd Schubert <bernd@bsbernd.com> Date: Thu, 20 Feb 2025 13:47:48 +0100 Subject: [PATCH 069/241] Switch .dir-locals.el to linux kernel style We have a non ideal configuration for different editors. The project uses linux style - emacs config should also correspond to that. Taken from https://www.kernel.org/doc/html/v6.13/process/coding-style.html#you-ve-made-a-mess-of-it with the attempt to update for c++. Signed-off-by: Bernd Schubert <bernd@bsbernd.com> --- .dir-locals.el | 107 ++++++++++++++++++++++++++++--------------------- 1 file changed, 62 insertions(+), 45 deletions(-) diff --git a/.dir-locals.el b/.dir-locals.el index 628f512ab..4616d4003 100644 --- a/.dir-locals.el +++ b/.dir-locals.el @@ -1,47 +1,64 @@ ((python-mode . ((indent-tabs-mode . nil))) - (autoconf-mode . ((indent-tabs-mode . t))) - (c++-mode . ((c-file-style . "k&r") - (indent-tabs-mode . nil) - (c-basic-offset . 4) - (c-file-offsets . - ((block-close . 0) - (brace-list-close . 0) - (brace-list-entry . 0) - (brace-list-intro . +) - (case-label . 0) - (class-close . 0) - (defun-block-intro . +) - (defun-close . 0) - (defun-open . 0) - (else-clause . 0) - (inclass . +) - (label . 0) - (statement . 0) - (statement-block-intro . +) - (statement-case-intro . +) - (statement-cont . +) - (substatement . +) - (topmost-intro . 0))))) - (c-mode . ((c-file-style . "stroustrup") - (indent-tabs-mode . t) - (tab-width . 8) + (c++-mode . ((c-file-style . "linux-kernel") + (c-basic-offset . 8) + (c-label-minimum-indentation . 0) + (c-offsets-alist . ( + (arglist-close . c-lineup-arglist-tabs-only) + (arglist-cont-nonempty . + (c-lineup-gcc-asm-reg c-lineup-arglist-tabs-only)) + (arglist-intro . +) + (brace-list-intro . +) + (c . c-lineup-C-comments) + (case-label . 0) + (comment-intro . c-lineup-comment) + (cpp-define-intro . +) + (cpp-macro . -1000) + (cpp-macro-cont . +) + (defun-block-intro . +) + (else-clause . 0) + (func-decl-cont . +) + (inclass . +) + (inher-cont . c-lineup-multi-inher) + (knr-argdecl-intro . 0) + (label . -1000) + (statement . 0) + (statement-block-intro . +) + (statement-case-intro . +) + (statement-cont . +) + (substatement . +) + )) + (indent-tabs-mode . t) + (show-trailing-whitespace . t) + )) + (c-mode . ((c-file-style . "linux-kernel") (c-basic-offset . 8) - (c-file-offsets . - ((block-close . 0) - (brace-list-close . 0) - (brace-list-entry . 0) - (brace-list-intro . +) - (case-label . 0) - (class-close . 0) - (defun-block-intro . +) - (defun-close . 0) - (defun-open . 0) - (else-clause . 0) - (inclass . +) - (label . 0) - (statement . 0) - (statement-block-intro . +) - (statement-case-intro . +) - (statement-cont . +) - (substatement . +) - (topmost-intro . 0)))))) + (c-label-minimum-indentation . 0) + (c-offsets-alist . ( + (arglist-close . c-lineup-arglist-tabs-only) + (arglist-cont-nonempty . + (c-lineup-gcc-asm-reg c-lineup-arglist-tabs-only)) + (arglist-intro . +) + (brace-list-intro . +) + (c . c-lineup-C-comments) + (case-label . 0) + (comment-intro . c-lineup-comment) + (cpp-define-intro . +) + (cpp-macro . -1000) + (cpp-macro-cont . +) + (defun-block-intro . +) + (else-clause . 0) + (func-decl-cont . +) + (inclass . +) + (inher-cont . c-lineup-multi-inher) + (knr-argdecl-intro . 0) + (label . -1000) + (statement . 0) + (statement-block-intro . +) + (statement-case-intro . +) + (statement-cont . +) + (substatement . +) + )) + (indent-tabs-mode . t) + (show-trailing-whitespace . t) + )) +) From 9f7597a576eb6329ece28a4baee007970a2f278c Mon Sep 17 00:00:00 2001 From: Bernd Schubert <bernd@bsbernd.com> Date: Wed, 19 Mar 2025 18:46:33 +0100 Subject: [PATCH 070/241] 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 14520a6ef7eaf010d0d9354d3431fed362e205ce Mon Sep 17 00:00:00 2001 From: Bernd Schubert <bernd@bsbernd.com> Date: Sun, 23 Mar 2025 00:19:04 +0100 Subject: [PATCH 071/241] convert LL_ENABLE/LL_DISABLE to do-while-loop This is to avoid checkpatch reports and might potentially avoid future issues if these macros would be part of conditions. Signed-off-by: Bernd Schubert <bernd@bsbernd.com> --- lib/helper.c | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/lib/helper.c b/lib/helper.c index a7b2fe002..1fe3fdeb8 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 |= (cap) -#define LL_DISABLE(cond,cap) \ - if (cond) conn->want &= ~(cap) +#define LL_ENABLE(cond, cap) \ + do { \ + if (cond) \ + conn->want |= (cap); \ + } while (0) + +#define LL_DISABLE(cond, cap) \ + do { \ + if (cond) \ + conn->want &= ~(cap); \ + } while (0) LL_ENABLE(opts->splice_read, FUSE_CAP_SPLICE_READ); LL_DISABLE(opts->no_splice_read, FUSE_CAP_SPLICE_READ); From 5d1c49e54ca226f144b618a1623117e2a52e7e17 Mon Sep 17 00:00:00 2001 From: Bernd Schubert <bernd@bsbernd.com> Date: Sun, 23 Mar 2025 00:34:52 +0100 Subject: [PATCH 072/241] test/test_write_cache.c: Reformat with clang-format The file is going to be updated - should be more conform Signed-off-by: Bernd Schubert <bernd@bsbernd.com> --- test/test_write_cache.c | 416 ++++++++++++++++++++-------------------- 1 file changed, 212 insertions(+), 204 deletions(-) diff --git a/test/test_write_cache.c b/test/test_write_cache.c index 0c8fa7e8b..2e9c671e2 100644 --- a/test/test_write_cache.c +++ b/test/test_write_cache.c @@ -6,7 +6,6 @@ See the file COPYING. */ - #define FUSE_USE_VERSION 30 /* Not really needed - just to test build with FUSE_USE_VERSION == 30 */ @@ -37,24 +36,22 @@ /* Command line parsing */ struct options { - int writeback; - int data_size; - int delay_ms; + int writeback; + int data_size; + int delay_ms; } options = { - .writeback = 0, - .data_size = 2048, - .delay_ms = 0, + .writeback = 0, + .data_size = 2048, + .delay_ms = 0, }; #define WRITE_SYSCALLS 64 -#define OPTION(t, p) \ - { t, offsetof(struct options, p), 1 } +#define OPTION(t, p) { t, offsetof(struct options, p), 1 } static const struct fuse_opt option_spec[] = { - OPTION("writeback_cache", writeback), - OPTION("--data-size=%d", data_size), - OPTION("--delay_ms=%d", delay_ms), - FUSE_OPT_END + OPTION("writeback_cache", writeback), + OPTION("--data-size=%d", data_size), OPTION("--delay_ms=%d", delay_ms), + FUSE_OPT_END }; static int got_write; static atomic_int write_cnt; @@ -63,229 +60,241 @@ pthread_cond_t cond = PTHREAD_COND_INITIALIZER; pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER; static int write_start, write_done; -static void tfs_init (void *userdata, struct fuse_conn_info *conn) +static void tfs_init(void *userdata, struct fuse_conn_info *conn) { - (void) userdata; + (void)userdata; - if(options.writeback) { - assert(fuse_get_feature_flag(conn, FUSE_CAP_WRITEBACK_CACHE)); - conn->want |= FUSE_CAP_WRITEBACK_CACHE; - } + if (options.writeback) { + assert(fuse_get_feature_flag(conn, FUSE_CAP_WRITEBACK_CACHE)); + conn->want |= FUSE_CAP_WRITEBACK_CACHE; + } } -static int tfs_stat(fuse_ino_t ino, struct stat *stbuf) { - stbuf->st_ino = ino; - if (ino == FUSE_ROOT_ID) { - stbuf->st_mode = S_IFDIR | 0755; - stbuf->st_nlink = 1; - } - - else if (ino == FILE_INO) { - stbuf->st_mode = S_IFREG | 0222; - stbuf->st_nlink = 1; - stbuf->st_size = 0; - } - - else - return -1; - - return 0; +static int tfs_stat(fuse_ino_t ino, struct stat *stbuf) +{ + stbuf->st_ino = ino; + if (ino == FUSE_ROOT_ID) { + stbuf->st_mode = S_IFDIR | 0755; + stbuf->st_nlink = 1; + } + + else if (ino == FILE_INO) { + stbuf->st_mode = S_IFREG | 0222; + stbuf->st_nlink = 1; + stbuf->st_size = 0; + } + + else + return -1; + + return 0; } -static void tfs_lookup(fuse_req_t req, fuse_ino_t parent, - const char *name) { - struct fuse_entry_param e; - memset(&e, 0, sizeof(e)); +static void tfs_lookup(fuse_req_t req, fuse_ino_t parent, const char *name) +{ + struct fuse_entry_param e; + + memset(&e, 0, sizeof(e)); - if (parent != FUSE_ROOT_ID) - goto err_out; - else if (strcmp(name, FILE_NAME) == 0) - e.ino = FILE_INO; - else - goto err_out; + if (parent != FUSE_ROOT_ID) + goto err_out; + else if (strcmp(name, FILE_NAME) == 0) + e.ino = FILE_INO; + else + goto err_out; - if (tfs_stat(e.ino, &e.attr) != 0) - goto err_out; - fuse_reply_entry(req, &e); - return; + if (tfs_stat(e.ino, &e.attr) != 0) + goto err_out; + fuse_reply_entry(req, &e); + return; err_out: - fuse_reply_err(req, ENOENT); + fuse_reply_err(req, ENOENT); } static void tfs_getattr(fuse_req_t req, fuse_ino_t ino, - struct fuse_file_info *fi) { - struct stat stbuf; + struct fuse_file_info *fi) +{ + struct stat stbuf; - (void) fi; + (void)fi; - memset(&stbuf, 0, sizeof(stbuf)); - if (tfs_stat(ino, &stbuf) != 0) - fuse_reply_err(req, ENOENT); - else - fuse_reply_attr(req, &stbuf, 5); + memset(&stbuf, 0, sizeof(stbuf)); + if (tfs_stat(ino, &stbuf) != 0) + fuse_reply_err(req, ENOENT); + else + fuse_reply_attr(req, &stbuf, 5); } -static void tfs_open(fuse_req_t req, fuse_ino_t ino, - struct fuse_file_info *fi) { - if (ino == FUSE_ROOT_ID) - fuse_reply_err(req, EISDIR); - else { - assert(ino == FILE_INO); - /* Test close(rofd) does not block waiting for pending writes */ - fi->noflush = !options.writeback && options.delay_ms && - (fi->flags & O_ACCMODE) == O_RDONLY; - fuse_reply_open(req, fi); - } +static void tfs_open(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi) +{ + if (ino == FUSE_ROOT_ID) + fuse_reply_err(req, EISDIR); + else { + assert(ino == FILE_INO); + /* Test close(rofd) does not block waiting for pending writes */ + fi->noflush = !options.writeback && options.delay_ms && + (fi->flags & O_ACCMODE) == O_RDONLY; + fuse_reply_open(req, fi); + } } static void tfs_write(fuse_req_t req, fuse_ino_t ino, const char *buf, - size_t size, off_t off, struct fuse_file_info *fi) { - (void) fi; (void) buf; (void) off; - size_t expected; - - assert(ino == FILE_INO); - expected = options.data_size; - if(options.writeback) - expected *= 2; - - write_cnt++; - - if(size != expected && !options.writeback) - fprintf(stderr, "ERROR: Expected %zd bytes, got %zd\n!", - expected, size); - else - got_write = 1; - - /* Simulate waiting for pending writes */ - if (options.delay_ms) { - pthread_mutex_lock(&lock); - write_start = 1; - pthread_cond_signal(&cond); - pthread_mutex_unlock(&lock); - - usleep(options.delay_ms * 1000); - - pthread_mutex_lock(&lock); - write_done = 1; - pthread_cond_signal(&cond); - pthread_mutex_unlock(&lock); - } - - fuse_reply_write(req, size); + size_t size, off_t off, struct fuse_file_info *fi) +{ + (void)fi; + (void)buf; + (void)off; + size_t expected; + + assert(ino == FILE_INO); + expected = options.data_size; + if (options.writeback) + expected *= 2; + + write_cnt++; + + if (size != expected && !options.writeback) + fprintf(stderr, "ERROR: Expected %zd bytes, got %zd\n!", + expected, size); + else + got_write = 1; + + /* Simulate waiting for pending writes */ + if (options.delay_ms) { + pthread_mutex_lock(&lock); + write_start = 1; + pthread_cond_signal(&cond); + pthread_mutex_unlock(&lock); + + usleep(options.delay_ms * 1000); + + pthread_mutex_lock(&lock); + write_done = 1; + pthread_cond_signal(&cond); + pthread_mutex_unlock(&lock); + } + + fuse_reply_write(req, size); } static struct fuse_lowlevel_ops tfs_oper = { - .init = tfs_init, - .lookup = tfs_lookup, - .getattr = tfs_getattr, - .open = tfs_open, - .write = tfs_write, + .init = tfs_init, + .lookup = tfs_lookup, + .getattr = tfs_getattr, + .open = tfs_open, + .write = tfs_write, }; -static void* close_rofd(void *data) { - int rofd = (int)(long) data; +static void *close_rofd(void *data) +{ + int rofd = (int)(long)data; - /* Wait for first write to start */ - pthread_mutex_lock(&lock); - while (!write_start && !write_done) - pthread_cond_wait(&cond, &lock); - pthread_mutex_unlock(&lock); + /* Wait for first write to start */ + pthread_mutex_lock(&lock); + while (!write_start && !write_done) + pthread_cond_wait(&cond, &lock); + pthread_mutex_unlock(&lock); - close(rofd); - printf("rofd closed. write_start: %d write_done: %d\n", write_start, write_done); + close(rofd); + printf("rofd closed. write_start: %d write_done: %d\n", write_start, + write_done); - /* First write should not have been completed */ - if (write_done) - fprintf(stderr, "ERROR: close(rofd) blocked on write!\n"); + /* First write should not have been completed */ + if (write_done) + fprintf(stderr, "ERROR: close(rofd) blocked on write!\n"); - return NULL; + return NULL; } -static void* run_fs(void *data) { - struct fuse_session *se = (struct fuse_session*) data; - assert(fuse_session_loop(se) == 0); - return NULL; -} +static void *run_fs(void *data) +{ + struct fuse_session *se = (struct fuse_session *)data; -static void test_fs(char *mountpoint) { - char fname[PATH_MAX]; - char *buf; - const size_t iosize = options.data_size; - const size_t dsize = options.data_size * WRITE_SYSCALLS; - int fd, rofd; - pthread_t rofd_thread; - off_t off = 0; - - buf = malloc(dsize); - assert(buf != NULL); - assert((fd = open("/dev/urandom", O_RDONLY)) != -1); - assert(read(fd, buf, dsize) == dsize); - close(fd); - - assert(snprintf(fname, PATH_MAX, "%s/" FILE_NAME, - mountpoint) > 0); - fd = open(fname, O_WRONLY); - if (fd == -1) { - perror(fname); - assert(0); - } - - if (options.delay_ms) { - /* Verify that close(rofd) does not block waiting for pending writes */ - rofd = open(fname, O_RDONLY); - assert(pthread_create(&rofd_thread, NULL, close_rofd, (void *)(long)rofd) == 0); - /* Give close_rofd time to start */ - usleep(options.delay_ms * 1000); - } - - for (int cnt = 0; cnt < WRITE_SYSCALLS; cnt++) { - assert(pwrite(fd, buf + off, iosize, off) == iosize); - off += iosize; - assert(off <= dsize); - } - free(buf); - close(fd); - - if (options.delay_ms) { - printf("rwfd closed. write_start: %d write_done: %d\n", write_start, write_done); - assert(pthread_join(rofd_thread, NULL) == 0); - } + assert(fuse_session_loop(se) == 0); + return NULL; } -int main(int argc, char *argv[]) { - struct fuse_args args = FUSE_ARGS_INIT(argc, argv); - struct fuse_session *se; - struct fuse_cmdline_opts fuse_opts; - pthread_t fs_thread; +static void test_fs(char *mountpoint) +{ + char fname[PATH_MAX]; + char *buf; + const size_t iosize = options.data_size; + const size_t dsize = options.data_size * WRITE_SYSCALLS; + int fd, rofd; + pthread_t rofd_thread; + off_t off = 0; + + buf = malloc(dsize); + assert(buf != NULL); + assert((fd = open("/dev/urandom", O_RDONLY)) != -1); + assert(read(fd, buf, dsize) == dsize); + close(fd); + + assert(snprintf(fname, PATH_MAX, "%s/" FILE_NAME, mountpoint) > 0); + fd = open(fname, O_WRONLY); + if (fd == -1) { + perror(fname); + assert(0); + } + + if (options.delay_ms) { + /* Verify that close(rofd) does not block waiting for pending writes */ + rofd = open(fname, O_RDONLY); + assert(pthread_create(&rofd_thread, NULL, close_rofd, + (void *)(long)rofd) == 0); + /* Give close_rofd time to start */ + usleep(options.delay_ms * 1000); + } + + for (int cnt = 0; cnt < WRITE_SYSCALLS; cnt++) { + assert(pwrite(fd, buf + off, iosize, off) == iosize); + off += iosize; + assert(off <= dsize); + } + free(buf); + close(fd); + + if (options.delay_ms) { + printf("rwfd closed. write_start: %d write_done: %d\n", + write_start, write_done); + assert(pthread_join(rofd_thread, NULL) == 0); + } +} - assert(fuse_opt_parse(&args, &options, option_spec, NULL) == 0); - assert(fuse_parse_cmdline(&args, &fuse_opts) == 0); -#ifndef __FreeBSD__ - assert(fuse_opt_add_arg(&args, "-oauto_unmount") == 0); +int main(int argc, char *argv[]) +{ + struct fuse_args args = FUSE_ARGS_INIT(argc, argv); + struct fuse_session *se; + struct fuse_cmdline_opts fuse_opts; + pthread_t fs_thread; + + assert(fuse_opt_parse(&args, &options, option_spec, NULL) == 0); + assert(fuse_parse_cmdline(&args, &fuse_opts) == 0); +#ifndef __FreeBSD__ + assert(fuse_opt_add_arg(&args, "-oauto_unmount") == 0); #endif - se = fuse_session_new(&args, &tfs_oper, - sizeof(tfs_oper), NULL); - fuse_opt_free_args(&args); - assert (se != NULL); - assert(fuse_set_signal_handlers(se) == 0); - assert(fuse_session_mount(se, fuse_opts.mountpoint) == 0); + se = fuse_session_new(&args, &tfs_oper, sizeof(tfs_oper), NULL); + fuse_opt_free_args(&args); + assert(se != NULL); + assert(fuse_set_signal_handlers(se) == 0); + assert(fuse_session_mount(se, fuse_opts.mountpoint) == 0); - /* Start file-system thread */ - assert(pthread_create(&fs_thread, NULL, run_fs, (void *)se) == 0); + /* Start file-system thread */ + assert(pthread_create(&fs_thread, NULL, run_fs, (void *)se) == 0); - /* Write test data */ - test_fs(fuse_opts.mountpoint); - free(fuse_opts.mountpoint); + /* Write test data */ + test_fs(fuse_opts.mountpoint); + free(fuse_opts.mountpoint); - /* Stop file system */ - fuse_session_exit(se); - fuse_session_unmount(se); - assert(pthread_join(fs_thread, NULL) == 0); + /* Stop file system */ + fuse_session_exit(se); + fuse_session_unmount(se); + assert(pthread_join(fs_thread, NULL) == 0); - assert(got_write == 1); + assert(got_write == 1); - /* + /* * when writeback cache is enabled, kernel side can merge requests, but * memory pressure, system 'sync' might trigger data flushes before - flush * might happen in between write syscalls - merging subpage writes into @@ -293,19 +302,18 @@ int main(int argc, char *argv[]) { * Though we can expect that that at least some (but maybe all) write * system calls can be merged. */ - if (options.writeback) - assert(write_cnt < WRITE_SYSCALLS); - else - assert(write_cnt == WRITE_SYSCALLS); + if (options.writeback) + assert(write_cnt < WRITE_SYSCALLS); + else + assert(write_cnt == WRITE_SYSCALLS); - fuse_remove_signal_handlers(se); - fuse_session_destroy(se); + fuse_remove_signal_handlers(se); + fuse_session_destroy(se); - printf("Test completed successfully.\n"); - return 0; + printf("Test completed successfully.\n"); + return 0; } - /** * Local Variables: * mode: c From f68970cd235a7e14026ca0f6240428bbebe8223b Mon Sep 17 00:00:00 2001 From: Bernd Schubert <bernd@bsbernd.com> Date: Sat, 22 Mar 2025 23:57:55 +0100 Subject: [PATCH 073/241] 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> --- .github/workflows/checkpatch.yml | 2 +- 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 +- 11 files changed, 265 insertions(+), 31 deletions(-) create mode 100644 test/test_want_conversion.c diff --git a/.github/workflows/checkpatch.yml b/.github/workflows/checkpatch.yml index 45133eb87..8eb6acf06 100644 --- a/.github/workflows/checkpatch.yml +++ b/.github/workflows/checkpatch.yml @@ -30,7 +30,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 --max-line-length=100 --no-tree --ignore MAINTAINERS,SPDX_LICENSE_TAG,COMMIT_MESSAGE,FILE_PATH_CHANGES,EMAIL_SUBJECT,AVOID_EXTERNS,GIT_COMMIT_ID,ENOSYS_SYSCALL,ENOSYS,FROM_SIGN_OFF_MISMATCH,QUOTED_COMMIT_ID -g $commit; then + if ! ./checkpatch.pl --max-line-length=100 --no-tree --ignore MAINTAINERS,SPDX_LICENSE_TAG,COMMIT_MESSAGE,FILE_PATH_CHANGES,EMAIL_SUBJECT,AVOID_EXTERNS,GIT_COMMIT_ID,ENOSYS_SYSCALL,ENOSYS,FROM_SIGN_OFF_MISMATCH,QUOTED_COMMIT_ID,PREFER_ATTRIBUTE_ALWAYS_UNUSED,PREFER_DEFINED_ATTRIBUTE_MACRO -g $commit; then echo "checkpatch.pl found issues in commit $commit - $subject" exit 1 fi 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 bf8410f33..23fcaa6d4 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) \ ({ \ @@ -224,3 +227,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 1e2491bbe..17bc81692 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 1fe3fdeb8..0b0dad262 100644 --- a/lib/helper.c +++ b/lib/helper.c @@ -426,13 +426,13 @@ void fuse_apply_conn_info_opts(struct fuse_conn_info_opts *opts, #define LL_ENABLE(cond, cap) \ do { \ if (cond) \ - conn->want |= (cap); \ + conn->want_ext |= (cap); \ } while (0) #define LL_DISABLE(cond, cap) \ do { \ if (cond) \ - conn->want &= ~(cap); \ + conn->want_ext &= ~(cap); \ } while (0) LL_ENABLE(opts->splice_read, FUSE_CAP_SPLICE_READ); diff --git a/lib/util.h b/lib/util.h index a5c5463a5..508fafb12 100644 --- a/lib/util.h +++ b/lib/util.h @@ -1,6 +1,33 @@ +#ifndef FUSE_UTIL_H_ +#define FUSE_UTIL_H_ + +#include <stdint.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) 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 2e9c671e2..2b5b4f383 100644 --- a/test/test_write_cache.c +++ b/test/test_write_cache.c @@ -66,7 +66,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 1f1a78b18d29fc95b0f134b1134532000f4a726f Mon Sep 17 00:00:00 2001 From: Bernd Schubert <bernd@bsbernd.com> Date: Tue, 25 Mar 2025 09:50:00 +0100 Subject: [PATCH 074/241] checkpatch: Ignore dependabot commits Thes commits have the habit to persistently fail, mostly with long lines. We don't have control over these commits, so let's ignore them. Signed-off-by: Bernd Schubert <bernd@bsbernd.com> --- .github/workflows/checkpatch.yml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.github/workflows/checkpatch.yml b/.github/workflows/checkpatch.yml index 8eb6acf06..0da6c5410 100644 --- a/.github/workflows/checkpatch.yml +++ b/.github/workflows/checkpatch.yml @@ -28,6 +28,11 @@ jobs: 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 + # Skip dependabot commits - we do not have control over it + if git log -1 --format='%ae' $commit | grep -q "dependabot"; then + echo "Skipping dependabot commit: $commit" + continue + fi subject=$(git log -1 --format=%s $commit) echo "Checking commit: $commit - $subject" if ! ./checkpatch.pl --max-line-length=100 --no-tree --ignore MAINTAINERS,SPDX_LICENSE_TAG,COMMIT_MESSAGE,FILE_PATH_CHANGES,EMAIL_SUBJECT,AVOID_EXTERNS,GIT_COMMIT_ID,ENOSYS_SYSCALL,ENOSYS,FROM_SIGN_OFF_MISMATCH,QUOTED_COMMIT_ID,PREFER_ATTRIBUTE_ALWAYS_UNUSED,PREFER_DEFINED_ATTRIBUTE_MACRO -g $commit; then From 86817c3ca0d2574aa87cdd84eb7a326c74e38b59 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 25 Mar 2025 09:27:43 +0000 Subject: [PATCH 075/241] build(deps): bump github/codeql-action from 3.28.11 to 3.28.13 Bumps [github/codeql-action](https://github.com/github/codeql-action) from 3.28.11 to 3.28.13. - [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/6bb031afdd8eb862ea3fc1848194185e076637e5...1b549b9259bda1cb5ddde3b41741a82a2d15a841) --- 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 c2cb051a7..12c6c8dee 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@6bb031afdd8eb862ea3fc1848194185e076637e5 # v3.28.11 + uses: github/codeql-action/init@1b549b9259bda1cb5ddde3b41741a82a2d15a841 # v3.28.13 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@6bb031afdd8eb862ea3fc1848194185e076637e5 # v3.28.11 + uses: github/codeql-action/analyze@1b549b9259bda1cb5ddde3b41741a82a2d15a841 # v3.28.13 with: category: "/language:${{matrix.language}}" From 6cdb65047f60057724d0939836c261bb40433e53 Mon Sep 17 00:00:00 2001 From: izxl007 <zeng.zheng@zte.com.cn> Date: Tue, 25 Mar 2025 22:15:38 +0800 Subject: [PATCH 076/241] example/hello_ll.c: Improve the compilation comments When building and installing FUSE3 using Meson on CentOS8, the fuse3.pc file is installed in the /usr/local/lib64/pkgconfig directory. However, pkg-config does not search for fuse3.pc in this directory, leading to GCC compilation failures. This patch improves the compilation comments, helping users successfully execute the GCC command. Signed-off-by: izxl007 <zeng.zheng@zte.com.cn> --- example/hello_ll.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/example/hello_ll.c b/example/hello_ll.c index 12927cc2f..b1ef69275 100644 --- a/example/hello_ll.c +++ b/example/hello_ll.c @@ -14,6 +14,10 @@ * * gcc -Wall hello_ll.c `pkg-config fuse3 --cflags --libs` -o hello_ll * + * Note: If the pkg-config command fails due to the absence of the fuse3.pc + * file, you should configure the path to the fuse3.pc file in the + * PKG_CONFIG_PATH variable. + * * ## Source code ## * \include hello_ll.c */ From a25fb9bd49ef56a2223262784f18dd9bbc2601dc Mon Sep 17 00:00:00 2001 From: izxl007 <zeng.zheng@zte.com.cn> Date: Thu, 27 Mar 2025 12:24:40 +0800 Subject: [PATCH 077/241] example: Add a compilation instruction in README.compile When building FUSE3 with Meson on CentOS 8, the fuse3.pc file gets installed in /usr/local/lib64/pkgconfig. Since pkg-config doesn't search this path by default, GCC compilation fails due to missing FUSE3 flags. This patch adds an instruction for setting PKG_CONFIG_PATH variable in README.compile to fix GCC compilation issues. Signed-off-by: izxl007 <zeng.zheng@zte.com.cn> --- example/README.compile | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 example/README.compile diff --git a/example/README.compile b/example/README.compile new file mode 100644 index 000000000..a7b681982 --- /dev/null +++ b/example/README.compile @@ -0,0 +1,4 @@ +Note: + * If the pkg-config command fails due to the absence of the fuse3.pc file, + you should configure the path to the fuse3.pc file in the PKG_CONFIG_PATH + variable. \ No newline at end of file From eabe4b62d1ca945eaa65054cee5b0cd0b7583459 Mon Sep 17 00:00:00 2001 From: Joanne Koong <joannelkoong@gmail.com> Date: Mon, 24 Feb 2025 12:56:34 -0800 Subject: [PATCH 078/241] lib: Add usdt.h Copy over usdt.h from [1] as part of the prepatory work for adding tracepoints for requests. [1] https://github.com/libbpf/usdt Signed-off-by: Joanne Koong <joannelkoong@gmail.com> --- lib/usdt.h | 540 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 540 insertions(+) create mode 100644 lib/usdt.h diff --git a/lib/usdt.h b/lib/usdt.h new file mode 100644 index 000000000..6a0bc4258 --- /dev/null +++ b/lib/usdt.h @@ -0,0 +1,540 @@ +/* + * Copied from https://github.com/libbpf/usdt/ + */ + +// SPDX-License-Identifier: BSD-2-Clause +/* + * This single-header library defines a collection of variadic macros for + * defining and triggering USDTs (User Statically-Defined Tracepoints): + * + * - For USDTs without associated semaphore: + * USDT(group, name, args...) + * + * - For USDTs with implicit (transparent to the user) semaphore: + * USDT_WITH_SEMA(group, name, args...) + * USDT_IS_ACTIVE(group, name) + * + * - For USDTs with explicit (user-defined and provided) semaphore: + * USDT_WITH_EXPLICIT_SEMA(sema, group, name, args...) + * USDT_SEMA_IS_ACTIVE(sema) + * + * all of which emit a NOP instruction into the instruction stream, and so + * have *zero* overhead for the surrounding code. USDTs are identified by + * a combination of `group` and `name` identifiers, which is used by external + * tracing tooling (tracers) for identifying exact USDTs of interest. + * + * USDTs can have an associated (2-byte) activity counter (USDT semaphore), + * automatically maintained by Linux kernel whenever any correctly written + * BPF-based tracer is attached to the USDT. This USDT semaphore can be used + * to check whether there is a need to do any extra data collection and + * processing for a given USDT (if necessary), and otherwise avoid extra work + * for a common case of USDT not being traced ("active"). + * + * See documentation for USDT_WITH_SEMA()/USDT_IS_ACTIVE() or + * USDT_WITH_EXPLICIT_SEMA()/USDT_SEMA_IS_ACTIVE() APIs below for details on + * working with USDTs with implicitly or explicitly associated + * USDT semaphores, respectively. + * + * There is also some additional data recorded into an auxiliary note + * section. The data in the note section describes the operands, in terms of + * size and location, used by tracing tooling to know where to find USDT + * arguments. Each location is encoded as an assembler operand string. + * Tracing tools (bpftrace and BPF-based tracers, systemtap, etc) insert + * breakpoints on top of the nop, and decode the location operand-strings, + * like an assembler, to find the values being passed. + * + * The operand strings are selected by the compiler for each operand. + * They are constrained by inline-assembler codes.The default is: + * + * #define USDT_ARG_CONSTRAINT nor + * + * This is a good default if the operands tend to be integral and + * moderate in number (smaller than number of registers). In other + * cases, the compiler may report "'asm' requires impossible reload" or + * similar. In this case, consider simplifying the macro call (fewer + * and simpler operands), reduce optimization, or override the default + * constraints string via: + * + * #define USDT_ARG_CONSTRAINT g + * #include <usdt.h> + * + * For some historical description of USDT v3 format (the one used by this + * library and generally recognized and assumed by BPF-based tracing tools) + * see [0]. The more formal specification can be found at [1]. Additional + * argument constraints information can be found at [2]. + * + * Original SystemTap's sys/sdt.h implementation ([3]) was used as a base for + * this USDT library implementation. Current implementation differs *a lot* in + * terms of exposed user API and general usability, which was the main goal + * and focus of the reimplementation work. Nevertheless, underlying recorded + * USDT definitions are fully binary compatible and any USDT-based tooling + * should work equally well with USDTs defined by either SystemTap's or this + * library's USDT implementation. + * + * [0] https://ecos.sourceware.org/ml/systemtap/2010-q3/msg00145.html + * [1] https://sourceware.org/systemtap/wiki/UserSpaceProbeImplementation + * [2] https://gcc.gnu.org/onlinedocs/gcc/Constraints.html + * [3] https://sourceware.org/git/?p=systemtap.git;a=blob;f=includes/sys/sdt.h + */ +#ifndef __USDT_H +#define __USDT_H + +/* + * Changelog: + * + * 0.1.0 + * ----- + * - Initial release + */ +#define USDT_MAJOR_VERSION 0 +#define USDT_MINOR_VERSION 1 +#define USDT_PATCH_VERSION 0 + +/* C++20 and C23 added __VA_OPT__ as a standard replacement for non-standard `##__VA_ARGS__` extension */ +#if (defined(__STDC_VERSION__) && __STDC_VERSION__ > 201710L) || (defined(__cplusplus) && __cplusplus > 201703L) +#define __usdt_va_opt 1 +#define __usdt_va_args(...) __VA_OPT__(,) __VA_ARGS__ +#else +#define __usdt_va_args(...) , ##__VA_ARGS__ +#endif + +/* + * Trigger USDT with `group`:`name` identifier and pass through `args` as its + * arguments. Zero arguments are acceptable as well. No USDT semaphore is + * associated with this USDT. + * + * Such "semaphoreless" USDTs are commonly used when there is no extra data + * collection or processing needed to collect and prepare USDT arguments and + * they are just available in the surrounding code. USDT() macro will just + * record their locations in CPU registers or in memory for tracing tooling to + * be able to access them, if necessary. + */ +#ifdef __usdt_va_opt +#define USDT(group, name, ...) \ + __usdt_probe(group, name, __usdt_sema_none, 0 __VA_OPT__(,) __VA_ARGS__) +#else +#define USDT(group, name, ...) \ + __usdt_probe(group, name, __usdt_sema_none, 0, ##__VA_ARGS__) +#endif + +/* + * Trigger USDT with `group`:`name` identifier and pass through `args` as its + * arguments. Zero arguments are acceptable as well. USDT also get an + * implicitly-defined associated USDT semaphore, which will be "activated" by + * tracing tooling and can be used to check whether USDT is being actively + * observed. + * + * USDTs with semaphore are commonly used when there is a need to perform + * additional data collection and processing to prepare USDT arguments, which + * otherwise might not be necessary for the rest of application logic. In such + * case, USDT semaphore can be used to avoid unnecessary extra work. If USDT + * is not traced (which is presumed to be a common situation), the associated + * USDT semaphore is "inactive", and so there is no need to waste resources to + * prepare USDT arguments. Use USDT_IS_ACTIVE(group, name) to check whether + * USDT is "active". + * + * N.B. There is an inherent (albeit short) gap between checking whether USDT + * is active and triggering corresponding USDT, in which external tracer can + * be attached to an USDT and activate USDT semaphore after the activity check. + * If such a race occurs, tracers might miss one USDT execution. Tracers are + * expected to accommodate such possibility and this is expected to not be + * a problem for applications and tracers. + * + * N.B. Implicit USDT semaphore defined by USDT_WITH_SEMA() is contained + * within a single executable or shared library and is not shared outside + * them. I.e., if you use USDT_WITH_SEMA() with the same USDT group and name + * identifier across executable and shared library, it will work and won't + * conflict, per se, but will define independent USDT semaphores, one for each + * shared library/executable in which USDT_WITH_SEMA(group, name) is used. + * That is, if you attach to this USDT in one shared library (or executable), + * then only USDT semaphore within that shared library (or executable) will be + * updated by the kernel, while other libraries (or executable) will not see + * activated USDT semaphore. In short, it's best to use unique USDT group:name + * identifiers across different shared libraries (and, equivalently, between + * executable and shared library). This is advanced consideration and is + * rarely (if ever) seen in practice, but just to avoid surprises this is + * called out here. (Static libraries become a part of final executable, once + * linked by linker, so the above considerations don't apply to them.) + */ +#ifdef __usdt_va_opt +#define USDT_WITH_SEMA(group, name, ...) \ + __usdt_probe(group, name, \ + __usdt_sema_implicit, __usdt_sema_name(group, name) \ + __VA_OPT__(,) __VA_ARGS__) +#else +#define USDT_WITH_SEMA(group, name, ...) \ + __usdt_probe(group, name, \ + __usdt_sema_implicit, __usdt_sema_name(group, name), \ + ##__VA_ARGS__) +#endif + +struct usdt_sema { volatile unsigned short active; }; + +/* + * Check if USDT with `group`:`name` identifier is "active" (i.e., whether it + * is attached to by external tracing tooling and is actively observed). + * + * This macro can be used to decide whether any additional and potentially + * expensive data collection or processing should be done to pass extra + * information into the given USDT. It is assumed that USDT is triggered with + * USDT_WITH_SEMA() macro which will implicitly define associated USDT + * semaphore. (If one needs more control over USDT semaphore, see + * USDT_DEFINE_SEMA() and USDT_WITH_EXPLICIT_SEMA() macros below.) + * + * N.B. Such checks are necessarily racy and speculative. Between checking + * whether USDT is active and triggering the USDT itself, tracer can be + * detached with no notification. This race should be extremely rare and worst + * case should result in one-time wasted extra data collection and processing. + */ +#define USDT_IS_ACTIVE(group, name) ({ \ + extern struct usdt_sema __usdt_sema_name(group, name) \ + __usdt_asm_name(__usdt_sema_name(group, name)); \ + __usdt_sema_implicit(__usdt_sema_name(group, name)); \ + __usdt_sema_name(group, name).active > 0; \ +}) + +/* + * APIs for working with user-defined explicit USDT semaphores. + * + * This is a less commonly used advanced API for use cases in which user needs + * an explicit control over (potentially shared across multiple USDTs) USDT + * semaphore instance. This can be used when there is a group of logically + * related USDTs that all need extra data collection and processing whenever + * any of a family of related USDTs are "activated" (i.e., traced). In such + * a case, all such related USDTs will be associated with the same shared USDT + * semaphore defined with USDT_DEFINE_SEMA() and the USDTs themselves will be + * triggered with USDT_WITH_EXPLICIT_SEMA() macros, taking an explicit extra + * USDT semaphore identifier as an extra parameter. + */ + +/** + * Underlying C global variable name for user-defined USDT semaphore with + * `sema` identifier. Could be useful for debugging, but normally shouldn't be + * used explicitly. + */ +#define USDT_SEMA(sema) __usdt_sema_##sema + +/* + * Define storage for user-defined USDT semaphore `sema`. + * + * Should be used only once in non-header source file to let compiler allocate + * space for the semaphore variable. Just like with any other global variable. + * + * This macro can be used anywhere where global variable declaration is + * allowed. Just like with global variable definitions, there should be only + * one definition of user-defined USDT semaphore with given `sema` identifier, + * otherwise compiler or linker will complain about duplicate variable + * definition. + * + * For C++, it is allowed to use USDT_DEFINE_SEMA() both in global namespace + * and inside namespaces (including nested namespaces). Just make sure that + * USDT_DECLARE_SEMA() is placed within the namespace where this semaphore is + * referenced, or any of its parent namespaces, so the C++ language-level + * identifier is visible to the code that needs to reference the semaphore. + * At the lowest layer, USDT semaphores have global naming and visibility + * (they have a corresponding `__usdt_sema_<name>` symbol, which can be linked + * against from C or C++ code, if necessary). To keep it simple, putting + * USDT_DECLARE_SEMA() declarations into global namespaces is the simplest + * no-brainer solution. All these aspects are irrelevant for plain C, because + * C doesn't have namespaces and everything is always in the global namespace. + * + * N.B. Due to USDT metadata being recorded in non-allocatable ELF note + * section, it has limitations when it comes to relocations, which, in + * practice, means that it's not possible to correctly share USDT semaphores + * between main executable and shared libraries, or even between multiple + * shared libraries. USDT semaphore has to be contained to individual shared + * library or executable to avoid unpleasant surprises with half-working USDT + * semaphores. We enforce this by marking semaphore ELF symbols as having + * a hidden visibility. This is quite an advanced use case and consideration + * and for most users this should have no consequences whatsoever. + */ +#define USDT_DEFINE_SEMA(sema) \ + struct usdt_sema __usdt_sema_sec USDT_SEMA(sema) \ + __usdt_asm_name(USDT_SEMA(sema)) \ + __attribute__((visibility("hidden"))) = { 0 } + +/* + * Declare extern reference to user-defined USDT semaphore `sema`. + * + * Refers to a variable defined in another compilation unit by + * USDT_DEFINE_SEMA() and allows to use the same USDT semaphore across + * multiple compilation units (i.e., .c and .cpp files). + * + * See USDT_DEFINE_SEMA() notes above for C++ language usage peculiarities. + */ +#define USDT_DECLARE_SEMA(sema) \ + extern struct usdt_sema USDT_SEMA(sema) __usdt_asm_name(USDT_SEMA(sema)) + +/* + * Check if user-defined USDT semaphore `sema` is "active" (i.e., whether it + * is attached to by external tracing tooling and is actively observed). + * + * This macro can be used to decide whether any additional and potentially + * expensive data collection or processing should be done to pass extra + * information into USDT(s) associated with USDT semaphore `sema`. + * + * N.B. Such checks are necessarily racy. Between checking the state of USDT + * semaphore and triggering associated USDT(s), the active tracer might attach + * or detach. This race should be extremely rare and worst case should result + * in one-time missed USDT event or wasted extra data collection and + * processing. USDT-using tracers should be written with this in mind and is + * not a concern of the application defining USDTs with associated semaphore. + */ +#define USDT_SEMA_IS_ACTIVE(sema) (USDT_SEMA(sema).active > 0) + +/* + * Invoke USDT specified by `group` and `name` identifiers and associate + * explicitly user-defined semaphore `sema` with it. Pass through `args` as + * USDT arguments. `args` are optional and zero arguments are acceptable. + * + * Semaphore is defined with the help of USDT_DEFINE_SEMA() macro and can be + * checked whether active with USDT_SEMA_IS_ACTIVE(). + */ +#ifdef __usdt_va_opt +#define USDT_WITH_EXPLICIT_SEMA(sema, group, name, ...) \ + __usdt_probe(group, name, __usdt_sema_explicit, USDT_SEMA(sema), ##__VA_ARGS__) +#else +#define USDT_WITH_EXPLICIT_SEMA(sema, group, name, ...) \ + __usdt_probe(group, name, __usdt_sema_explicit, USDT_SEMA(sema) __VA_OPT__(,) __VA_ARGS__) +#endif + +/* + * Adjustable implementation aspects + */ +#ifndef USDT_ARG_CONSTRAINT +#if defined __powerpc__ +#define USDT_ARG_CONSTRAINT nZr +#elif defined __arm__ +#define USDT_ARG_CONSTRAINT g +#elif defined __loongarch__ +#define USDT_ARG_CONSTRAINT nmr +#else +#define USDT_ARG_CONSTRAINT nor +#endif +#endif /* USDT_ARG_CONSTRAINT */ + +#ifndef USDT_NOP +#if defined(__ia64__) || defined(__s390__) || defined(__s390x__) +#define USDT_NOP nop 0 +#else +#define USDT_NOP nop +#endif +#endif /* USDT_NOP */ + +/* + * Implementation details + */ +/* USDT name for implicitly-defined USDT semaphore, derived from group:name */ +#define __usdt_sema_name(group, name) __usdt_sema_##group##__##name +/* ELF section into which USDT semaphores are put */ +#define __usdt_sema_sec __attribute__((section(".probes"))) + +#define __usdt_concat(a, b) a ## b +#define __usdt_apply(fn, n) __usdt_concat(fn, n) + +#ifndef __usdt_nth +#define __usdt_nth(_, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, N, ...) N +#endif + +#ifndef __usdt_narg +#ifdef __usdt_va_opt +#define __usdt_narg(...) __usdt_nth(_ __VA_OPT__(,) __VA_ARGS__, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0) +#else +#define __usdt_narg(...) __usdt_nth(_, ##__VA_ARGS__, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0) +#endif +#endif /* __usdt_narg */ + +#define __usdt_hash # +#define __usdt_str_(x) #x +#define __usdt_str(x) __usdt_str_(x) + +#ifndef __usdt_asm_name +#define __usdt_asm_name(name) __asm__(__usdt_str(name)) +#endif + +#define __usdt_asm1(a) __usdt_str(a) "\n" +#define __usdt_asm2(a,b) __usdt_str(a) "," __usdt_str(b) "\n" +#define __usdt_asm3(a,b,c) __usdt_str(a) "," __usdt_str(b) "," __usdt_str(c) "\n" +#define __usdt_asm5(a,b,c,d,e) __usdt_str(a) "," __usdt_str(b) "," __usdt_str(c) "," \ + __usdt_str(d) "," __usdt_str(e) "\n" + +#ifdef __LP64__ +#define __usdt_asm_addr .8byte +#else +#define __usdt_asm_addr .4byte +#endif + +#define __usdt_asm_strz_(x) __usdt_asm1(.asciz #x) +#define __usdt_asm_strz(x) __usdt_asm_strz_(x) +#define __usdt_asm_str_(x) __usdt_asm1(.ascii #x) +#define __usdt_asm_str(x) __usdt_asm_str_(x) + +/* "semaphoreless" USDT case */ +#ifndef __usdt_sema_none +#define __usdt_sema_none(sema) +#endif + +/* implicitly defined __usdt_sema__group__name semaphore (using weak symbols) */ +#ifndef __usdt_sema_implicit +#define __usdt_sema_implicit(sema) \ + __asm__ __volatile__ ( \ + __usdt_asm1(.ifndef sema) \ + __usdt_asm3( .pushsection .probes, "aw", "progbits") \ + __usdt_asm1( .weak sema) \ + __usdt_asm1( .hidden sema) \ + __usdt_asm1( .align 2) \ + __usdt_asm1(sema:) \ + __usdt_asm1( .zero 2) \ + __usdt_asm2( .type sema, @object) \ + __usdt_asm2( .size sema, 2) \ + __usdt_asm1( .popsection) \ + __usdt_asm1(.endif) \ + ); +#endif + +/* externally defined semaphore using USDT_DEFINE_SEMA() and passed explicitly by user */ +#ifndef __usdt_sema_explicit +#define __usdt_sema_explicit(sema) \ + __asm__ __volatile__ ("" :: "m" (sema)); +#endif + +/* main USDT definition (nop and .note.stapsdt metadata) */ +#define __usdt_probe(group, name, sema_def, sema, ...) do { \ + sema_def(sema) \ + __asm__ __volatile__ ( \ + __usdt_asm1(990: USDT_NOP) \ + __usdt_asm3( .pushsection .note.stapsdt, "", "note") \ + __usdt_asm1( .balign 4) \ + __usdt_asm3( .4byte 992f-991f,994f-993f,3) \ + __usdt_asm1(991: .asciz "stapsdt") \ + __usdt_asm1(992: .balign 4) \ + __usdt_asm1(993: __usdt_asm_addr 990b) \ + __usdt_asm1( __usdt_asm_addr _.stapsdt.base) \ + __usdt_asm1( __usdt_asm_addr sema) \ + __usdt_asm_strz(group) \ + __usdt_asm_strz(name) \ + __usdt_asm_args(__VA_ARGS__) \ + __usdt_asm1( .ascii "\0") \ + __usdt_asm1(994: .balign 4) \ + __usdt_asm1( .popsection) \ + __usdt_asm1(.ifndef _.stapsdt.base) \ + __usdt_asm5( .pushsection .stapsdt.base,"aG","progbits",.stapsdt.base,comdat)\ + __usdt_asm1( .weak _.stapsdt.base) \ + __usdt_asm1( .hidden _.stapsdt.base) \ + __usdt_asm1(_.stapsdt.base:) \ + __usdt_asm1( .space 1) \ + __usdt_asm2( .size _.stapsdt.base, 1) \ + __usdt_asm1( .popsection) \ + __usdt_asm1(.endif) \ + :: __usdt_asm_ops(__VA_ARGS__) \ + ); \ +} while (0) + +/* + * NB: gdb PR24541 highlighted an unspecified corner of the sdt.h + * operand note format. + * + * The named register may be a longer or shorter (!) alias for the + * storage where the value in question is found. For example, on + * i386, 64-bit value may be put in register pairs, and a register + * name stored would identify just one of them. Previously, gcc was + * asked to emit the %w[id] (16-bit alias of some registers holding + * operands), even when a wider 32-bit value was used. + * + * Bottom line: the byte-width given before the @ sign governs. If + * there is a mismatch between that width and that of the named + * register, then a sys/sdt.h note consumer may need to employ + * architecture-specific heuristics to figure out where the compiler + * has actually put the complete value. + */ +#if defined(__powerpc__) || defined(__powerpc64__) +#define __usdt_argref(id) %I[id]%[id] +#elif defined(__i386__) +#define __usdt_argref(id) %k[id] /* gcc.gnu.org/PR80115 sourceware.org/PR24541 */ +#else +#define __usdt_argref(id) %[id] +#endif + +#define __usdt_asm_arg(n) __usdt_asm_str(%c[__usdt_asz##n]) \ + __usdt_asm1(.ascii "@") \ + __usdt_asm_str(__usdt_argref(__usdt_aval##n)) + +#define __usdt_asm_args0 /* no arguments */ +#define __usdt_asm_args1 __usdt_asm_arg(1) +#define __usdt_asm_args2 __usdt_asm_args1 __usdt_asm1(.ascii " ") __usdt_asm_arg(2) +#define __usdt_asm_args3 __usdt_asm_args2 __usdt_asm1(.ascii " ") __usdt_asm_arg(3) +#define __usdt_asm_args4 __usdt_asm_args3 __usdt_asm1(.ascii " ") __usdt_asm_arg(4) +#define __usdt_asm_args5 __usdt_asm_args4 __usdt_asm1(.ascii " ") __usdt_asm_arg(5) +#define __usdt_asm_args6 __usdt_asm_args5 __usdt_asm1(.ascii " ") __usdt_asm_arg(6) +#define __usdt_asm_args7 __usdt_asm_args6 __usdt_asm1(.ascii " ") __usdt_asm_arg(7) +#define __usdt_asm_args8 __usdt_asm_args7 __usdt_asm1(.ascii " ") __usdt_asm_arg(8) +#define __usdt_asm_args9 __usdt_asm_args8 __usdt_asm1(.ascii " ") __usdt_asm_arg(9) +#define __usdt_asm_args10 __usdt_asm_args9 __usdt_asm1(.ascii " ") __usdt_asm_arg(10) +#define __usdt_asm_args11 __usdt_asm_args10 __usdt_asm1(.ascii " ") __usdt_asm_arg(11) +#define __usdt_asm_args12 __usdt_asm_args11 __usdt_asm1(.ascii " ") __usdt_asm_arg(12) +#define __usdt_asm_args(...) __usdt_apply(__usdt_asm_args, __usdt_narg(__VA_ARGS__)) + +#define __usdt_is_arr(x) (__builtin_classify_type(x) == 14 || __builtin_classify_type(x) == 5) +#define __usdt_arg_size(x) (__usdt_is_arr(x) ? sizeof(void *) : sizeof(x)) + +/* + * We can't use __builtin_choose_expr() in C++, so fall back to table-based + * signedness determination for known types, utilizing templates magic. + */ +#ifdef __cplusplus + +#define __usdt_is_signed(x) (!__usdt_is_arr(x) && __usdt_t<__typeof(x)>::is_signed) + +#include <cstddef> + +template<typename T> struct __usdt_t { static const bool is_signed = false; }; +template<typename A> struct __usdt_t<A[]> : public __usdt_t<A *> {}; +template<typename A, size_t N> struct __usdt_t<A[N]> : public __usdt_t<A *> {}; + +#define __usdt_def_signed(T) \ +template<> struct __usdt_t<T> { static const bool is_signed = true; }; \ +template<> struct __usdt_t<const T> { static const bool is_signed = true; }; \ +template<> struct __usdt_t<volatile T> { static const bool is_signed = true; }; \ +template<> struct __usdt_t<const volatile T> { static const bool is_signed = true; } +#define __usdt_maybe_signed(T) \ +template<> struct __usdt_t<T> { static const bool is_signed = (T)-1 < (T)1; }; \ +template<> struct __usdt_t<const T> { static const bool is_signed = (T)-1 < (T)1; }; \ +template<> struct __usdt_t<volatile T> { static const bool is_signed = (T)-1 < (T)1; }; \ +template<> struct __usdt_t<const volatile T> { static const bool is_signed = (T)-1 < (T)1; } + +__usdt_def_signed(signed char); +__usdt_def_signed(short); +__usdt_def_signed(int); +__usdt_def_signed(long); +__usdt_def_signed(long long); +__usdt_maybe_signed(char); +__usdt_maybe_signed(wchar_t); + +#else /* !__cplusplus */ + +#define __usdt_is_inttype(x) (__builtin_classify_type(x) >= 1 && __builtin_classify_type(x) <= 4) +#define __usdt_inttype(x) __typeof(__builtin_choose_expr(__usdt_is_inttype(x), (x), 0U)) +#define __usdt_is_signed(x) ((__usdt_inttype(x))-1 < (__usdt_inttype(x))1) + +#endif /* __cplusplus */ + +#define __usdt_asm_op(n, x) \ + [__usdt_asz##n] "n" ((__usdt_is_signed(x) ? (int)-1 : 1) * (int)__usdt_arg_size(x)), \ + [__usdt_aval##n] __usdt_str(USDT_ARG_CONSTRAINT)(x) + +#define __usdt_asm_ops0() [__usdt_dummy] "g" (0) +#define __usdt_asm_ops1(x) __usdt_asm_op(1, x) +#define __usdt_asm_ops2(a,x) __usdt_asm_ops1(a), __usdt_asm_op(2, x) +#define __usdt_asm_ops3(a,b,x) __usdt_asm_ops2(a,b), __usdt_asm_op(3, x) +#define __usdt_asm_ops4(a,b,c,x) __usdt_asm_ops3(a,b,c), __usdt_asm_op(4, x) +#define __usdt_asm_ops5(a,b,c,d,x) __usdt_asm_ops4(a,b,c,d), __usdt_asm_op(5, x) +#define __usdt_asm_ops6(a,b,c,d,e,x) __usdt_asm_ops5(a,b,c,d,e), __usdt_asm_op(6, x) +#define __usdt_asm_ops7(a,b,c,d,e,f,x) __usdt_asm_ops6(a,b,c,d,e,f), __usdt_asm_op(7, x) +#define __usdt_asm_ops8(a,b,c,d,e,f,g,x) __usdt_asm_ops7(a,b,c,d,e,f,g), __usdt_asm_op(8, x) +#define __usdt_asm_ops9(a,b,c,d,e,f,g,h,x) __usdt_asm_ops8(a,b,c,d,e,f,g,h), __usdt_asm_op(9, x) +#define __usdt_asm_ops10(a,b,c,d,e,f,g,h,i,x) __usdt_asm_ops9(a,b,c,d,e,f,g,h,i), __usdt_asm_op(10, x) +#define __usdt_asm_ops11(a,b,c,d,e,f,g,h,i,j,x) __usdt_asm_ops10(a,b,c,d,e,f,g,h,i,j), __usdt_asm_op(11, x) +#define __usdt_asm_ops12(a,b,c,d,e,f,g,h,i,j,k,x) __usdt_asm_ops11(a,b,c,d,e,f,g,h,i,j,k), __usdt_asm_op(12, x) +#define __usdt_asm_ops(...) __usdt_apply(__usdt_asm_ops, __usdt_narg(__VA_ARGS__))(__VA_ARGS__) + +#endif /* __USDT_H */ From 11fde2346885683202eace0302ae4dc39869bfec Mon Sep 17 00:00:00 2001 From: Joanne Koong <joannelkoong@gmail.com> Date: Fri, 21 Feb 2025 13:24:39 -0800 Subject: [PATCH 079/241] fuse_lowlevel: add tracepoints for request receiving, processing, and replying Add user statically-defined tracepoints for request lifecycle. Verified by: [machine]$ readelf -n /home/libfuse/build/lib/libfuse3.so | grep -A 1000 '.note.stapsdt' Displaying notes found in: .note.stapsdt Owner Data size Description stapsdt 0x00000035 NT_STAPSDT (SystemTap probe descriptors) Provider: libfuse Name: request_receive Location: 0x0000000000016360, Base: 0x00000000000002fc, Semaphore: 0x0000000000000000 Arguments: -4@%edi stapsdt 0x00000055 NT_STAPSDT (SystemTap probe descriptors) Provider: libfuse Name: request_reply Location: 0x0000000000017c0f, Base: 0x00000000000002fc, Semaphore: 0x0000000000000000 Arguments: 8@8(%r12) 4@(%r12) -4@4(%r12) -4@%r13d stapsdt 0x0000003e NT_STAPSDT (SystemTap probe descriptors) Provider: libfuse Name: request_process Location: 0x000000000001acf7, Base: 0x00000000000002fc, Semaphore: 0x0000000000000000 Arguments: 4@%r8d 4@%edx Signed-off-by: Joanne Koong <joannelkoong@gmail.com> --- lib/fuse_lowlevel.c | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/lib/fuse_lowlevel.c b/lib/fuse_lowlevel.c index 17bc81692..a5d9e0c21 100644 --- a/lib/fuse_lowlevel.c +++ b/lib/fuse_lowlevel.c @@ -17,6 +17,7 @@ #include "fuse_opt.h" #include "fuse_misc.h" #include "mount_util.h" +#include "usdt.h" #include "util.h" #include <stdint.h> @@ -60,6 +61,23 @@ static __attribute__((constructor)) void fuse_ll_init_pagesize(void) pagesize = getpagesize(); } +/* tracepoints */ +static void trace_request_receive(int err) +{ + USDT(libfuse, request_receive, err); +} + +static void trace_request_process(unsigned int opcode, unsigned int unique) +{ + USDT(libfuse, request_process, opcode, unique); +} + +static void trace_request_reply(uint64_t unique, unsigned int len, + int error, int reply_err) +{ + USDT(libfuse, request_reply, unique, len, error, reply_err); +} + static void convert_stat(const struct stat *stbuf, struct fuse_attr *attr) { attr->ino = stbuf->st_ino; @@ -207,6 +225,7 @@ static int fuse_send_msg(struct fuse_session *se, struct fuse_chan *ch, res = writev(ch ? ch->fd : se->fd, iov, count); int err = errno; + trace_request_reply(out->unique, out->len, out->error, err); if (res == -1) { /* ENOENT means the operation was interrupted */ @@ -2809,6 +2828,8 @@ void fuse_session_process_buf_internal(struct fuse_session *se, in = buf->mem; } + trace_request_process(in->opcode, in->unique); + if (se->debug) { fuse_log(FUSE_LOG_DEBUG, "unique: %llu, opcode: %s (%i), nodeid: %llu, insize: %zu, pid: %u\n", @@ -3067,6 +3088,7 @@ static int _fuse_session_receive_buf(struct fuse_session *se, bufsize, 0); } err = errno; + trace_request_receive(err); if (fuse_session_exited(se)) return 0; @@ -3171,6 +3193,7 @@ static int _fuse_session_receive_buf(struct fuse_session *se, res = read(ch ? ch->fd : se->fd, buf->mem, bufsize); } err = errno; + trace_request_receive(err); if (fuse_session_exited(se)) return 0; From 9265134c5097d6a640171566510f26633a98614e Mon Sep 17 00:00:00 2001 From: Joanne Koong <joannelkoong@gmail.com> Date: Mon, 24 Feb 2025 13:11:38 -0800 Subject: [PATCH 080/241] example: add usdt.bt Add example bpftrace file for monitoring tracepoints. Signed-off-by: Joanne Koong <joannelkoong@gmail.com> --- example/usdt.bt | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) create mode 100644 example/usdt.bt diff --git a/example/usdt.bt b/example/usdt.bt new file mode 100644 index 000000000..41cfcdc59 --- /dev/null +++ b/example/usdt.bt @@ -0,0 +1,19 @@ +#!/usr/bin/env bpftrace + +// To run, do `sudo bpftrace usdt.bt` + +usdt:../build/lib/libfuse3.so:libfuse:request_receive +{ + printf("libfuse:request_receive hit, err=%d\n", arg0); +} + +usdt:../build/lib/libfuse3.so:libfuse:request_process +{ + printf("libfuse:request_process hit, opcode=%u, unique=%u\n", arg0, arg1); +} + +usdt:../build/lib/libfuse3.so:libfuse:request_reply +{ + printf("libfuse:request_reply hit, unique=%lu, len=%u, err=%u, reply_err=%d\n", + arg0, arg1, arg2, arg3); +} From 206f4a4ec05cb7ce2f8f47c8f5b4b2c80ea221a2 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 081/241] 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 cbcd70d02..96c655c31 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 197ea99fe4520b1ada8a4f2e3e1dcaf6de145002 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 082/241] 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 96c655c31..bab98da3c 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 ead0a13d57dcbc5ac7b5396722a61cc99cd0c9dc Mon Sep 17 00:00:00 2001 From: Joanne Koong <joannelkoong@gmail.com> Date: Wed, 2 Apr 2025 16:24:12 -0700 Subject: [PATCH 083/241] meson: add option for enabling usdt Default is having usdt disabled. Signed-off-by: Joanne Koong <joannelkoong@gmail.com> --- lib/fuse_lowlevel.c | 27 ++++++++++++++++++++++++++- meson.build | 2 ++ meson_options.txt | 2 ++ 3 files changed, 30 insertions(+), 1 deletion(-) diff --git a/lib/fuse_lowlevel.c b/lib/fuse_lowlevel.c index a5d9e0c21..ff7a73b2b 100644 --- a/lib/fuse_lowlevel.c +++ b/lib/fuse_lowlevel.c @@ -17,7 +17,6 @@ #include "fuse_opt.h" #include "fuse_misc.h" #include "mount_util.h" -#include "usdt.h" #include "util.h" #include <stdint.h> @@ -34,6 +33,10 @@ #include <sys/file.h> #include <sys/ioctl.h> +#ifdef USDT_ENABLED +#include "usdt.h" +#endif + #ifndef F_LINUX_SPECIFIC_BASE #define F_LINUX_SPECIFIC_BASE 1024 #endif @@ -61,6 +64,7 @@ static __attribute__((constructor)) void fuse_ll_init_pagesize(void) pagesize = getpagesize(); } +#ifdef USDT_ENABLED /* tracepoints */ static void trace_request_receive(int err) { @@ -77,6 +81,27 @@ static void trace_request_reply(uint64_t unique, unsigned int len, { USDT(libfuse, request_reply, unique, len, error, reply_err); } +#else +static void trace_request_receive(int err) +{ + (void)err; +} + +static void trace_request_process(unsigned int opcode, unsigned int unique) +{ + (void)opcode; + (void)unique; +} + +static void trace_request_reply(uint64_t unique, unsigned int len, + int error, int reply_err) +{ + (void)unique; + (void)len; + (void)error; + (void)reply_err; +} +#endif static void convert_stat(const struct stat *stbuf, struct fuse_attr *attr) { diff --git a/meson.build b/meson.build index bab98da3c..f28df3db5 100644 --- a/meson.build +++ b/meson.build @@ -98,6 +98,8 @@ private_cfg.set('HAVE_STRUCT_STAT_ST_ATIMESPEC', prefix: include_default, args: args_default)) +private_cfg.set('USDT_ENABLED', get_option('enable-usdt')) + # # Compiler configuration # diff --git a/meson_options.txt b/meson_options.txt index fa4749c72..957c5fc36 100644 --- a/meson_options.txt +++ b/meson_options.txt @@ -22,3 +22,5 @@ option('tests', type : 'boolean', value : true, option('disable-libc-symbol-version', type : 'boolean', value : false, description: 'Disable versioned symbols through libc') +option('enable-usdt', type : 'boolean', value : false, + description: 'Enable user statically defined tracepoints for extra observability') From 12507408c311601b9e933645b10d8f801f32452c Mon Sep 17 00:00:00 2001 From: Bernd Schubert <bschubert@ddn.com> Date: Mon, 7 Apr 2025 11:50:19 +0200 Subject: [PATCH 084/241] 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 1c8db6c4d..8b5214c21 100644 --- a/example/passthrough_hp.cc +++ b/example/passthrough_hp.cc @@ -228,6 +228,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 ff7a73b2b..cf8a7b0d6 100644 --- a/lib/fuse_lowlevel.c +++ b/lib/fuse_lowlevel.c @@ -3159,16 +3159,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; @@ -3198,14 +3198,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: @@ -3223,7 +3227,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 cb586bbfc3a1fc0e00ef8d8f8b73c881ff99f908 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 8 Apr 2025 00:28:39 +0000 Subject: [PATCH 085/241] build(deps): bump github/codeql-action from 3.28.13 to 3.28.15 Bumps [github/codeql-action](https://github.com/github/codeql-action) from 3.28.13 to 3.28.15. - [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/1b549b9259bda1cb5ddde3b41741a82a2d15a841...45775bd8235c68ba998cffa5171334d58593da47) --- updated-dependencies: - dependency-name: github/codeql-action dependency-version: 3.28.15 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 12c6c8dee..9c59174eb 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@1b549b9259bda1cb5ddde3b41741a82a2d15a841 # v3.28.13 + uses: github/codeql-action/init@45775bd8235c68ba998cffa5171334d58593da47 # v3.28.15 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@1b549b9259bda1cb5ddde3b41741a82a2d15a841 # v3.28.13 + uses: github/codeql-action/analyze@45775bd8235c68ba998cffa5171334d58593da47 # v3.28.15 with: category: "/language:${{matrix.language}}" From 0d4a6281c297aeb200dc30664372fc252fff0f99 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 086/241] 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 d66ca89e86a72fa5ad48d88ef5570062a79397be Mon Sep 17 00:00:00 2001 From: Meng Lu Wang <mwang@ddn.com> Date: Thu, 10 Apr 2025 16:58:57 +0800 Subject: [PATCH 087/241] mount: Add FUSE_KERN_DEVICE env variable to specify fuse kernel device For kernel development it might be necessary to load a module with renamed symbols and renamed /dev/<devicenode>. Reason is that for example ubuntu kernels have fuse compiled in and it is not possible to replace it at run time. And fuse might also be used for other file systems - a different device node is then needed. Also consolidate device path handling and remove unnecessary string duplication in mount_fuse() in fusermount.c. Signed-off-by: Meng Lu Wang <mwang@ddn.com> --- lib/mount.c | 7 ++++-- util/fusermount.c | 54 ++++++++++++----------------------------------- 2 files changed, 19 insertions(+), 42 deletions(-) diff --git a/lib/mount.c b/lib/mount.c index 6ed4444b3..34e5dd454 100644 --- a/lib/mount.c +++ b/lib/mount.c @@ -49,6 +49,7 @@ #define FUSERMOUNT_PROG "fusermount3" #define FUSE_COMMFD_ENV "_FUSE_COMMFD" #define FUSE_COMMFD2_ENV "_FUSE_COMMFD2" +#define FUSE_KERN_DEVICE_ENV "FUSE_KERN_DEVICE" #ifndef MS_DIRSYNC #define MS_DIRSYNC 128 @@ -506,7 +507,7 @@ static int fuse_mount_sys(const char *mnt, struct mount_opts *mo, const char *mnt_opts) { char tmp[128]; - const char *devname = "/dev/fuse"; + const char *devname = getenv(FUSE_KERN_DEVICE_ENV) ?: "/dev/fuse"; char *source = NULL; char *type = NULL; struct stat stbuf; @@ -528,7 +529,9 @@ static int fuse_mount_sys(const char *mnt, struct mount_opts *mo, fd = open(devname, O_RDWR | O_CLOEXEC); if (fd == -1) { if (errno == ENODEV || errno == ENOENT) - fuse_log(FUSE_LOG_ERR, "fuse: device not found, try 'modprobe fuse' first\n"); + fuse_log(FUSE_LOG_ERR, + "fuse: device %s not found. Kernel module not loaded?\n", + devname); else fuse_log(FUSE_LOG_ERR, "fuse: failed to open %s: %s\n", devname, strerror(errno)); diff --git a/util/fusermount.c b/util/fusermount.c index da6d5f2be..68fa31e55 100644 --- a/util/fusermount.c +++ b/util/fusermount.c @@ -41,6 +41,7 @@ #endif #define FUSE_COMMFD_ENV "_FUSE_COMMFD" +#define FUSE_KERN_DEVICE_ENV "FUSE_KERN_DEVICE" #define FUSE_DEV "/dev/fuse" @@ -1163,56 +1164,30 @@ static int check_perm(const char **mntp, struct stat *stbuf, int *mountpoint_fd) return -1; } -static int try_open(const char *dev, char **devp, int silent) -{ - int fd = open(dev, O_RDWR); - if (fd != -1) { - *devp = strdup(dev); - if (*devp == NULL) { - fprintf(stderr, "%s: failed to allocate memory\n", - progname); - close(fd); - fd = -1; - } - } else if (errno == ENODEV || - errno == ENOENT)/* check for ENOENT too, for the udev case */ - return -2; - else if (!silent) { - fprintf(stderr, "%s: failed to open %s: %s\n", progname, dev, - strerror(errno)); - } - return fd; -} - -static int try_open_fuse_device(char **devp) +static int open_fuse_device(const char *dev) { int fd; drop_privs(); - fd = try_open(FUSE_DEV, devp, 0); + fd = open(dev, O_RDWR); + if (fd == -1) { + if (errno == ENODEV || errno == ENOENT)/* check for ENOENT too, for the udev case */ + fprintf(stderr, + "%s: fuse device %s not found. Kernel module not loaded?\n", + progname, dev); + else + fprintf(stderr, + "%s: failed to open %s: %s\n", progname, dev, strerror(errno)); + } restore_privs(); return fd; } -static int open_fuse_device(char **devp) -{ - int fd = try_open_fuse_device(devp); - if (fd >= -1) - return fd; - - fprintf(stderr, - "%s: fuse device not found, try 'modprobe fuse' first\n", - progname); - - return -1; -} - - static int mount_fuse(const char *mnt, const char *opts, const char **type) { int res; int fd; - char *dev; + const char *dev = getenv(FUSE_KERN_DEVICE_ENV) ?: FUSE_DEV; struct stat stbuf; char *source = NULL; char *mnt_opts = NULL; @@ -1221,7 +1196,7 @@ static int mount_fuse(const char *mnt, const char *opts, const char **type) char *do_mount_opts = NULL; char *x_opts = NULL; - fd = open_fuse_device(&dev); + fd = open_fuse_device(dev); if (fd == -1) return -1; @@ -1292,7 +1267,6 @@ static int mount_fuse(const char *mnt, const char *opts, const char **type) out_free: free(source); free(mnt_opts); - free(dev); free(x_opts); free(do_mount_opts); From 3863da58b1f7904675ca050434d8219bc410f34a Mon Sep 17 00:00:00 2001 From: Bernd Schubert <bschubert@ddn.com> Date: Wed, 16 Apr 2025 00:24:42 +0200 Subject: [PATCH 088/241] 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 7c60178b42a301c3cd068d38d5606c9b044ba41c 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 089/241] 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 f28df3db5..a3aa73510 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 f0dd86451023a6e5b0aeb5625130e404a6cb1db6 Mon Sep 17 00:00:00 2001 From: swj <1186093704@qq.com> Date: Wed, 23 Apr 2025 22:39:28 +0800 Subject: [PATCH 090/241] 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 e025a78d9b296bc78e9e3ac2925d8bc0ec26d702 Mon Sep 17 00:00:00 2001 From: Bernd Schubert <bernd@bsbernd.com> Date: Wed, 23 Apr 2025 15:16:42 +0200 Subject: [PATCH 091/241] 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 c5dbcdce2d1942abb567d03bf9dafb74f06b5769 Mon Sep 17 00:00:00 2001 From: Bernd Schubert <bschubert@ddn.com> Date: Sun, 30 Mar 2025 22:43:09 +0200 Subject: [PATCH 092/241] Fix multi-threaded fuse session exit Issue with previous code was that fuse_session_exit() didn't wake up the semaphore in fuse_loop_mt.c. Lock, semaphore and all uses of checking for "exited" are now moved to struct fuse_session to have it available for the signal handler. This also removes internal fuse_session_reset() calls, as that makes testing hard. From git history I also don't see why it was added. Closes: https://github.com/libfuse/libfuse/issues/997 Signed-off-by: Bernd Schubert <bschubert@ddn.com> --- lib/fuse.c | 1 - lib/fuse_i.h | 8 +- lib/fuse_loop_mt.c | 60 ++++++------- lib/fuse_lowlevel.c | 18 +++- lib/fuse_signals.c | 13 ++- test/meson.build | 3 + test/test_ctests.py | 11 +++ test/test_signals.c | 202 ++++++++++++++++++++++++++++++++++++++++++++ 8 files changed, 277 insertions(+), 39 deletions(-) create mode 100644 test/test_signals.c diff --git a/lib/fuse.c b/lib/fuse.c index 49f57112a..02ceed202 100644 --- a/lib/fuse.c +++ b/lib/fuse.c @@ -4595,7 +4595,6 @@ static int fuse_session_loop_remember(struct fuse *f) } free(fbuf.mem); - fuse_session_reset(se); return res < 0 ? -1 : 0; } diff --git a/lib/fuse_i.h b/lib/fuse_i.h index 48b8294f3..1f5994427 100644 --- a/lib/fuse_i.h +++ b/lib/fuse_i.h @@ -10,6 +10,8 @@ #include "fuse_lowlevel.h" #include "util.h" +#include <pthread.h> +#include <semaphore.h> #include <stdint.h> #include <stdbool.h> #include <errno.h> @@ -55,7 +57,6 @@ struct fuse_notify_req { struct fuse_session { char *mountpoint; - volatile int exited; int fd; struct fuse_custom_io *io; struct mount_opts *mo; @@ -83,6 +84,11 @@ struct fuse_session { */ struct libfuse_version version; + /* thread synchronization */ + _Atomic bool mt_exited; + pthread_mutex_t mt_lock; + sem_t mt_finish; + /* true if reading requests from /dev/fuse are handled internally */ bool buf_reallocable; }; diff --git a/lib/fuse_loop_mt.c b/lib/fuse_loop_mt.c index d6be99849..c13b476f9 100644 --- a/lib/fuse_loop_mt.c +++ b/lib/fuse_loop_mt.c @@ -53,14 +53,12 @@ struct fuse_worker { struct fuse_mt *mt; }; +/* synchronization via se->mt_lock */ struct fuse_mt { - pthread_mutex_t lock; int numworker; int numavail; struct fuse_session *se; struct fuse_worker main; - sem_t finish; - int exit; int error; int clone_fd; int max_idle; @@ -131,32 +129,32 @@ static void *fuse_do_work(void *data) { struct fuse_worker *w = (struct fuse_worker *) data; struct fuse_mt *mt = w->mt; + struct fuse_session *se = mt->se; #ifdef HAVE_PTHREAD_SETNAME_NP pthread_setname_np(pthread_self(), "fuse_worker"); #endif - while (!fuse_session_exited(mt->se)) { + while (!fuse_session_exited(se)) { int isforget = 0; int res; pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL); - res = fuse_session_receive_buf_internal(mt->se, &w->fbuf, - w->ch); + res = fuse_session_receive_buf_internal(se, &w->fbuf, w->ch); pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, NULL); if (res == -EINTR) continue; if (res <= 0) { if (res < 0) { - fuse_session_exit(mt->se); + fuse_session_exit(se); mt->error = res; } break; } - pthread_mutex_lock(&mt->lock); - if (mt->exit) { - pthread_mutex_unlock(&mt->lock); + pthread_mutex_lock(&se->mt_lock); + if (fuse_session_exited(se)) { + pthread_mutex_unlock(&se->mt_lock); return NULL; } @@ -176,11 +174,11 @@ static void *fuse_do_work(void *data) mt->numavail--; if (mt->numavail == 0 && mt->numworker < mt->max_threads) fuse_loop_start_thread(mt); - pthread_mutex_unlock(&mt->lock); + pthread_mutex_unlock(&se->mt_lock); - fuse_session_process_buf_internal(mt->se, &w->fbuf, w->ch); + fuse_session_process_buf_internal(se, &w->fbuf, w->ch); - pthread_mutex_lock(&mt->lock); + pthread_mutex_lock(&se->mt_lock); if (!isforget) mt->numavail++; @@ -191,14 +189,14 @@ static void *fuse_do_work(void *data) * delayed, a moving average might be useful for that. */ if (mt->max_idle != -1 && mt->numavail > mt->max_idle && mt->numworker > 1) { - if (mt->exit) { - pthread_mutex_unlock(&mt->lock); + if (fuse_session_exited(se)) { + pthread_mutex_unlock(&se->mt_lock); return NULL; } list_del_worker(w); mt->numavail--; mt->numworker--; - pthread_mutex_unlock(&mt->lock); + pthread_mutex_unlock(&se->mt_lock); pthread_detach(w->thread_id); fuse_buf_free(&w->fbuf); @@ -206,11 +204,10 @@ static void *fuse_do_work(void *data) free(w); return NULL; } - pthread_mutex_unlock(&mt->lock); + pthread_mutex_unlock(&se->mt_lock); } - sem_post(&mt->finish); - + sem_post(&se->mt_finish); return NULL; } @@ -354,9 +351,9 @@ static int fuse_loop_start_thread(struct fuse_mt *mt) static void fuse_join_worker(struct fuse_mt *mt, struct fuse_worker *w) { pthread_join(w->thread_id, NULL); - pthread_mutex_lock(&mt->lock); + pthread_mutex_lock(&mt->se->mt_lock); list_del_worker(w); - pthread_mutex_unlock(&mt->lock); + pthread_mutex_unlock(&mt->se->mt_lock); fuse_buf_free(&w->fbuf); fuse_chan_put(w->ch); free(w); @@ -392,22 +389,21 @@ int err; mt.max_threads = config->max_threads; mt.main.thread_id = pthread_self(); mt.main.prev = mt.main.next = &mt.main; - sem_init(&mt.finish, 0, 0); - pthread_mutex_init(&mt.lock, NULL); - pthread_mutex_lock(&mt.lock); + pthread_mutex_lock(&se->mt_lock); err = fuse_loop_start_thread(&mt); - pthread_mutex_unlock(&mt.lock); + pthread_mutex_unlock(&se->mt_lock); if (!err) { - /* sem_wait() is interruptible */ while (!fuse_session_exited(se)) - sem_wait(&mt.finish); + sem_wait(&se->mt_finish); + if (se->debug) + fuse_log(FUSE_LOG_DEBUG, + "fuse: session exited, terminating workers\n"); - pthread_mutex_lock(&mt.lock); + pthread_mutex_lock(&se->mt_lock); for (w = mt.main.next; w != &mt.main; w = w->next) pthread_cancel(w->thread_id); - mt.exit = 1; - pthread_mutex_unlock(&mt.lock); + pthread_mutex_unlock(&se->mt_lock); while (mt.main.next != &mt.main) fuse_join_worker(&mt, mt.main.next); @@ -415,11 +411,9 @@ int err; err = mt.error; } - pthread_mutex_destroy(&mt.lock); - sem_destroy(&mt.finish); + pthread_mutex_destroy(&se->mt_lock); if(se->error != 0) err = se->error; - fuse_session_reset(se); if (created_config) { fuse_loop_cfg_destroy(config); diff --git a/lib/fuse_lowlevel.c b/lib/fuse_lowlevel.c index cf8a7b0d6..d66e6aba2 100644 --- a/lib/fuse_lowlevel.c +++ b/lib/fuse_lowlevel.c @@ -19,6 +19,8 @@ #include "mount_util.h" #include "util.h" +#include <pthread.h> +#include <stdatomic.h> #include <stdint.h> #include <stdbool.h> #include <stdio.h> @@ -3006,6 +3008,8 @@ void fuse_session_destroy(struct fuse_session *se) if (llp != NULL) fuse_ll_pipe_free(llp); pthread_key_delete(se->pipe_key); + sem_destroy(&se->mt_finish); + pthread_mutex_destroy(&se->mt_lock); pthread_mutex_destroy(&se->lock); free(se->cuse_data); if (se->fd != -1) @@ -3358,6 +3362,8 @@ fuse_session_new_versioned(struct fuse_args *args, list_init_nreq(&se->notify_list); se->notify_ctr = 1; pthread_mutex_init(&se->lock, NULL); + sem_init(&se->mt_finish, 0, 0); + pthread_mutex_init(&se->mt_lock, NULL); err = pthread_key_create(&se->pipe_key, fuse_ll_pipe_destructor); if (err) { @@ -3382,6 +3388,8 @@ fuse_session_new_versioned(struct fuse_args *args, return se; out5: + sem_destroy(&se->mt_finish); + pthread_mutex_destroy(&se->mt_lock); pthread_mutex_destroy(&se->lock); out4: fuse_opt_free_args(args); @@ -3605,18 +3613,22 @@ int fuse_req_getgroups(fuse_req_t req, int size, gid_t list[]) __attribute__((no_sanitize_thread)) void fuse_session_exit(struct fuse_session *se) { - se->exited = 1; + atomic_store_explicit(&se->mt_exited, 1, memory_order_relaxed); + sem_post(&se->mt_finish); } __attribute__((no_sanitize_thread)) void fuse_session_reset(struct fuse_session *se) { - se->exited = 0; + se->mt_exited = false; se->error = 0; } __attribute__((no_sanitize_thread)) int fuse_session_exited(struct fuse_session *se) { - return se->exited; + bool exited = + atomic_load_explicit(&se->mt_exited, memory_order_relaxed); + + return exited ? 1 : 0; } diff --git a/lib/fuse_signals.c b/lib/fuse_signals.c index 6ac72308a..3848aec65 100644 --- a/lib/fuse_signals.c +++ b/lib/fuse_signals.c @@ -22,7 +22,12 @@ #include <execinfo.h> #endif +/* + * Must not handle SIGCANCEL, as that is used to wake up threads from + * syscalls reading requests from /dev/fuse + */ static int teardown_sigs[] = { SIGHUP, SIGINT, SIGTERM }; + static int ignore_sigs[] = { SIGPIPE}; static int fail_sigs[] = { SIGILL, SIGTRAP, SIGABRT, SIGBUS, SIGFPE, SIGSEGV }; static struct fuse_session *fuse_instance; @@ -53,8 +58,14 @@ static void dump_stack(void) static void exit_handler(int sig) { - if (fuse_instance == NULL) + if (fuse_instance == NULL) { + fuse_log(FUSE_LOG_ERR, "fuse_instance is NULL\n"); return; + } + + if (fuse_instance->debug) + fuse_log(FUSE_LOG_ERR, "exit_handler called with sig %d\n", + sig); fuse_session_exit(fuse_instance); diff --git a/test/meson.build b/test/meson.build index 56568d8f7..332921657 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_signals', 'test_signals.c', + dependencies: [ libfuse_dep, thread_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_ctests.py b/test/test_ctests.py index feefc0707..ae5cc8f15 100644 --- a/test/test_ctests.py +++ b/test/test_ctests.py @@ -144,3 +144,14 @@ def test_notify_file_size(tmpdir, notify, output_checker): logger.error(f"Failure in unmount: '{' '.join(cmdline)}'") cleanup(mount_process, mnt_dir) logger.debug("Unmount completed") + +def test_signals(output_checker): + """Test for proper signal handling (issue #1182)""" + logger = logging.getLogger(__name__) + logger.debug("Testing signal handling") + cmdline = [ pjoin(basename, 'test', 'test_signals') ] + logger.debug(f"Command line: {' '.join(cmdline)}") + subprocess.run(cmdline, stdout=output_checker.fd, \ + stderr=output_checker.fd, timeout=10, check=True) + logger.debug("Signal handling test completed successfully") + diff --git a/test/test_signals.c b/test/test_signals.c new file mode 100644 index 000000000..558202698 --- /dev/null +++ b/test/test_signals.c @@ -0,0 +1,202 @@ +/* + * FUSE: Filesystem in Userspace + * Copyright (C) 2025 Bernd Schubert <bernd@bsbernd.com> + * + * Test for signal handling in libfuse. + * + * This program can be distributed under the terms of the GNU LGPLv2. + * See the file COPYING.LIB + */ + +#define FUSE_USE_VERSION FUSE_MAKE_VERSION(3, 17) + +#include "fuse_config.h" +#include "fuse_lowlevel.h" +#include "fuse_i.h" + +#include <pthread.h> +#include <stdatomic.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <signal.h> +#include <errno.h> +#include <sys/types.h> +#include <sys/wait.h> + +static void test_ll_lookup(fuse_req_t req, fuse_ino_t parent, const char *name) +{ + (void)parent; + (void)name; + /* Simulate slow lookup to test signal interruption */ + sleep(2); + fuse_reply_err(req, ENOENT); +} + +static void test_ll_getattr(fuse_req_t req, fuse_ino_t ino, + struct fuse_file_info *fi) +{ + (void)ino; + (void)fi; + /* Simulate slow getattr to test signal interruption */ + sleep(2); + fuse_reply_err(req, ENOENT); +} + +static const struct fuse_lowlevel_ops test_ll_ops = { + .lookup = test_ll_lookup, + .getattr = test_ll_getattr, +}; + +static void *signal_sender_thread(void *arg) +{ + (void)arg; + + usleep(2 * 1000 * 1000); + + /* Send SIGTERM to the process */ + kill(getpid(), SIGTERM); + return NULL; +} + +static void fork_child(void) +{ + struct fuse_args args = FUSE_ARGS_INIT(0, NULL); + struct fuse_session *se; + struct fuse_loop_config *loop_config; + pthread_t sig_thread; + char *mountpoint = NULL; + int ret = -1; + + /* Add the program name to arg[0] */ + if (fuse_opt_add_arg(&args, "test_signals")) { + fprintf(stderr, "Failed to add argument\n"); + goto out_free_mountpoint; + } + + /* Add debug flag to see more output */ + fuse_opt_add_arg(&args, "-d"); + + /* Create temporary mount point */ + mountpoint = strdup("/tmp/fuse_test_XXXXXX"); + if (!mountpoint || !mkdtemp(mountpoint)) { + fprintf(stderr, "Failed to create temp dir\n"); + goto out_free_args; + } + + /* Create session */ + se = fuse_session_new(&args, &test_ll_ops, sizeof(test_ll_ops), NULL); + if (!se) { + fprintf(stderr, "Failed to create FUSE session\n"); + goto out_free_mountpoint; + } + + /* Mount filesystem */ + if (fuse_session_mount(se, mountpoint)) { + fprintf(stderr, "Failed to mount FUSE filesystem\n"); + goto out_destroy_session; + } + + /* Create loop config */ + loop_config = fuse_loop_cfg_create(); + if (!loop_config) { + fprintf(stderr, "Failed to create loop config\n"); + goto out_unmount; + } + fuse_loop_cfg_set_clone_fd(loop_config, 0); + fuse_loop_cfg_set_max_threads(loop_config, 2); + + /* Set up signal handlers */ + if (fuse_set_signal_handlers(se)) { + fprintf(stderr, "Failed to set up signal handlers\n"); + goto out_destroy_config; + } + + /* Create thread that will send signals */ + if (pthread_create(&sig_thread, NULL, signal_sender_thread, NULL)) { + fprintf(stderr, "Failed to create signal sender thread\n"); + goto out_remove_handlers; + } + + /* Enter FUSE loop */ + ret = fuse_session_loop_mt_312(se, loop_config); + + printf("Debug: fuse_session_loop_mt_312 returned %d\n", ret); + printf("Debug: session exited state: %d\n", fuse_session_exited(se)); + printf("Debug: session status: %d\n", se->error); + + /* Check exit status before cleanup */ + int clean_exit = (fuse_session_exited(se) && se->error == SIGTERM); + + /* Clean up */ + pthread_join(sig_thread, NULL); + fuse_remove_signal_handlers(se); + fuse_session_unmount(se); + fuse_session_destroy(se); + fuse_loop_cfg_destroy(loop_config); + rmdir(mountpoint); + free(mountpoint); + fuse_opt_free_args(&args); + + /* Use saved exit status */ + if (clean_exit) { + printf("Debug: Clean shutdown via SIGTERM\n"); + exit(0); + } + printf("Debug: Exiting with status %d\n", ret != 0); + exit(ret != 0); + +out_remove_handlers: + fuse_remove_signal_handlers(se); +out_destroy_config: + fuse_loop_cfg_destroy(loop_config); +out_unmount: + fuse_session_unmount(se); +out_destroy_session: + fuse_session_destroy(se); +out_free_mountpoint: + rmdir(mountpoint); + free(mountpoint); +out_free_args: + fuse_opt_free_args(&args); + exit(1); +} + +static void run_test_in_child(void) +{ + pid_t child; + int status; + + child = fork(); + if (child == -1) { + perror("fork"); + exit(1); + } + + if (child == 0) + fork_child(); + + /* In parent process */ + if (waitpid(child, &status, 0) == -1) { + perror("waitpid"); + exit(1); + } + + /* Check if child exited due to SIGTERM - this is expected */ + if (WIFSIGNALED(status) && WTERMSIG(status) == SIGTERM) { + printf("Child process terminated by SIGTERM as expected\n"); + exit(0); + } + + /* For any other type of exit, maintain existing behavior */ + exit(WIFEXITED(status) ? WEXITSTATUS(status) : 1); +} + +int main(void) +{ + printf("Testing SIGTERM handling in libfuse\n"); + run_test_in_child(); + printf("SIGTERM handling test passed\n"); + return 0; +} From e940beb7405e672ad98dddc13679bc9d267b78e2 Mon Sep 17 00:00:00 2001 From: Bernd Schubert <bschubert@ddn.com> Date: Thu, 24 Apr 2025 18:43:43 +0200 Subject: [PATCH 093/241] Fix test/test_examples.py::test_passthrough The test had multiple issues - default passthrough_ll timeout was used - the created file was then not listed if timeout was not passed yet - mnt_name was actually point to src_dir, file comparison of stat, etc succeeded, because the same file was compared. Switching to the right dir made stat to always fail, because st_dev is different for source and mount. I.e. the test must not compare all stat values. Not sure how this test ever passed, but in a very slow debug VM with lots of kernel debug options enabled, the default passthrough_ll timeout it systematically failed. Signed-off-by: Bernd Schubert <bschubert@ddn.com> --- test/test_examples.py | 57 ++++++++++++++++++++++++++++++++++++++----- 1 file changed, 51 insertions(+), 6 deletions(-) diff --git a/test/test_examples.py b/test/test_examples.py index 9c8b77eec..2e23697fd 100755 --- a/test/test_examples.py +++ b/test/test_examples.py @@ -157,10 +157,20 @@ def test_passthrough(short_tmpdir, name, debug, output_checker, writeback): cmdline = base_cmdline + \ [ pjoin(basename, 'example', 'passthrough'), '--plus', '-f', mnt_dir ] - else: + elif name == 'passthrough_ll': + cmdline = base_cmdline + \ + [ pjoin(basename, 'example', name), + '-f', mnt_dir, '-o', 'timeout=0' ] + else: # passthrough and passthrough_fh cmdline = base_cmdline + \ [ pjoin(basename, 'example', name), '-f', mnt_dir ] + + # Set all timeouts to 0 for everything except passthrough_ll + # (this includes passthrough, passthrough_plus, and passthrough_fh) + if name != 'passthrough_ll': + cmdline.extend(['-o', 'entry_timeout=0,negative_timeout=0,attr_timeout=0,ac_attr_timeout=0']) + if debug: cmdline.append('-d') @@ -169,7 +179,9 @@ def test_passthrough(short_tmpdir, name, debug, output_checker, writeback): pytest.skip('example does not support writeback caching') cmdline.append('-o') cmdline.append('writeback') - + + print(f"\nDebug: Command line: {' '.join(cmdline)}") + mount_process = subprocess.Popen(cmdline, stdout=output_checker.fd, stderr=output_checker.fd) try: @@ -871,25 +883,58 @@ def tst_utimens(mnt_dir, ns_tol=0): def tst_passthrough(src_dir, mnt_dir): name = name_generator() src_name = pjoin(src_dir, name) - mnt_name = pjoin(src_dir, name) + mnt_name = pjoin(mnt_dir, name) + + print(f"\nDebug: Creating file {name}") + print(f"Debug: src_name={src_name}") + print(f"Debug: mnt_name={mnt_name}") + + # First test: write to source directory assert name not in os.listdir(src_dir) assert name not in os.listdir(mnt_dir) with open(src_name, 'w') as fh: fh.write('Hello, world') + + print(f"Debug: File written to src_name") + + start_time = time.time() + while time.time() - start_time < 10: # 10 second timeout + if name in os.listdir(mnt_dir): + break + print(f"Debug: Waiting for file to appear... ({time.time() - start_time:.1f}s)") + time.sleep(0.1) + else: + pytest.fail("File did not appear in mount directory within 10 seconds") + assert name in os.listdir(src_dir) assert name in os.listdir(mnt_dir) - assert os.stat(src_name) == os.stat(mnt_name) + # Compare relevant stat attributes + src_stat = os.stat(src_name) + mnt_stat = os.stat(mnt_name) + assert src_stat.st_mode == mnt_stat.st_mode + assert src_stat.st_ino == mnt_stat.st_ino + assert src_stat.st_size == mnt_stat.st_size + assert src_stat.st_mtime == mnt_stat.st_mtime + + # Second test: write to mount directory name = name_generator() src_name = pjoin(src_dir, name) - mnt_name = pjoin(src_dir, name) + mnt_name = pjoin(mnt_dir, name) assert name not in os.listdir(src_dir) assert name not in os.listdir(mnt_dir) with open(mnt_name, 'w') as fh: fh.write('Hello, world') assert name in os.listdir(src_dir) assert name in os.listdir(mnt_dir) - assert os.stat(src_name) == os.stat(mnt_name) + + # Compare relevant stat attributes + src_stat = os.stat(src_name) + mnt_stat = os.stat(mnt_name) + assert src_stat.st_mode == mnt_stat.st_mode + assert src_stat.st_ino == mnt_stat.st_ino + assert src_stat.st_size == mnt_stat.st_size + assert abs(src_stat.st_mtime - mnt_stat.st_mtime) < 0.01 def tst_xattr(path): From 82bcd818fb3e7d5ced9b0c04b7b7a98a892e807e Mon Sep 17 00:00:00 2001 From: Bernd Schubert <bschubert@ddn.com> Date: Thu, 24 Apr 2025 16:49:08 +0200 Subject: [PATCH 094/241] 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> --- meson.build | 67 +++++++++++++++++++++++++++++++++++++++-------------- 1 file changed, 49 insertions(+), 18 deletions(-) diff --git a/meson.build b/meson.build index a3aa73510..4e5c90c34 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)) private_cfg.set('USDT_ENABLED', get_option('enable-usdt')) From 0eb8513206c2c467fa7510d0c762eb5c868dc66d 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 095/241] 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 4e5c90c34..3615a5a26 100644 --- a/meson.build +++ b/meson.build @@ -1,6 +1,6 @@ project('libfuse3', ['c'], version: '3.18.0-rc0', # Version with RC suffix - 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 066ed644762714c099667564f748058201f3f598 Mon Sep 17 00:00:00 2001 From: Bernd Schubert <bschubert@ddn.com> Date: Mon, 24 Mar 2025 17:44:56 +0100 Subject: [PATCH 096/241] Add fuse-io-uring dependencies to github workfow files Signed-off-by: Bernd Schubert <bschubert@ddn.com> --- .github/workflows/abicheck.yml | 2 +- .github/workflows/codeql.yml | 2 +- .github/workflows/pr-ci.yml | 3 ++- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/.github/workflows/abicheck.yml b/.github/workflows/abicheck.yml index 10a8e6d98..b79ccc6ed 100644 --- a/.github/workflows/abicheck.yml +++ b/.github/workflows/abicheck.yml @@ -26,7 +26,7 @@ jobs: if: runner.os == 'Linux' run: | sudo apt-get update - sudo apt-get -y install abigail-tools clang gcc + sudo apt-get -y install abigail-tools clang gcc liburing-dev libnuma-dev - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 9c59174eb..de23031a7 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -73,7 +73,7 @@ jobs: - if: matrix.build-mode == 'manual' shell: bash run: | - sudo apt install meson ninja-build python3-pytest + sudo apt install meson ninja-build python3-pytest liburing-dev libnuma-dev meson setup build --buildtype=debug meson compile -C build diff --git a/.github/workflows/pr-ci.yml b/.github/workflows/pr-ci.yml index 4bb7f66de..78b5e02a9 100644 --- a/.github/workflows/pr-ci.yml +++ b/.github/workflows/pr-ci.yml @@ -31,7 +31,8 @@ jobs: 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 + libpcap0.8-dev:i386 libudev-dev:i386 pkg-config:i386 \ + liburing-dev libnuma-dev - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - uses: actions/setup-python@v5 with: From d393ffa85b0926374c8df543a9ffc81b1d0ce232 Mon Sep 17 00:00:00 2001 From: Bernd Schubert <bschubert@ddn.com> Date: Mon, 24 Mar 2025 17:53:01 +0100 Subject: [PATCH 097/241] Synchronize fuse_kernel.h with linux-6.14 Also add FUSE_CAP_OVER_IO_URING Signed-off-by: Bernd Schubert <bschubert@ddn.com> --- include/fuse_common.h | 7 +++- include/fuse_kernel.h | 96 ++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 101 insertions(+), 2 deletions(-) diff --git a/include/fuse_common.h b/include/fuse_common.h index 582505fa9..f1d4b170a 100644 --- a/include/fuse_common.h +++ b/include/fuse_common.h @@ -7,7 +7,6 @@ /** @file */ -#include <stdbool.h> #if !defined(FUSE_H_) && !defined(FUSE_LOWLEVEL_H_) #error "Never include <fuse_common.h> directly; use <fuse.h> or <fuse_lowlevel.h> instead." #endif @@ -24,6 +23,7 @@ #include "fuse_opt.h" #include "fuse_log.h" #include <stdint.h> +#include <stdbool.h> #include <sys/types.h> #include <assert.h> @@ -515,6 +515,11 @@ struct fuse_loop_config_v1 { */ #define FUSE_CAP_NO_EXPORT_SUPPORT (1 << 30) +/** + * Indicates support for io-uring between fuse-server and fuse-client + */ +#define FUSE_CAP_OVER_IO_URING (1 << 31) + /** * Ioctl flags * diff --git a/include/fuse_kernel.h b/include/fuse_kernel.h index d08b99d60..5e0eb41d9 100644 --- a/include/fuse_kernel.h +++ b/include/fuse_kernel.h @@ -217,6 +217,18 @@ * - add backing_id to fuse_open_out, add FOPEN_PASSTHROUGH open flag * - add FUSE_NO_EXPORT_SUPPORT init flag * - add FUSE_NOTIFY_RESEND, add FUSE_HAS_RESEND init flag + * + * 7.41 + * - add FUSE_ALLOW_IDMAP + * 7.42 + * - Add FUSE_OVER_IO_URING and all other io-uring related flags and data + * structures: + * - struct fuse_uring_ent_in_out + * - struct fuse_uring_req_header + * - struct fuse_uring_cmd_req + * - FUSE_URING_IN_OUT_HEADER_SZ + * - FUSE_URING_OP_IN_OUT_SZ + * - enum fuse_uring_cmd */ #ifndef _LINUX_FUSE_H @@ -252,7 +264,7 @@ #define FUSE_KERNEL_VERSION 7 /** Minor version number of this interface */ -#define FUSE_KERNEL_MINOR_VERSION 40 +#define FUSE_KERNEL_MINOR_VERSION 42 /** The node ID of the root inode */ #define FUSE_ROOT_ID 1 @@ -421,6 +433,8 @@ struct fuse_file_lock { * FUSE_NO_EXPORT_SUPPORT: explicitly disable export support * FUSE_HAS_RESEND: kernel supports resending pending requests, and the high bit * of the request ID indicates resend requests + * FUSE_ALLOW_IDMAP: allow creation of idmapped mounts + * FUSE_OVER_IO_URING: Indicate that client supports io-uring */ #define FUSE_ASYNC_READ (1 << 0) #define FUSE_POSIX_LOCKS (1 << 1) @@ -466,6 +480,8 @@ struct fuse_file_lock { /* Obsolete alias for FUSE_DIRECT_IO_ALLOW_MMAP */ #define FUSE_DIRECT_IO_RELAX FUSE_DIRECT_IO_ALLOW_MMAP +#define FUSE_ALLOW_IDMAP (1ULL << 40) +#define FUSE_OVER_IO_URING (1ULL << 41) /** * CUSE INIT request/reply flags @@ -984,6 +1000,21 @@ struct fuse_fallocate_in { */ #define FUSE_UNIQUE_RESEND (1ULL << 63) +/** + * This value will be set by the kernel to + * (struct fuse_in_header).{uid,gid} fields in + * case when: + * - fuse daemon enabled FUSE_ALLOW_IDMAP + * - idmapping information is not available and uid/gid + * can not be mapped in accordance with an idmapping. + * + * Note: an idmapping information always available + * for inode creation operations like: + * FUSE_MKNOD, FUSE_SYMLINK, FUSE_MKDIR, FUSE_TMPFILE, + * FUSE_CREATE and FUSE_RENAME2 (with RENAME_WHITEOUT). + */ +#define FUSE_INVALID_UIDGID ((uint32_t)(-1)) + struct fuse_in_header { uint32_t len; uint32_t opcode; @@ -1186,4 +1217,67 @@ struct fuse_supp_groups { uint32_t groups[]; }; +/** + * Size of the ring buffer header + */ +#define FUSE_URING_IN_OUT_HEADER_SZ 128 +#define FUSE_URING_OP_IN_OUT_SZ 128 + +/* Used as part of the fuse_uring_req_header */ +struct fuse_uring_ent_in_out { + uint64_t flags; + + /* + * commit ID to be used in a reply to a ring request (see also + * struct fuse_uring_cmd_req) + */ + uint64_t commit_id; + + /* size of user payload buffer */ + uint32_t payload_sz; + uint32_t padding; + + uint64_t reserved; +}; + +/** + * Header for all fuse-io-uring requests + */ +struct fuse_uring_req_header { + /* struct fuse_in_header / struct fuse_out_header */ + char in_out[FUSE_URING_IN_OUT_HEADER_SZ]; + + /* per op code header */ + char op_in[FUSE_URING_OP_IN_OUT_SZ]; + + struct fuse_uring_ent_in_out ring_ent_in_out; +}; + +/** + * sqe commands to the kernel + */ +enum fuse_uring_cmd { + FUSE_IO_URING_CMD_INVALID = 0, + + /* register the request buffer and fetch a fuse request */ + FUSE_IO_URING_CMD_REGISTER = 1, + + /* commit fuse request result and fetch next request */ + FUSE_IO_URING_CMD_COMMIT_AND_FETCH = 2, +}; + +/** + * In the 80B command area of the SQE. + */ +struct fuse_uring_cmd_req { + uint64_t flags; + + /* entry identifier for commits */ + uint64_t commit_id; + + /* queue the command is for (queue index) */ + uint16_t qid; + uint8_t padding[6]; +}; + #endif /* _LINUX_FUSE_H */ From 2f4d7ca9c47d0266196578d45c6dad3bdcbee3e6 Mon Sep 17 00:00:00 2001 From: Bernd Schubert <bschubert@ddn.com> Date: Mon, 24 Mar 2025 18:07:27 +0100 Subject: [PATCH 098/241] fuse_lowlevel: Add support for header/payload separation Header/payload separation is part of the fuse-io-uring protocol and might be later on for /dev/fuse legacy communication as well. This is a preparation commit, for now fuse_ll_ops2 is unused. Signed-off-by: Bernd Schubert <bschubert@ddn.com> --- lib/cuse_lowlevel.c | 11 +- lib/fuse_i.h | 2 + lib/fuse_lowlevel.c | 766 ++++++++++++++++++++++++++++++++++---------- 3 files changed, 608 insertions(+), 171 deletions(-) diff --git a/lib/cuse_lowlevel.c b/lib/cuse_lowlevel.c index 5387f8430..a25711bfc 100644 --- a/lib/cuse_lowlevel.c +++ b/lib/cuse_lowlevel.c @@ -192,9 +192,11 @@ static int cuse_reply_init(fuse_req_t req, struct cuse_init_out *arg, return fuse_send_reply_iov_nofree(req, 0, iov, 3); } -void cuse_lowlevel_init(fuse_req_t req, fuse_ino_t nodeid, const void *inarg) +void _cuse_lowlevel_init(fuse_req_t req, const fuse_ino_t nodeid, + const void *op_in, const void *req_payload) { - struct fuse_init_in *arg = (struct fuse_init_in *) inarg; + const struct fuse_init_in *arg = op_in; + (void)req_payload; struct cuse_init_out outarg; struct fuse_session *se = req->se; struct cuse_data *cd = se->cuse_data; @@ -263,6 +265,11 @@ void cuse_lowlevel_init(fuse_req_t req, fuse_ino_t nodeid, const void *inarg) fuse_free_req(req); } +void cuse_lowlevel_init(fuse_req_t req, fuse_ino_t nodeid, const void *inarg) +{ + _cuse_lowlevel_init(req, nodeid, inarg, NULL); +} + struct fuse_session *cuse_lowlevel_setup(int argc, char *argv[], const struct cuse_info *ci, const struct cuse_lowlevel_ops *clop, diff --git a/lib/fuse_i.h b/lib/fuse_i.h index 1f5994427..89a5c6f9b 100644 --- a/lib/fuse_i.h +++ b/lib/fuse_i.h @@ -195,6 +195,8 @@ int fuse_send_reply_iov_nofree(fuse_req_t req, int error, struct iovec *iov, int count); void fuse_free_req(fuse_req_t req); +void _cuse_lowlevel_init(fuse_req_t req, const fuse_ino_t nodeid, + const void *req_header, const void *req_payload); void cuse_lowlevel_init(fuse_req_t req, fuse_ino_t nodeide, const void *inarg); int fuse_start_thread(pthread_t *thread_id, void *(*func)(void *), void *arg); diff --git a/lib/fuse_lowlevel.c b/lib/fuse_lowlevel.c index d66e6aba2..e0aa52380 100644 --- a/lib/fuse_lowlevel.c +++ b/lib/fuse_lowlevel.c @@ -1183,9 +1183,12 @@ int fuse_reply_lseek(fuse_req_t req, off_t off) return send_reply_ok(req, &arg, sizeof(arg)); } -static void do_lookup(fuse_req_t req, fuse_ino_t nodeid, const void *inarg) +static void _do_lookup(fuse_req_t req, const fuse_ino_t nodeid, + const void *op_in, const void *in_payload) { - char *name = (char *) inarg; + (void)op_in; + + char *name = (char *)in_payload; if (req->se->op.lookup) req->se->op.lookup(req, nodeid, name); @@ -1193,9 +1196,18 @@ static void do_lookup(fuse_req_t req, fuse_ino_t nodeid, const void *inarg) fuse_reply_err(req, ENOSYS); } -static void do_forget(fuse_req_t req, fuse_ino_t nodeid, const void *inarg) +static void do_lookup(fuse_req_t req, const fuse_ino_t nodeid, + const void *inarg) +{ + _do_lookup(req, nodeid, NULL, inarg); +} + +static void _do_forget(fuse_req_t req, const fuse_ino_t nodeid, + const void *op_in, const void *in_payload) { - struct fuse_forget_in *arg = (struct fuse_forget_in *) inarg; + (void)in_payload; + + struct fuse_forget_in *arg = (struct fuse_forget_in *)op_in; if (req->se->op.forget) req->se->op.forget(req, nodeid, arg->nlookup); @@ -1203,21 +1215,27 @@ static void do_forget(fuse_req_t req, fuse_ino_t nodeid, const void *inarg) fuse_reply_none(req); } -static void do_batch_forget(fuse_req_t req, fuse_ino_t nodeid, - const void *inarg) +static void do_forget(fuse_req_t req, const fuse_ino_t nodeid, + const void *inarg) +{ + _do_forget(req, nodeid, inarg, NULL); +} + +static void _do_batch_forget(fuse_req_t req, const fuse_ino_t nodeid, + const void *op_in, const void *in_payload) { - struct fuse_batch_forget_in *arg = (void *) inarg; - struct fuse_forget_one *param = (void *) PARAM(arg); + (void)nodeid; unsigned int i; - (void) nodeid; + const struct fuse_batch_forget_in *arg = op_in; + const struct fuse_forget_one *forgets = in_payload; if (req->se->op.forget_multi) { req->se->op.forget_multi(req, arg->count, - (struct fuse_forget_data *) param); + (struct fuse_forget_data *)in_payload); } else if (req->se->op.forget) { for (i = 0; i < arg->count; i++) { - struct fuse_forget_one *forget = ¶m[i]; + const struct fuse_forget_one *forget = &forgets[i]; struct fuse_req *dummy_req; dummy_req = fuse_ll_alloc_req(req->se); @@ -1237,14 +1255,25 @@ static void do_batch_forget(fuse_req_t req, fuse_ino_t nodeid, } } -static void do_getattr(fuse_req_t req, fuse_ino_t nodeid, const void *inarg) +static void do_batch_forget(fuse_req_t req, const fuse_ino_t nodeid, + const void *inarg) { + struct fuse_batch_forget_in *arg = (void *)inarg; + struct fuse_forget_one *param = (void *)PARAM(arg); + + _do_batch_forget(req, nodeid, inarg, param); +} + +static void _do_getattr(fuse_req_t req, const fuse_ino_t nodeid, + const void *op_in, const void *in_payload) +{ + struct fuse_getattr_in *arg = (struct fuse_getattr_in *)op_in; + (void)in_payload; + struct fuse_file_info *fip = NULL; struct fuse_file_info fi; if (req->se->conn.proto_minor >= 9) { - struct fuse_getattr_in *arg = (struct fuse_getattr_in *) inarg; - if (arg->getattr_flags & FUSE_GETATTR_FH) { memset(&fi, 0, sizeof(fi)); fi.fh = arg->fh; @@ -1258,9 +1287,18 @@ static void do_getattr(fuse_req_t req, fuse_ino_t nodeid, const void *inarg) fuse_reply_err(req, ENOSYS); } -static void do_setattr(fuse_req_t req, fuse_ino_t nodeid, const void *inarg) +static void do_getattr(fuse_req_t req, const fuse_ino_t nodeid, + const void *inarg) { - struct fuse_setattr_in *arg = (struct fuse_setattr_in *) inarg; + _do_getattr(req, nodeid, inarg, NULL); +} + +static void _do_setattr(fuse_req_t req, const fuse_ino_t nodeid, + const void *op_in, const void *in_payload) +{ + (void)in_payload; + const struct fuse_setattr_in *arg = op_in; + uint32_t valid = arg->valid; if (req->se->op.setattr) { struct fuse_file_info *fi = NULL; @@ -1269,32 +1307,34 @@ static void do_setattr(fuse_req_t req, fuse_ino_t nodeid, const void *inarg) memset(&stbuf, 0, sizeof(stbuf)); convert_attr(arg, &stbuf); if (arg->valid & FATTR_FH) { - arg->valid &= ~FATTR_FH; + valid &= ~FATTR_FH; memset(&fi_store, 0, sizeof(fi_store)); fi = &fi_store; fi->fh = arg->fh; } - arg->valid &= - FUSE_SET_ATTR_MODE | - FUSE_SET_ATTR_UID | - FUSE_SET_ATTR_GID | - FUSE_SET_ATTR_SIZE | - FUSE_SET_ATTR_ATIME | - FUSE_SET_ATTR_MTIME | - FUSE_SET_ATTR_KILL_SUID | - FUSE_SET_ATTR_KILL_SGID | - FUSE_SET_ATTR_ATIME_NOW | - FUSE_SET_ATTR_MTIME_NOW | - FUSE_SET_ATTR_CTIME; - - req->se->op.setattr(req, nodeid, &stbuf, arg->valid, fi); + valid &= FUSE_SET_ATTR_MODE | FUSE_SET_ATTR_UID | + FUSE_SET_ATTR_GID | FUSE_SET_ATTR_SIZE | + FUSE_SET_ATTR_ATIME | FUSE_SET_ATTR_MTIME | + FUSE_SET_ATTR_KILL_SUID | FUSE_SET_ATTR_KILL_SGID | + FUSE_SET_ATTR_ATIME_NOW | FUSE_SET_ATTR_MTIME_NOW | + FUSE_SET_ATTR_CTIME; + + req->se->op.setattr(req, nodeid, &stbuf, valid, fi); } else fuse_reply_err(req, ENOSYS); } -static void do_access(fuse_req_t req, fuse_ino_t nodeid, const void *inarg) +static void do_setattr(fuse_req_t req, const fuse_ino_t nodeid, + const void *inarg) { - struct fuse_access_in *arg = (struct fuse_access_in *) inarg; + _do_setattr(req, nodeid, inarg, NULL); +} + +static void _do_access(fuse_req_t req, const fuse_ino_t nodeid, + const void *op_in, const void *in_payload) +{ + (void)in_payload; + const struct fuse_access_in *arg = op_in; if (req->se->op.access) req->se->op.access(req, nodeid, arg->mask); @@ -1302,9 +1342,17 @@ static void do_access(fuse_req_t req, fuse_ino_t nodeid, const void *inarg) fuse_reply_err(req, ENOSYS); } -static void do_readlink(fuse_req_t req, fuse_ino_t nodeid, const void *inarg) +static void do_access(fuse_req_t req, const fuse_ino_t nodeid, + const void *inarg) +{ + _do_access(req, nodeid, inarg, NULL); +} + +static void _do_readlink(fuse_req_t req, const fuse_ino_t nodeid, + const void *op_in, const void *in_payload) { - (void) inarg; + (void)op_in; + (void)in_payload; if (req->se->op.readlink) req->se->op.readlink(req, nodeid); @@ -1312,15 +1360,20 @@ static void do_readlink(fuse_req_t req, fuse_ino_t nodeid, const void *inarg) fuse_reply_err(req, ENOSYS); } -static void do_mknod(fuse_req_t req, fuse_ino_t nodeid, const void *inarg) +static void do_readlink(fuse_req_t req, const fuse_ino_t nodeid, + const void *inarg) { - struct fuse_mknod_in *arg = (struct fuse_mknod_in *) inarg; - char *name = PARAM(arg); + _do_readlink(req, nodeid, inarg, NULL); +} + +static void _do_mknod(fuse_req_t req, const fuse_ino_t nodeid, + const void *op_in, const void *in_payload) +{ + const struct fuse_mknod_in *arg = (struct fuse_mknod_in *)op_in; + const char *name = in_payload; if (req->se->conn.proto_minor >= 12) req->ctx.umask = arg->umask; - else - name = (char *) inarg + FUSE_COMPAT_MKNOD_IN_SIZE; if (req->se->op.mknod) req->se->op.mknod(req, nodeid, name, arg->mode, arg->rdev); @@ -1328,22 +1381,45 @@ static void do_mknod(fuse_req_t req, fuse_ino_t nodeid, const void *inarg) fuse_reply_err(req, ENOSYS); } -static void do_mkdir(fuse_req_t req, fuse_ino_t nodeid, const void *inarg) +static void do_mknod(fuse_req_t req, const fuse_ino_t nodeid, const void *inarg) { - struct fuse_mkdir_in *arg = (struct fuse_mkdir_in *) inarg; + struct fuse_mknod_in *arg = (struct fuse_mknod_in *)inarg; + char *name = PARAM(arg); + + if (req->se->conn.proto_minor < 12) + name = (char *)inarg + FUSE_COMPAT_MKNOD_IN_SIZE; + + _do_mknod(req, nodeid, inarg, name); +} + +static void _do_mkdir(fuse_req_t req, const fuse_ino_t nodeid, + const void *op_in, const void *in_payload) +{ + const char *name = in_payload; + const struct fuse_mkdir_in *arg = op_in; if (req->se->conn.proto_minor >= 12) req->ctx.umask = arg->umask; if (req->se->op.mkdir) - req->se->op.mkdir(req, nodeid, PARAM(arg), arg->mode); + req->se->op.mkdir(req, nodeid, name, arg->mode); else fuse_reply_err(req, ENOSYS); } -static void do_unlink(fuse_req_t req, fuse_ino_t nodeid, const void *inarg) +static void do_mkdir(fuse_req_t req, const fuse_ino_t nodeid, const void *inarg) +{ + const struct fuse_mkdir_in *arg = inarg; + const char *name = PARAM(arg); + + _do_mkdir(req, nodeid, inarg, name); +} + +static void _do_unlink(fuse_req_t req, const fuse_ino_t nodeid, + const void *op_in, const void *in_payload) { - char *name = (char *) inarg; + (void)op_in; + const char *name = in_payload; if (req->se->op.unlink) req->se->op.unlink(req, nodeid, name); @@ -1351,9 +1427,17 @@ static void do_unlink(fuse_req_t req, fuse_ino_t nodeid, const void *inarg) fuse_reply_err(req, ENOSYS); } -static void do_rmdir(fuse_req_t req, fuse_ino_t nodeid, const void *inarg) +static void do_unlink(fuse_req_t req, const fuse_ino_t nodeid, + const void *inarg) +{ + _do_unlink(req, nodeid, NULL, inarg); +} + +static void _do_rmdir(fuse_req_t req, const fuse_ino_t nodeid, + const void *op_in, const void *in_payload) { - char *name = (char *) inarg; + (void)op_in; + const char *name = in_payload; if (req->se->op.rmdir) req->se->op.rmdir(req, nodeid, name); @@ -1361,10 +1445,17 @@ static void do_rmdir(fuse_req_t req, fuse_ino_t nodeid, const void *inarg) fuse_reply_err(req, ENOSYS); } -static void do_symlink(fuse_req_t req, fuse_ino_t nodeid, const void *inarg) +static void do_rmdir(fuse_req_t req, const fuse_ino_t nodeid, const void *inarg) +{ + _do_rmdir(req, nodeid, NULL, inarg); +} + +static void _do_symlink(fuse_req_t req, const fuse_ino_t nodeid, + const void *op_in, const void *in_payload) { - char *name = (char *) inarg; - char *linkname = ((char *) inarg) + strlen((char *) inarg) + 1; + (void)op_in; + const char *name = (char *)in_payload; + const char *linkname = name + strlen(name) + 1; if (req->se->op.symlink) req->se->op.symlink(req, linkname, nodeid, name); @@ -1372,45 +1463,63 @@ static void do_symlink(fuse_req_t req, fuse_ino_t nodeid, const void *inarg) fuse_reply_err(req, ENOSYS); } -static void do_rename(fuse_req_t req, fuse_ino_t nodeid, const void *inarg) +static void do_symlink(fuse_req_t req, const fuse_ino_t nodeid, + const void *inarg) { - struct fuse_rename_in *arg = (struct fuse_rename_in *) inarg; - char *oldname = PARAM(arg); - char *newname = oldname + strlen(oldname) + 1; + _do_symlink(req, nodeid, NULL, inarg); +} + +static void _do_rename(fuse_req_t req, const fuse_ino_t nodeid, + const void *op_in, const void *in_payload) +{ + const struct fuse_rename_in *arg = (struct fuse_rename_in *)op_in; + const char *oldname = in_payload; + const char *newname = oldname + strlen(oldname) + 1; if (req->se->op.rename) req->se->op.rename(req, nodeid, oldname, arg->newdir, newname, - 0); + 0); else fuse_reply_err(req, ENOSYS); } -static void do_rename2(fuse_req_t req, fuse_ino_t nodeid, const void *inarg) +static void do_rename(fuse_req_t req, const fuse_ino_t nodeid, + const void *inarg) +{ + const struct fuse_rename_in *arg = inarg; + const void *payload = PARAM(arg); + + _do_rename(req, nodeid, arg, payload); +} + +static void _do_rename2(fuse_req_t req, const fuse_ino_t nodeid, + const void *op_in, const void *in_payload) { - struct fuse_rename2_in *arg = (struct fuse_rename2_in *) inarg; - char *oldname = PARAM(arg); - char *newname = oldname + strlen(oldname) + 1; + const struct fuse_rename2_in *arg = op_in; + const char *oldname = in_payload; + const char *newname = oldname + strlen(oldname) + 1; if (req->se->op.rename) req->se->op.rename(req, nodeid, oldname, arg->newdir, newname, - arg->flags); + arg->flags); else fuse_reply_err(req, ENOSYS); } -static void do_link(fuse_req_t req, fuse_ino_t nodeid, const void *inarg) +static void do_rename2(fuse_req_t req, const fuse_ino_t nodeid, + const void *inarg) { - struct fuse_link_in *arg = (struct fuse_link_in *) inarg; + const struct fuse_rename2_in *arg = inarg; + const void *payload = PARAM(arg); - if (req->se->op.link) - req->se->op.link(req, arg->oldnodeid, nodeid, PARAM(arg)); - else - fuse_reply_err(req, ENOSYS); + _do_rename2(req, nodeid, arg, payload); } -static void do_tmpfile(fuse_req_t req, fuse_ino_t nodeid, const void *inarg) +static void _do_tmpfile(fuse_req_t req, fuse_ino_t nodeid, const void *op_in, + const void *in_payload) { - struct fuse_create_in *arg = (struct fuse_create_in *) inarg; + (void)in_payload; + const struct fuse_create_in *arg = op_in; if (req->se->op.tmpfile) { struct fuse_file_info fi; @@ -1426,35 +1535,79 @@ static void do_tmpfile(fuse_req_t req, fuse_ino_t nodeid, const void *inarg) fuse_reply_err(req, ENOSYS); } -static void do_create(fuse_req_t req, fuse_ino_t nodeid, const void *inarg) +static void do_tmpfile(fuse_req_t req, fuse_ino_t nodeid, const void *inarg) { struct fuse_create_in *arg = (struct fuse_create_in *) inarg; + _do_tmpfile(req, nodeid, arg, NULL); +} + +static void _do_link(fuse_req_t req, const fuse_ino_t nodeid, const void *op_in, + const void *in_payload) +{ + struct fuse_link_in *arg = (struct fuse_link_in *)op_in; + + if (req->se->op.link) + req->se->op.link(req, arg->oldnodeid, nodeid, in_payload); + else + fuse_reply_err(req, ENOSYS); +} + +static void do_link(fuse_req_t req, fuse_ino_t nodeid, const void *inarg) +{ + const struct fuse_link_in *arg = inarg; + const void *name = PARAM(arg); + + _do_link(req, nodeid, inarg, name); +} + +static void _do_create(fuse_req_t req, const fuse_ino_t nodeid, + const void *op_in, const void *in_payload) +{ + const struct fuse_create_in *arg = op_in; + const char *name = in_payload; + if (req->se->op.create) { struct fuse_file_info fi; - char *name = PARAM(arg); memset(&fi, 0, sizeof(fi)); fi.flags = arg->flags; if (req->se->conn.proto_minor >= 12) req->ctx.umask = arg->umask; - else - name = (char *) inarg + sizeof(struct fuse_open_in); + + /* XXX: fuse_create_in::open_flags */ req->se->op.create(req, nodeid, name, arg->mode, &fi); - } else + } else { fuse_reply_err(req, ENOSYS); + } +} + +static void do_create(fuse_req_t req, const fuse_ino_t nodeid, + const void *inarg) +{ + const struct fuse_create_in *arg = (struct fuse_create_in *)inarg; + void *payload = PARAM(arg); + + if (req->se->conn.proto_minor < 12) + payload = (char *)inarg + sizeof(struct fuse_open_in); + + _do_create(req, nodeid, arg, payload); } -static void do_open(fuse_req_t req, fuse_ino_t nodeid, const void *inarg) +static void _do_open(fuse_req_t req, const fuse_ino_t nodeid, const void *op_in, + const void *in_payload) { - struct fuse_open_in *arg = (struct fuse_open_in *) inarg; + (void)in_payload; + struct fuse_open_in *arg = (struct fuse_open_in *)op_in; struct fuse_file_info fi; memset(&fi, 0, sizeof(fi)); fi.flags = arg->flags; + /* XXX: fuse_open_in::open_flags */ + if (req->se->op.open) req->se->op.open(req, nodeid, &fi); else if (req->se->conn.want_ext & FUSE_CAP_NO_OPEN_SUPPORT) @@ -1463,9 +1616,16 @@ static void do_open(fuse_req_t req, fuse_ino_t nodeid, const void *inarg) fuse_reply_open(req, &fi); } -static void do_read(fuse_req_t req, fuse_ino_t nodeid, const void *inarg) +static void do_open(fuse_req_t req, const fuse_ino_t nodeid, const void *inarg) +{ + _do_open(req, nodeid, inarg, NULL); +} + +static void _do_read(fuse_req_t req, const fuse_ino_t nodeid, const void *op_in, + const void *in_payload) { - struct fuse_read_in *arg = (struct fuse_read_in *) inarg; + (void)in_payload; + struct fuse_read_in *arg = (struct fuse_read_in *)op_in; if (req->se->op.read) { struct fuse_file_info fi; @@ -1481,68 +1641,97 @@ static void do_read(fuse_req_t req, fuse_ino_t nodeid, const void *inarg) fuse_reply_err(req, ENOSYS); } -static void do_write(fuse_req_t req, fuse_ino_t nodeid, const void *inarg) +static void do_read(fuse_req_t req, const fuse_ino_t nodeid, const void *inarg) +{ + _do_read(req, nodeid, inarg, NULL); +} + +static void _do_write(fuse_req_t req, const fuse_ino_t nodeid, + const void *op_in, const void *in_payload) { - struct fuse_write_in *arg = (struct fuse_write_in *) inarg; + struct fuse_write_in *arg = (struct fuse_write_in *)op_in; + const char *buf = in_payload; struct fuse_file_info fi; - char *param; memset(&fi, 0, sizeof(fi)); fi.fh = arg->fh; fi.writepage = (arg->write_flags & FUSE_WRITE_CACHE) != 0; - if (req->se->conn.proto_minor < 9) { - param = ((char *) arg) + FUSE_COMPAT_WRITE_IN_SIZE; - } else { + if (req->se->conn.proto_minor >= 9) { fi.lock_owner = arg->lock_owner; fi.flags = arg->flags; - param = PARAM(arg); } if (req->se->op.write) - req->se->op.write(req, nodeid, param, arg->size, - arg->offset, &fi); + req->se->op.write(req, nodeid, buf, arg->size, arg->offset, + &fi); else fuse_reply_err(req, ENOSYS); } -static void do_write_buf(fuse_req_t req, fuse_ino_t nodeid, const void *inarg, - const struct fuse_buf *ibuf) +static void do_write(fuse_req_t req, const fuse_ino_t nodeid, const void *inarg) +{ + struct fuse_write_in *arg = (struct fuse_write_in *)inarg; + const void *payload; + + if (req->se->conn.proto_minor < 9) + payload = ((char *)arg) + FUSE_COMPAT_WRITE_IN_SIZE; + else + payload = PARAM(arg); + + _do_write(req, nodeid, arg, payload); +} + +static void _do_write_buf(fuse_req_t req, const fuse_ino_t nodeid, + const void *op_in, struct fuse_bufvec *bufv) { struct fuse_session *se = req->se; - struct fuse_bufvec bufv = { - .buf[0] = *ibuf, - .count = 1, - }; - struct fuse_write_in *arg = (struct fuse_write_in *) inarg; + struct fuse_write_in *arg = (struct fuse_write_in *)op_in; struct fuse_file_info fi; memset(&fi, 0, sizeof(fi)); fi.fh = arg->fh; fi.writepage = arg->write_flags & FUSE_WRITE_CACHE; + if (se->conn.proto_minor >= 9) { + fi.lock_owner = arg->lock_owner; + fi.flags = arg->flags; + } + + se->op.write_buf(req, nodeid, bufv, arg->offset, &fi); +} + +static void do_write_buf(fuse_req_t req, const fuse_ino_t nodeid, + const void *inarg, const struct fuse_buf *ibuf) +{ + struct fuse_session *se = req->se; + struct fuse_bufvec bufv = { + .buf[0] = *ibuf, + .count = 1, + }; + struct fuse_write_in *arg = (struct fuse_write_in *)inarg; + if (se->conn.proto_minor < 9) { - bufv.buf[0].mem = ((char *) arg) + FUSE_COMPAT_WRITE_IN_SIZE; + bufv.buf[0].mem = ((char *)arg) + FUSE_COMPAT_WRITE_IN_SIZE; bufv.buf[0].size -= sizeof(struct fuse_in_header) + - FUSE_COMPAT_WRITE_IN_SIZE; + FUSE_COMPAT_WRITE_IN_SIZE; assert(!(bufv.buf[0].flags & FUSE_BUF_IS_FD)); } else { - fi.lock_owner = arg->lock_owner; - fi.flags = arg->flags; if (!(bufv.buf[0].flags & FUSE_BUF_IS_FD)) bufv.buf[0].mem = PARAM(arg); bufv.buf[0].size -= sizeof(struct fuse_in_header) + - sizeof(struct fuse_write_in); + sizeof(struct fuse_write_in); } if (bufv.buf[0].size < arg->size) { - fuse_log(FUSE_LOG_ERR, "fuse: do_write_buf: buffer size too small\n"); + fuse_log(FUSE_LOG_ERR, "fuse: %s: buffer size too small\n", + __func__); fuse_reply_err(req, EIO); goto out; } bufv.buf[0].size = arg->size; - se->op.write_buf(req, nodeid, &bufv, arg->offset, &fi); + _do_write_buf(req, nodeid, inarg, &bufv); out: /* Need to reset the pipe if ->write_buf() didn't consume all data */ @@ -1550,9 +1739,11 @@ static void do_write_buf(fuse_req_t req, fuse_ino_t nodeid, const void *inarg, fuse_ll_clear_pipe(se); } -static void do_flush(fuse_req_t req, fuse_ino_t nodeid, const void *inarg) +static void _do_flush(fuse_req_t req, const fuse_ino_t nodeid, + const void *op_in, const void *in_payload) { - struct fuse_flush_in *arg = (struct fuse_flush_in *) inarg; + (void)in_payload; + struct fuse_flush_in *arg = (struct fuse_flush_in *)op_in; struct fuse_file_info fi; memset(&fi, 0, sizeof(fi)); @@ -1567,9 +1758,16 @@ static void do_flush(fuse_req_t req, fuse_ino_t nodeid, const void *inarg) fuse_reply_err(req, ENOSYS); } -static void do_release(fuse_req_t req, fuse_ino_t nodeid, const void *inarg) +static void do_flush(fuse_req_t req, const fuse_ino_t nodeid, const void *inarg) +{ + _do_flush(req, nodeid, inarg, NULL); +} + +static void _do_release(fuse_req_t req, const fuse_ino_t nodeid, + const void *op_in, const void *in_payload) { - struct fuse_release_in *arg = (struct fuse_release_in *) inarg; + (void)in_payload; + const struct fuse_release_in *arg = op_in; struct fuse_file_info fi; memset(&fi, 0, sizeof(fi)); @@ -1590,9 +1788,17 @@ static void do_release(fuse_req_t req, fuse_ino_t nodeid, const void *inarg) fuse_reply_err(req, 0); } -static void do_fsync(fuse_req_t req, fuse_ino_t nodeid, const void *inarg) +static void do_release(fuse_req_t req, const fuse_ino_t nodeid, + const void *inarg) +{ + _do_release(req, nodeid, inarg, NULL); +} + +static void _do_fsync(fuse_req_t req, const fuse_ino_t nodeid, + const void *op_in, const void *in_payload) { - struct fuse_fsync_in *arg = (struct fuse_fsync_in *) inarg; + (void)in_payload; + const struct fuse_fsync_in *arg = op_in; struct fuse_file_info fi; int datasync = arg->fsync_flags & 1; @@ -1605,13 +1811,21 @@ static void do_fsync(fuse_req_t req, fuse_ino_t nodeid, const void *inarg) fuse_reply_err(req, ENOSYS); } -static void do_opendir(fuse_req_t req, fuse_ino_t nodeid, const void *inarg) +static void do_fsync(fuse_req_t req, const fuse_ino_t nodeid, const void *inarg) { - struct fuse_open_in *arg = (struct fuse_open_in *) inarg; + _do_fsync(req, nodeid, inarg, NULL); +} + +static void _do_opendir(fuse_req_t req, const fuse_ino_t nodeid, + const void *op_in, const void *in_payload) +{ + (void)in_payload; + const struct fuse_open_in *arg = op_in; struct fuse_file_info fi; memset(&fi, 0, sizeof(fi)); fi.flags = arg->flags; + /* XXX: fuse_open_in::open_flags */ if (req->se->op.opendir) req->se->op.opendir(req, nodeid, &fi); @@ -1621,9 +1835,17 @@ static void do_opendir(fuse_req_t req, fuse_ino_t nodeid, const void *inarg) fuse_reply_open(req, &fi); } -static void do_readdir(fuse_req_t req, fuse_ino_t nodeid, const void *inarg) +static void do_opendir(fuse_req_t req, const fuse_ino_t nodeid, + const void *inarg) +{ + _do_opendir(req, nodeid, inarg, NULL); +} + +static void _do_readdir(fuse_req_t req, const fuse_ino_t nodeid, + const void *op_in, const void *in_payload) { - struct fuse_read_in *arg = (struct fuse_read_in *) inarg; + (void)in_payload; + struct fuse_read_in *arg = (struct fuse_read_in *)op_in; struct fuse_file_info fi; memset(&fi, 0, sizeof(fi)); @@ -1635,9 +1857,17 @@ static void do_readdir(fuse_req_t req, fuse_ino_t nodeid, const void *inarg) fuse_reply_err(req, ENOSYS); } -static void do_readdirplus(fuse_req_t req, fuse_ino_t nodeid, const void *inarg) +static void do_readdir(fuse_req_t req, const fuse_ino_t nodeid, + const void *inarg) { - struct fuse_read_in *arg = (struct fuse_read_in *) inarg; + _do_readdir(req, nodeid, inarg, NULL); +} + +static void _do_readdirplus(fuse_req_t req, const fuse_ino_t nodeid, + const void *op_in, const void *in_payload) +{ + (void)in_payload; + struct fuse_read_in *arg = (struct fuse_read_in *)op_in; struct fuse_file_info fi; memset(&fi, 0, sizeof(fi)); @@ -1649,9 +1879,17 @@ static void do_readdirplus(fuse_req_t req, fuse_ino_t nodeid, const void *inarg) fuse_reply_err(req, ENOSYS); } -static void do_releasedir(fuse_req_t req, fuse_ino_t nodeid, const void *inarg) +static void do_readdirplus(fuse_req_t req, const fuse_ino_t nodeid, + const void *inarg) { - struct fuse_release_in *arg = (struct fuse_release_in *) inarg; + _do_readdirplus(req, nodeid, inarg, NULL); +} + +static void _do_releasedir(fuse_req_t req, const fuse_ino_t nodeid, + const void *op_in, const void *in_payload) +{ + (void)in_payload; + struct fuse_release_in *arg = (struct fuse_release_in *)op_in; struct fuse_file_info fi; memset(&fi, 0, sizeof(fi)); @@ -1664,9 +1902,17 @@ static void do_releasedir(fuse_req_t req, fuse_ino_t nodeid, const void *inarg) fuse_reply_err(req, 0); } -static void do_fsyncdir(fuse_req_t req, fuse_ino_t nodeid, const void *inarg) +static void do_releasedir(fuse_req_t req, const fuse_ino_t nodeid, + const void *inarg) { - struct fuse_fsync_in *arg = (struct fuse_fsync_in *) inarg; + _do_releasedir(req, nodeid, inarg, NULL); +} + +static void _do_fsyncdir(fuse_req_t req, const fuse_ino_t nodeid, + const void *op_in, const void *in_payload) +{ + (void)in_payload; + struct fuse_fsync_in *arg = (struct fuse_fsync_in *)op_in; struct fuse_file_info fi; int datasync = arg->fsync_flags & 1; @@ -1678,11 +1924,18 @@ static void do_fsyncdir(fuse_req_t req, fuse_ino_t nodeid, const void *inarg) else fuse_reply_err(req, ENOSYS); } +static void do_fsyncdir(fuse_req_t req, const fuse_ino_t nodeid, + const void *inarg) +{ + _do_fsyncdir(req, nodeid, inarg, NULL); +} -static void do_statfs(fuse_req_t req, fuse_ino_t nodeid, const void *inarg) +static void _do_statfs(fuse_req_t req, const fuse_ino_t nodeid, + const void *op_in, const void *in_payload) { (void) nodeid; - (void) inarg; + (void)op_in; + (void)in_payload; if (req->se->op.statfs) req->se->op.statfs(req, nodeid); @@ -1694,55 +1947,93 @@ static void do_statfs(fuse_req_t req, fuse_ino_t nodeid, const void *inarg) fuse_reply_statfs(req, &buf); } } +static void do_statfs(fuse_req_t req, const fuse_ino_t nodeid, + const void *inarg) +{ + _do_statfs(req, nodeid, inarg, NULL); +} -static void do_setxattr(fuse_req_t req, fuse_ino_t nodeid, const void *inarg) +static void _do_setxattr(fuse_req_t req, const fuse_ino_t nodeid, + const void *op_in, const void *in_payload) { - struct fuse_session *se = req->se; - 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; - char *value = name + strlen(name) + 1; + struct fuse_setxattr_in *arg = (struct fuse_setxattr_in *)op_in; + const char *name = in_payload; + const char *value = name + strlen(name) + 1; /* XXX:The API should be extended to support extra_flags/setxattr_flags */ + if (req->se->op.setxattr) req->se->op.setxattr(req, nodeid, name, value, arg->size, - arg->flags); + arg->flags); else fuse_reply_err(req, ENOSYS); } +static void do_setxattr(fuse_req_t req, const fuse_ino_t nodeid, + const void *inarg) +{ + struct fuse_session *se = req->se; + unsigned int xattr_ext = !!(se->conn.want & FUSE_CAP_SETXATTR_EXT); + const struct fuse_setxattr_in *arg = inarg; + char *payload = xattr_ext ? PARAM(arg) : + (char *)arg + FUSE_COMPAT_SETXATTR_IN_SIZE; -static void do_getxattr(fuse_req_t req, fuse_ino_t nodeid, const void *inarg) + _do_setxattr(req, nodeid, arg, payload); +} + +static void _do_getxattr(fuse_req_t req, const fuse_ino_t nodeid, + const void *op_in, const void *in_payload) { - struct fuse_getxattr_in *arg = (struct fuse_getxattr_in *) inarg; + const struct fuse_getxattr_in *arg = op_in; if (req->se->op.getxattr) - req->se->op.getxattr(req, nodeid, PARAM(arg), arg->size); + req->se->op.getxattr(req, nodeid, in_payload, arg->size); else fuse_reply_err(req, ENOSYS); } -static void do_listxattr(fuse_req_t req, fuse_ino_t nodeid, const void *inarg) +static void do_getxattr(fuse_req_t req, const fuse_ino_t nodeid, + const void *inarg) { - struct fuse_getxattr_in *arg = (struct fuse_getxattr_in *) inarg; + const struct fuse_getxattr_in *arg = inarg; + const void *payload = PARAM(arg); + + _do_getxattr(req, nodeid, arg, payload); +} + +static void _do_listxattr(fuse_req_t req, const fuse_ino_t nodeid, + const void *inarg, const void *in_payload) +{ + (void)in_payload; + const struct fuse_getxattr_in *arg = inarg; if (req->se->op.listxattr) req->se->op.listxattr(req, nodeid, arg->size); else fuse_reply_err(req, ENOSYS); } +static void do_listxattr(fuse_req_t req, const fuse_ino_t nodeid, + const void *inarg) +{ + _do_listxattr(req, nodeid, inarg, NULL); +} -static void do_removexattr(fuse_req_t req, fuse_ino_t nodeid, const void *inarg) +static void _do_removexattr(fuse_req_t req, const fuse_ino_t nodeid, + const void *inarg, const void *in_payload) { - char *name = (char *) inarg; + (void)inarg; + const char *name = in_payload; if (req->se->op.removexattr) req->se->op.removexattr(req, nodeid, name); else fuse_reply_err(req, ENOSYS); } +static void do_removexattr(fuse_req_t req, fuse_ino_t nodeid, const void *inarg) +{ + _do_removexattr(req, nodeid, NULL, inarg); +} -static void convert_fuse_file_lock(struct fuse_file_lock *fl, +static void convert_fuse_file_lock(const struct fuse_file_lock *fl, struct flock *flock) { memset(flock, 0, sizeof(struct flock)); @@ -1756,9 +2047,11 @@ static void convert_fuse_file_lock(struct fuse_file_lock *fl, flock->l_pid = fl->pid; } -static void do_getlk(fuse_req_t req, fuse_ino_t nodeid, const void *inarg) +static void _do_getlk(fuse_req_t req, const fuse_ino_t nodeid, + const void *op_in, const void *in_payload) { - struct fuse_lk_in *arg = (struct fuse_lk_in *) inarg; + (void)in_payload; + const struct fuse_lk_in *arg = op_in; struct fuse_file_info fi; struct flock flock; @@ -1772,11 +2065,15 @@ static void do_getlk(fuse_req_t req, fuse_ino_t nodeid, const void *inarg) else fuse_reply_err(req, ENOSYS); } +static void do_getlk(fuse_req_t req, fuse_ino_t nodeid, const void *inarg) +{ + _do_getlk(req, nodeid, inarg, NULL); +} -static void do_setlk_common(fuse_req_t req, fuse_ino_t nodeid, - const void *inarg, int sleep) +static void do_setlk_common(fuse_req_t req, const fuse_ino_t nodeid, + const void *op_in, int sleep) { - struct fuse_lk_in *arg = (struct fuse_lk_in *) inarg; + const struct fuse_lk_in *arg = op_in; struct fuse_file_info fi; struct flock flock; @@ -1814,14 +2111,27 @@ static void do_setlk_common(fuse_req_t req, fuse_ino_t nodeid, } } -static void do_setlk(fuse_req_t req, fuse_ino_t nodeid, const void *inarg) +static void _do_setlk(fuse_req_t req, const fuse_ino_t nodeid, + const void *op_in, const void *in_payload) { - do_setlk_common(req, nodeid, inarg, 0); + (void)in_payload; + do_setlk_common(req, nodeid, op_in, 0); } +static void do_setlk(fuse_req_t req, const fuse_ino_t nodeid, const void *inarg) +{ + _do_setlk(req, nodeid, inarg, NULL); +} + +static void _do_setlkw(fuse_req_t req, const fuse_ino_t nodeid, + const void *op_in, const void *in_payload) +{ + (void)in_payload; + do_setlk_common(req, nodeid, op_in, 1); +} static void do_setlkw(fuse_req_t req, fuse_ino_t nodeid, const void *inarg) { - do_setlk_common(req, nodeid, inarg, 1); + _do_setlkw(req, nodeid, inarg, NULL); } static int find_interrupted(struct fuse_session *se, struct fuse_req *req) @@ -1864,9 +2174,11 @@ static int find_interrupted(struct fuse_session *se, struct fuse_req *req) return 0; } -static void do_interrupt(fuse_req_t req, fuse_ino_t nodeid, const void *inarg) +static void _do_interrupt(fuse_req_t req, const fuse_ino_t nodeid, + const void *op_in, const void *in_payload) { - struct fuse_interrupt_in *arg = (struct fuse_interrupt_in *) inarg; + (void)in_payload; + const struct fuse_interrupt_in *arg = op_in; struct fuse_session *se = req->se; (void) nodeid; @@ -1885,6 +2197,10 @@ static void do_interrupt(fuse_req_t req, fuse_ino_t nodeid, const void *inarg) list_add_req(req, &se->interrupts); pthread_mutex_unlock(&se->lock); } +static void do_interrupt(fuse_req_t req, fuse_ino_t nodeid, const void *inarg) +{ + _do_interrupt(req, nodeid, inarg, NULL); +} static struct fuse_req *check_interrupt(struct fuse_session *se, struct fuse_req *req) @@ -1911,21 +2227,28 @@ static struct fuse_req *check_interrupt(struct fuse_session *se, return NULL; } -static void do_bmap(fuse_req_t req, fuse_ino_t nodeid, const void *inarg) +static void _do_bmap(fuse_req_t req, const fuse_ino_t nodeid, const void *op_in, + const void *in_payload) { - struct fuse_bmap_in *arg = (struct fuse_bmap_in *) inarg; + (void)in_payload; + const struct fuse_bmap_in *arg = op_in; if (req->se->op.bmap) req->se->op.bmap(req, nodeid, arg->blocksize, arg->block); else fuse_reply_err(req, ENOSYS); } +static void do_bmap(fuse_req_t req, fuse_ino_t nodeid, const void *inarg) +{ + _do_bmap(req, nodeid, inarg, NULL); +} -static void do_ioctl(fuse_req_t req, fuse_ino_t nodeid, const void *inarg) +static void _do_ioctl(fuse_req_t req, const fuse_ino_t nodeid, + const void *op_in, const void *in_payload) { - struct fuse_ioctl_in *arg = (struct fuse_ioctl_in *) inarg; + struct fuse_ioctl_in *arg = (struct fuse_ioctl_in *)op_in; unsigned int flags = arg->flags; - void *in_buf = arg->in_size ? PARAM(arg) : NULL; + const void *in_buf = in_payload; struct fuse_file_info fi; if (flags & FUSE_IOCTL_DIR && @@ -1949,15 +2272,24 @@ static void do_ioctl(fuse_req_t req, fuse_ino_t nodeid, const void *inarg) else fuse_reply_err(req, ENOSYS); } +static void do_ioctl(fuse_req_t req, fuse_ino_t nodeid, const void *inarg) +{ + const struct fuse_ioctl_in *arg = inarg; + void *in_buf = arg->in_size ? PARAM(arg) : NULL; + + _do_ioctl(req, nodeid, arg, in_buf); +} void fuse_pollhandle_destroy(struct fuse_pollhandle *ph) { free(ph); } -static void do_poll(fuse_req_t req, fuse_ino_t nodeid, const void *inarg) +static void _do_poll(fuse_req_t req, const fuse_ino_t nodeid, const void *op_in, + const void *in_payload) { - struct fuse_poll_in *arg = (struct fuse_poll_in *) inarg; + (void)in_payload; + struct fuse_poll_in *arg = (struct fuse_poll_in *)op_in; struct fuse_file_info fi; memset(&fi, 0, sizeof(fi)); @@ -1983,23 +2315,39 @@ static void do_poll(fuse_req_t req, fuse_ino_t nodeid, const void *inarg) } } -static void do_fallocate(fuse_req_t req, fuse_ino_t nodeid, const void *inarg) +static void do_poll(fuse_req_t req, const fuse_ino_t nodeid, const void *inarg) +{ + _do_poll(req, nodeid, inarg, NULL); +} + +static void _do_fallocate(fuse_req_t req, const fuse_ino_t nodeid, + const void *op_in, const void *in_payload) { - struct fuse_fallocate_in *arg = (struct fuse_fallocate_in *) inarg; + (void)in_payload; + const struct fuse_fallocate_in *arg = op_in; struct fuse_file_info fi; memset(&fi, 0, sizeof(fi)); fi.fh = arg->fh; if (req->se->op.fallocate) - req->se->op.fallocate(req, nodeid, arg->mode, arg->offset, arg->length, &fi); + req->se->op.fallocate(req, nodeid, arg->mode, arg->offset, + arg->length, &fi); else fuse_reply_err(req, ENOSYS); } -static void do_copy_file_range(fuse_req_t req, fuse_ino_t nodeid_in, const void *inarg) +static void do_fallocate(fuse_req_t req, const fuse_ino_t nodeid, + const void *inarg) +{ + _do_fallocate(req, nodeid, inarg, NULL); +} + +static void _do_copy_file_range(fuse_req_t req, const fuse_ino_t nodeid_in, + const void *op_in, const void *in_payload) { - struct fuse_copy_file_range_in *arg = (struct fuse_copy_file_range_in *) inarg; + (void)in_payload; + const struct fuse_copy_file_range_in *arg = op_in; struct fuse_file_info fi_in, fi_out; memset(&fi_in, 0, sizeof(fi_in)); @@ -2008,19 +2356,25 @@ static void do_copy_file_range(fuse_req_t req, fuse_ino_t nodeid_in, const void memset(&fi_out, 0, sizeof(fi_out)); fi_out.fh = arg->fh_out; - if (req->se->op.copy_file_range) - req->se->op.copy_file_range(req, nodeid_in, arg->off_in, - &fi_in, arg->nodeid_out, - arg->off_out, &fi_out, arg->len, - arg->flags); + req->se->op.copy_file_range(req, nodeid_in, arg->off_in, &fi_in, + arg->nodeid_out, arg->off_out, + &fi_out, arg->len, arg->flags); else fuse_reply_err(req, ENOSYS); } -static void do_lseek(fuse_req_t req, fuse_ino_t nodeid, const void *inarg) +static void do_copy_file_range(fuse_req_t req, const fuse_ino_t nodeid_in, + const void *inarg) +{ + _do_copy_file_range(req, nodeid_in, inarg, NULL); +} + +static void _do_lseek(fuse_req_t req, const fuse_ino_t nodeid, + const void *op_in, const void *in_payload) { - struct fuse_lseek_in *arg = (struct fuse_lseek_in *) inarg; + (void)in_payload; + const struct fuse_lseek_in *arg = op_in; struct fuse_file_info fi; memset(&fi, 0, sizeof(fi)); @@ -2032,6 +2386,11 @@ static void do_lseek(fuse_req_t req, fuse_ino_t nodeid, const void *inarg) fuse_reply_err(req, ENOSYS); } +static void do_lseek(fuse_req_t req, const fuse_ino_t nodeid, const void *inarg) +{ + _do_lseek(req, nodeid, inarg, NULL); +} + static bool want_flags_valid(uint64_t capable, uint64_t want) { uint64_t unknown_flags = want & (~capable); @@ -2046,10 +2405,12 @@ static bool want_flags_valid(uint64_t capable, uint64_t want) /* Prevent bogus data races (bogus since "init" is called before * multi-threading becomes relevant */ -static __attribute__((no_sanitize("thread"))) -void do_init(fuse_req_t req, fuse_ino_t nodeid, const void *inarg) +static __attribute__((no_sanitize("thread"))) void +_do_init(fuse_req_t req, const fuse_ino_t nodeid, const void *op_in, + const void *in_payload) { - struct fuse_init_in *arg = (struct fuse_init_in *) inarg; + (void)in_payload; + const struct fuse_init_in *arg = op_in; struct fuse_init_out outarg; struct fuse_session *se = req->se; size_t bufsize = se->bufsize; @@ -2366,12 +2727,20 @@ void do_init(fuse_req_t req, fuse_ino_t nodeid, const void *inarg) send_reply_ok(req, &outarg, outargsize); } -static void do_destroy(fuse_req_t req, fuse_ino_t nodeid, const void *inarg) +static __attribute__((no_sanitize("thread"))) void +do_init(fuse_req_t req, fuse_ino_t nodeid, const void *inarg) +{ + _do_init(req, nodeid, inarg, NULL); +} + +static void _do_destroy(fuse_req_t req, const fuse_ino_t nodeid, + const void *op_in, const void *in_payload) { struct fuse_session *se = req->se; (void) nodeid; - (void) inarg; + (void)op_in; + (void)in_payload; se->got_destroy = 1; se->got_init = 0; @@ -2381,6 +2750,11 @@ static void do_destroy(fuse_req_t req, fuse_ino_t nodeid, const void *inarg) send_reply_ok(req, NULL, 0); } +static void do_destroy(fuse_req_t req, fuse_ino_t nodeid, const void *inarg) +{ + _do_destroy(req, nodeid, inarg, NULL); +} + static void list_del_nreq(struct fuse_notify_req *nreq) { struct fuse_notify_req *prev = nreq->prev; @@ -2768,7 +3142,7 @@ static struct { [FUSE_SETLKW] = { do_setlkw, "SETLKW" }, [FUSE_ACCESS] = { do_access, "ACCESS" }, [FUSE_CREATE] = { do_create, "CREATE" }, - [FUSE_TMPFILE] = { do_tmpfile, "TMPFILE" }, + [FUSE_TMPFILE] = { do_tmpfile, "TMPFILE" }, [FUSE_INTERRUPT] = { do_interrupt, "INTERRUPT" }, [FUSE_BMAP] = { do_bmap, "BMAP" }, [FUSE_IOCTL] = { do_ioctl, "IOCTL" }, @@ -2784,6 +3158,60 @@ static struct { [CUSE_INIT] = { cuse_lowlevel_init, "CUSE_INIT" }, }; +static struct { + void (*func)(fuse_req_t req, const fuse_ino_t ino, const void *op_in, + const void *op_payload); + const char *name; +} fuse_ll_ops2[] __attribute__((unused)) = { + [FUSE_LOOKUP] = { _do_lookup, "LOOKUP" }, + [FUSE_FORGET] = { _do_forget, "FORGET" }, + [FUSE_GETATTR] = { _do_getattr, "GETATTR" }, + [FUSE_SETATTR] = { _do_setattr, "SETATTR" }, + [FUSE_READLINK] = { _do_readlink, "READLINK" }, + [FUSE_SYMLINK] = { _do_symlink, "SYMLINK" }, + [FUSE_MKNOD] = { _do_mknod, "MKNOD" }, + [FUSE_MKDIR] = { _do_mkdir, "MKDIR" }, + [FUSE_UNLINK] = { _do_unlink, "UNLINK" }, + [FUSE_RMDIR] = { _do_rmdir, "RMDIR" }, + [FUSE_RENAME] = { _do_rename, "RENAME" }, + [FUSE_LINK] = { _do_link, "LINK" }, + [FUSE_OPEN] = { _do_open, "OPEN" }, + [FUSE_READ] = { _do_read, "READ" }, + [FUSE_WRITE] = { _do_write, "WRITE" }, + [FUSE_STATFS] = { _do_statfs, "STATFS" }, + [FUSE_RELEASE] = { _do_release, "RELEASE" }, + [FUSE_FSYNC] = { _do_fsync, "FSYNC" }, + [FUSE_SETXATTR] = { _do_setxattr, "SETXATTR" }, + [FUSE_GETXATTR] = { _do_getxattr, "GETXATTR" }, + [FUSE_LISTXATTR] = { _do_listxattr, "LISTXATTR" }, + [FUSE_REMOVEXATTR] = { _do_removexattr, "REMOVEXATTR" }, + [FUSE_FLUSH] = { _do_flush, "FLUSH" }, + [FUSE_INIT] = { _do_init, "INIT" }, + [FUSE_OPENDIR] = { _do_opendir, "OPENDIR" }, + [FUSE_READDIR] = { _do_readdir, "READDIR" }, + [FUSE_RELEASEDIR] = { _do_releasedir, "RELEASEDIR" }, + [FUSE_FSYNCDIR] = { _do_fsyncdir, "FSYNCDIR" }, + [FUSE_GETLK] = { _do_getlk, "GETLK" }, + [FUSE_SETLK] = { _do_setlk, "SETLK" }, + [FUSE_SETLKW] = { _do_setlkw, "SETLKW" }, + [FUSE_ACCESS] = { _do_access, "ACCESS" }, + [FUSE_CREATE] = { _do_create, "CREATE" }, + [FUSE_TMPFILE] = { _do_tmpfile, "TMPFILE" }, + [FUSE_INTERRUPT] = { _do_interrupt, "INTERRUPT" }, + [FUSE_BMAP] = { _do_bmap, "BMAP" }, + [FUSE_IOCTL] = { _do_ioctl, "IOCTL" }, + [FUSE_POLL] = { _do_poll, "POLL" }, + [FUSE_FALLOCATE] = { _do_fallocate, "FALLOCATE" }, + [FUSE_DESTROY] = { _do_destroy, "DESTROY" }, + [FUSE_NOTIFY_REPLY] = { (void *)1, "NOTIFY_REPLY" }, + [FUSE_BATCH_FORGET] = { _do_batch_forget, "BATCH_FORGET" }, + [FUSE_READDIRPLUS] = { _do_readdirplus, "READDIRPLUS" }, + [FUSE_RENAME2] = { _do_rename2, "RENAME2" }, + [FUSE_COPY_FILE_RANGE] = { _do_copy_file_range, "COPY_FILE_RANGE" }, + [FUSE_LSEEK] = { _do_lseek, "LSEEK" }, + [CUSE_INIT] = { _cuse_lowlevel_init, "CUSE_INIT" }, +}; + /* * For ABI compatibility we cannot allow higher values than CUSE_INIT. * Without ABI compatibility we could use the size of the array. From c5a032b3410d7225ac0355355faa63565a209943 Mon Sep 17 00:00:00 2001 From: Bernd Schubert <bschubert@ddn.com> Date: Tue, 15 Apr 2025 22:03:09 +0200 Subject: [PATCH 099/241] 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> --- 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 02ceed202..bbf573a2b 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 e0aa52380..4a4c71eae 100644 --- a/lib/fuse_lowlevel.c +++ b/lib/fuse_lowlevel.c @@ -50,10 +50,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 ef533e2e3fedbd647726416fa7f36ca543ee13ea Mon Sep 17 00:00:00 2001 From: Bernd Schubert <bschubert@ddn.com> Date: Thu, 24 Apr 2025 13:53:59 +0200 Subject: [PATCH 100/241] Add a fuse_set_thread_name() helper Avoid splattering the code with ifdef Signed-off-by: Bernd Schubert <bschubert@ddn.com> --- lib/fuse.c | 4 +--- lib/fuse_loop_mt.c | 4 +--- lib/util.c | 19 +++++++++++++++++++ lib/util.h | 3 ++- 4 files changed, 23 insertions(+), 7 deletions(-) diff --git a/lib/fuse.c b/lib/fuse.c index bbf573a2b..9b9b98327 100644 --- a/lib/fuse.c +++ b/lib/fuse.c @@ -4912,9 +4912,7 @@ 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 + fuse_set_thread_name(pthread_self(), "fuse_prune_nodes"); while(1) { sleep_time = fuse_clean_cache(f); diff --git a/lib/fuse_loop_mt.c b/lib/fuse_loop_mt.c index c13b476f9..46e2d6eba 100644 --- a/lib/fuse_loop_mt.c +++ b/lib/fuse_loop_mt.c @@ -131,9 +131,7 @@ static void *fuse_do_work(void *data) struct fuse_mt *mt = w->mt; struct fuse_session *se = mt->se; -#ifdef HAVE_PTHREAD_SETNAME_NP - pthread_setname_np(pthread_self(), "fuse_worker"); -#endif + fuse_set_thread_name(pthread_self(), "fuse_worker"); while (!fuse_session_exited(se)) { int isforget = 0; diff --git a/lib/util.c b/lib/util.c index a529d383c..00f1e1adc 100644 --- a/lib/util.c +++ b/lib/util.c @@ -1,3 +1,12 @@ + +#include "fuse_config.h" + +#ifdef HAVE_PTHREAD_SETNAME_NP +#define _GNU_SOURCE +#include <pthread.h> +#endif + +#include <errno.h> #include <stdlib.h> #include <errno.h> @@ -25,3 +34,13 @@ int libfuse_strtol(const char *str, long *res) *res = val; return 0; } + +void fuse_set_thread_name(unsigned long tid, const char *name) +{ +#ifdef HAVE_PTHREAD_SETNAME_NP + pthread_setname_np(tid, name); +#else + (void)tid; + (void)name; +#endif +} diff --git a/lib/util.h b/lib/util.h index ed03ad40e..d1939cfef 100644 --- a/lib/util.h +++ b/lib/util.h @@ -9,6 +9,7 @@ #define unlikely(x) __builtin_expect(!!(x), 0) int libfuse_strtol(const char *str, long *res); +void fuse_set_thread_name(unsigned long tid, const char *name); /** * Return the low bits of a number @@ -36,4 +37,4 @@ static inline uint64_t fuse_higher_32_bits(uint64_t nr) ((type *)(__mptr - offsetof(type, member))); \ }) -#endif +#endif /* FUSE_UTIL_H_ */ From dde540e413eba6d22a4515659dd72262b8a01af4 Mon Sep 17 00:00:00 2001 From: Bernd Schubert <bschubert@ddn.com> Date: Mon, 24 Mar 2025 18:46:29 +0100 Subject: [PATCH 101/241] fuse: Add ring creation Signed-off-by: Bernd Schubert <bschubert@ddn.com> --- lib/fuse_i.h | 18 +- lib/fuse_loop.c | 6 +- lib/fuse_uring.c | 548 +++++++++++++++++++++++++++++++++++++++++++++ lib/fuse_uring_i.h | 18 ++ lib/meson.build | 8 + meson.build | 26 +++ meson_options.txt | 3 + test/ci-build.sh | 6 + 8 files changed, 630 insertions(+), 3 deletions(-) create mode 100644 lib/fuse_uring.c create mode 100644 lib/fuse_uring_i.h diff --git a/lib/fuse_i.h b/lib/fuse_i.h index 89a5c6f9b..b643e9041 100644 --- a/lib/fuse_i.h +++ b/lib/fuse_i.h @@ -6,6 +6,9 @@ See the file COPYING.LIB */ +#ifndef LIB_FUSE_I_H_ +#define LIB_FUSE_I_H_ + #include "fuse.h" #include "fuse_lowlevel.h" #include "util.h" @@ -24,6 +27,7 @@ }) struct mount_opts; +struct fuse_ring_pool; struct fuse_req { struct fuse_session *se; @@ -34,6 +38,7 @@ struct fuse_req { struct fuse_chan *ch; int interrupted; unsigned int ioctl_64bit : 1; + unsigned int is_uring : 1; union { struct { uint64_t unique; @@ -55,6 +60,11 @@ struct fuse_notify_req { struct fuse_notify_req *prev; }; +struct fuse_session_uring { + unsigned int q_depth; + struct fuse_ring_pool *pool; +}; + struct fuse_session { char *mountpoint; int fd; @@ -79,7 +89,8 @@ struct fuse_session { _Atomic size_t bufsize; int error; - /* This is useful if any kind of ABI incompatibility is found at + /* + * 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; @@ -91,6 +102,9 @@ struct fuse_session { /* true if reading requests from /dev/fuse are handled internally */ bool buf_reallocable; + + /* io_uring */ + struct fuse_session_uring uring; }; struct fuse_chan { @@ -269,3 +283,5 @@ static inline int convert_to_conn_want_ext(struct fuse_conn_info *conn, return 0; } + +#endif /* LIB_FUSE_I_H_*/ diff --git a/lib/fuse_loop.c b/lib/fuse_loop.c index 410f43fbb..a27111ca8 100644 --- a/lib/fuse_loop.c +++ b/lib/fuse_loop.c @@ -11,7 +11,7 @@ #include "fuse_config.h" #include "fuse_lowlevel.h" #include "fuse_i.h" - +#include "fuse_uring_i.h" #include <stdio.h> #include <stdlib.h> #include <errno.h> @@ -41,6 +41,8 @@ int fuse_session_loop(struct fuse_session *se) res = 0; if(se->error != 0) res = se->error; - fuse_session_reset(se); + + if (se->uring.pool) + fuse_uring_stop(se); return res; } diff --git a/lib/fuse_uring.c b/lib/fuse_uring.c new file mode 100644 index 000000000..ce567169a --- /dev/null +++ b/lib/fuse_uring.c @@ -0,0 +1,548 @@ +/* + * FUSE: Filesystem in Userspace + * Copyright (C) 2025 Bernd Schubert <bschubert@ddn.com> + * + * Implementation of (most of) the low-level FUSE API. The session loop + * functions are implemented in separate files. + * + * This program can be distributed under the terms of the GNU LGPLv2. + * See the file COPYING.LIB + */ + +#define _GNU_SOURCE + +#include "fuse_i.h" +#include "fuse_kernel.h" +#include "fuse_uring_i.h" + +#include <stdlib.h> +#include <liburing.h> +#include <sys/sysinfo.h> +#include <stdint.h> +#include <stdbool.h> +#include <string.h> +#include <unistd.h> +#include <numa.h> +#include <pthread.h> +#include <stdio.h> +#include <linux/sched.h> +#include <poll.h> +#include <sys/eventfd.h> + +/* Size of command data area in SQE when IORING_SETUP_SQE128 is used */ +#define FUSE_URING_MAX_SQE128_CMD_DATA 80 + +struct fuse_ring_ent { + struct fuse_ring_queue *ring_queue; /* back pointer */ + struct fuse_req req; + + struct fuse_uring_req_header *req_header; + void *op_payload; + size_t req_payload_sz; + + /* commit id of a fuse request */ + uint64_t req_commit_id; + + /* header and payload */ + struct iovec iov[2]; +}; + +struct fuse_ring_queue { + /* back pointer */ + struct fuse_ring_pool *ring_pool; + int qid; + int numa_node; + pthread_t tid; + int eventfd; + size_t req_header_sz; + struct io_uring ring; + + /* size depends on queue depth */ + struct fuse_ring_ent ent[]; +}; + +/** + * Main fuse_ring structure, holds all fuse-ring data + */ +struct fuse_ring_pool { + struct fuse_session *se; + + /* number of queues */ + size_t nr_queues; + + /* number of per queue entries */ + size_t queue_depth; + + /* max payload size for fuse requests*/ + size_t max_req_payload_sz; + + /* size of a single queue */ + size_t queue_mem_size; + + /* pointer to the first queue */ + struct fuse_ring_queue *queues; +}; + +static size_t +fuse_ring_queue_size(const size_t q_depth) +{ + const size_t req_size = sizeof(struct fuse_ring_ent) * q_depth; + + return sizeof(struct fuse_ring_queue) + req_size; +} + +static struct fuse_ring_queue * +fuse_uring_get_queue(struct fuse_ring_pool *fuse_ring, int qid) +{ + void *ptr = + ((char *)fuse_ring->queues) + (qid * fuse_ring->queue_mem_size); + + return ptr; +} + +/** + * return a pointer to the 80B area + */ +static void *fuse_uring_get_sqe_cmd(struct io_uring_sqe *sqe) +{ + return (void *)&sqe->cmd[0]; +} + +static void fuse_uring_sqe_set_req_data(struct fuse_uring_cmd_req *req, + const unsigned int qid, + const unsigned int commit_id) +{ + req->qid = qid; + req->commit_id = commit_id; + req->flags = 0; +} + +static void +fuse_uring_sqe_prepare(struct io_uring_sqe *sqe, struct fuse_ring_ent *req, + __u32 cmd_op) +{ + /* These fields should be written once, never change */ + sqe->opcode = IORING_OP_URING_CMD; + + /* + * IOSQE_FIXED_FILE: fd is the index to the fd *array* + * given to io_uring_register_files() + */ + sqe->flags = IOSQE_FIXED_FILE; + sqe->fd = 0; + + sqe->rw_flags = 0; + sqe->ioprio = 0; + sqe->off = 0; + + io_uring_sqe_set_data(sqe, req); + + sqe->cmd_op = cmd_op; + sqe->__pad1 = 0; +} + +static int fuse_queue_setup_io_uring(struct io_uring *ring, size_t qid, + size_t depth, int fd, int evfd) +{ + int rc; + struct io_uring_params params = {0}; + int files[2] = { fd, evfd }; + + depth += 1; /* for the eventfd poll SQE */ + + params.flags = IORING_SETUP_SQE128; + + /* Avoid cq overflow */ + params.flags |= IORING_SETUP_CQSIZE; + params.cq_entries = depth * 2; + + /* These flags should help to increase performance, but actually + * make it a bit slower - reason should get investigated. + */ + if (0) { + /* Has the main slow down effect */ + params.flags |= IORING_SETUP_SINGLE_ISSUER; + + // params.flags |= IORING_SETUP_DEFER_TASKRUN; + params.flags |= IORING_SETUP_TASKRUN_FLAG; + + /* Second main effect to make it slower */ + params.flags |= IORING_SETUP_COOP_TASKRUN; + } + + rc = io_uring_queue_init_params(depth, ring, ¶ms); + if (rc != 0) { + fuse_log(FUSE_LOG_ERR, "Failed to setup qid %zu: %d (%s)\n", + qid, rc, strerror(-rc)); + return rc; + } + + rc = io_uring_register_files(ring, files, 1); + if (rc != 0) { + rc = -errno; + fuse_log(FUSE_LOG_ERR, + "Failed to register files for ring idx %zu: %s", + qid, strerror(errno)); + return rc; + } + + return 0; +} + +static void fuse_session_destruct_uring(struct fuse_ring_pool *fuse_ring) +{ + for (size_t qid = 0; qid < fuse_ring->nr_queues; qid++) { + struct fuse_ring_queue *queue = + fuse_uring_get_queue(fuse_ring, qid); + + if (queue->tid != 0) { + int value = 1; + + write(queue->eventfd, &value, sizeof(value)); + pthread_join(queue->tid, NULL); + queue->tid = 0; + } + + if (queue->eventfd >= 0) { + close(queue->eventfd); + queue->eventfd = -1; + } + + if (queue->ring.ring_fd != -1) + io_uring_queue_exit(&queue->ring); + + for (size_t idx = 0; idx < fuse_ring->queue_depth; idx++) { + struct fuse_ring_ent *ent = &queue->ent[idx]; + + numa_free(ent->op_payload, ent->req_payload_sz); + numa_free(ent->req_header, queue->req_header_sz); + } + } + + free(fuse_ring->queues); + free(fuse_ring); +} + +static int fuse_uring_prepare_fetch_sqes(struct fuse_ring_queue *queue) +{ + struct fuse_ring_pool *ring_pool = queue->ring_pool; + unsigned int sq_ready; + struct io_uring_sqe *sqe; + + for (size_t idx = 0; idx < ring_pool->queue_depth; idx++) { + struct fuse_ring_ent *ent = &queue->ent[idx]; + + sqe = io_uring_get_sqe(&queue->ring); + if (sqe == NULL) { + /* All SQEs are idle here - no good reason this + * could fail + */ + + fuse_log(FUSE_LOG_ERR, "Failed to get all ring SQEs"); + return -EIO; + } + + fuse_uring_sqe_prepare(sqe, ent, FUSE_IO_URING_CMD_REGISTER); + + /* only needed for fetch */ + ent->iov[0].iov_base = ent->req_header; + ent->iov[0].iov_len = queue->req_header_sz; + + ent->iov[1].iov_base = ent->op_payload; + ent->iov[1].iov_len = ent->req_payload_sz; + + sqe->addr = (uint64_t)(ent->iov); + sqe->len = 2; + + /* this is a fetch, kernel does not read commit id */ + fuse_uring_sqe_set_req_data(fuse_uring_get_sqe_cmd(sqe), + queue->qid, 0); + } + + sq_ready = io_uring_sq_ready(&queue->ring); + if (sq_ready != ring_pool->queue_depth) { + fuse_log(FUSE_LOG_ERR, + "SQE ready mismatch, expected %d got %d\n", + ring_pool->queue_depth, sq_ready); + return -EINVAL; + } + + // Add the poll SQE for the eventfd to wake up on teardown + sqe = io_uring_get_sqe(&queue->ring); + if (sqe == NULL) { + fuse_log(FUSE_LOG_ERR, "Failed to get eventfd SQE"); + return -EIO; + } + + io_uring_prep_poll_add(sqe, queue->eventfd, POLLIN); + io_uring_sqe_set_data(sqe, (void *)(uintptr_t)queue->eventfd); + + io_uring_submit(&queue->ring); + + return 0; +} + +static struct fuse_ring_pool *fuse_create_ring(struct fuse_session *se) +{ + struct fuse_ring_pool *fuse_ring = NULL; + const size_t nr_queues = get_nprocs_conf(); + size_t payload_sz = se->bufsize - FUSE_BUFFER_HEADER_SIZE; + size_t queue_sz; + + if (se->debug) + fuse_log(FUSE_LOG_DEBUG, "starting io-uring q-depth=%d\n", + se->uring.q_depth); + + fuse_ring = calloc(1, sizeof(*fuse_ring)); + if (fuse_ring == NULL) { + fuse_log(FUSE_LOG_ERR, "Allocating the ring failed\n"); + goto err; + } + + queue_sz = fuse_ring_queue_size(se->uring.q_depth); + fuse_ring->queues = calloc(1, queue_sz * nr_queues); + if (fuse_ring->queues == NULL) { + fuse_log(FUSE_LOG_ERR, "Allocating the queues failed\n"); + goto err; + } + + fuse_ring->se = se; + fuse_ring->nr_queues = nr_queues; + fuse_ring->queue_depth = se->uring.q_depth; + fuse_ring->max_req_payload_sz = payload_sz; + fuse_ring->queue_mem_size = queue_sz; + + /* + * very basic queue initialization, that cannot fail and will + * allow easy cleanup if something (like mmap) fails in the middle + * below + */ + for (size_t qid = 0; qid < nr_queues; qid++) { + struct fuse_ring_queue *queue = + fuse_uring_get_queue(fuse_ring, qid); + + queue->ring.ring_fd = -1; + queue->numa_node = numa_node_of_cpu(qid); + queue->qid = qid; + queue->ring_pool = fuse_ring; + } + + return fuse_ring; + +err: + if (fuse_ring) + fuse_session_destruct_uring(fuse_ring); + + return NULL; +} + +/* stub function */ +static int fuse_uring_queue_handle_cqes(struct fuse_ring_queue *queue) +{ + (void)queue; + + return 0; +} + +/** + * In per-core-queue configuration we have thread per core - the thread + * to that core + */ +static void fuse_uring_set_thread_core(int qid) +{ + cpu_set_t mask; + int rc; + + CPU_ZERO(&mask); + CPU_SET(qid, &mask); + rc = sched_setaffinity(0, sizeof(cpu_set_t), &mask); + if (rc != 0) + fuse_log(FUSE_LOG_ERR, "Failed to bind qid=%d to its core: %s\n", + qid, strerror(errno)); + + if (0) { + const int policy = SCHED_IDLE; + const struct sched_param param = { + .sched_priority = sched_get_priority_min(policy), + }; + + /* Set the lowest possible priority, so that the application + * submitting requests is not moved away from the current core. + */ + rc = sched_setscheduler(0, policy, ¶m); + if (rc != 0) + fuse_log(FUSE_LOG_ERR, "Failed to set scheduler: %s\n", + strerror(errno)); + } +} + +/* + * @return negative error code or io-uring file descriptor + */ +static int fuse_uring_init_queue(struct fuse_ring_queue *queue) +{ + struct fuse_ring_pool *ring = queue->ring_pool; + struct fuse_session *se = ring->se; + int res; + size_t page_sz = sysconf(_SC_PAGESIZE); + + queue->eventfd = eventfd(0, EFD_CLOEXEC); + if (queue->eventfd < 0) { + res = -errno; + fuse_log(FUSE_LOG_ERR, + "Failed to create eventfd for qid %d: %s\n", + queue->qid, strerror(errno)); + return res; + } + + res = fuse_queue_setup_io_uring(&queue->ring, queue->qid, + ring->queue_depth, se->fd, + queue->eventfd); + if (res != 0) { + fuse_log(FUSE_LOG_ERR, "qid=%d io_uring init failed\n", + queue->qid); + goto err; + } + + queue->req_header_sz = ROUND_UP(sizeof(struct fuse_ring_ent), + page_sz); + + for (size_t idx = 0; idx < ring->queue_depth; idx++) { + struct fuse_ring_ent *ring_ent = &queue->ent[idx]; + struct fuse_req *req = &ring_ent->req; + + ring_ent->ring_queue = queue; + + /* + * Also allocate the header to have it page aligned, which + * is a requirement for page pinning + */ + ring_ent->req_header = + numa_alloc_local(queue->req_header_sz); + ring_ent->req_payload_sz = ring->max_req_payload_sz; + + ring_ent->op_payload = + numa_alloc_local(ring_ent->req_payload_sz); + + req->se = se; + pthread_mutex_init(&req->lock, NULL); + req->is_uring = true; + req->ref_cnt = 1; + } + + res = fuse_uring_prepare_fetch_sqes(queue); + if (res != 0) { + fuse_log( + FUSE_LOG_ERR, + "Grave fuse-uring error on preparing SQEs, aborting\n"); + se->error = -EIO; + fuse_session_exit(se); + } + + return queue->ring.ring_fd; + +err: + close(queue->eventfd); + return res; +} + +static void *fuse_uring_thread(void *arg) +{ + struct fuse_ring_queue *queue = arg; + struct fuse_ring_pool *ring_pool = queue->ring_pool; + struct fuse_session *se = ring_pool->se; + int err; + char thread_name[16] = { 0 }; + + snprintf(thread_name, 16, "fuse-ring-%d", queue->qid); + thread_name[15] = '\0'; + fuse_set_thread_name(pthread_self(), thread_name); + + fuse_uring_set_thread_core(queue->qid); + + err = fuse_uring_init_queue(queue); + if (err < 0) { + fuse_log(FUSE_LOG_ERR, "qid=%d queue setup failed\n", + queue->qid); + goto err; + } + + /* Not using fuse_session_exited(se), as that cannot be inlined */ + while (!atomic_load_explicit(&se->mt_exited, memory_order_relaxed)) { + io_uring_submit_and_wait(&queue->ring, 1); + + err = fuse_uring_queue_handle_cqes(queue); + if (err < 0) { + /* + * fuse-over-io-uring is not supported, operation can + * continue over /dev/fuse + */ + if (err == -EOPNOTSUPP) + goto ret; + goto err; + } + } + + return NULL; + +err: + fuse_session_exit(se); +ret: + return NULL; +} + +static int fuse_uring_start_ring_threads(struct fuse_ring_pool *ring) +{ + int rc = 0; + + for (size_t qid = 0; qid < ring->nr_queues; qid++) { + struct fuse_ring_queue *queue = fuse_uring_get_queue(ring, qid); + + rc = pthread_create(&queue->tid, NULL, fuse_uring_thread, queue); + if (rc != 0) + break; + } + + return rc; +} + +static int fuse_uring_sanity_check(void) +{ + _Static_assert(sizeof(struct fuse_uring_cmd_req) <= + FUSE_URING_MAX_SQE128_CMD_DATA, + "SQE128_CMD_DATA has 80B cmd data"); + + return 0; +} + +int fuse_uring_start(struct fuse_session *se) +{ + int err = 0; + struct fuse_ring_pool *fuse_ring; + + fuse_uring_sanity_check(); + + fuse_ring = fuse_create_ring(se); + if (fuse_ring == NULL) { + err = -EADDRNOTAVAIL; + goto out; + } + + se->uring.pool = fuse_ring; + + err = fuse_uring_start_ring_threads(fuse_ring); +out: + return err; +} + +int fuse_uring_stop(struct fuse_session *se) +{ + struct fuse_ring_pool *ring = se->uring.pool; + + if (ring == NULL) + return 0; + + fuse_session_destruct_uring(ring); + + return 0; +} diff --git a/lib/fuse_uring_i.h b/lib/fuse_uring_i.h new file mode 100644 index 000000000..fefb8a0a8 --- /dev/null +++ b/lib/fuse_uring_i.h @@ -0,0 +1,18 @@ +/* + * FUSE: Filesystem in Userspace + * Copyright (C) 2025 Bernd Schubert <bschubert@ddn.com> + * This program can be distributed under the terms of the GNU LGPLv2. + * See the file COPYING.LIB + */ + +#ifndef FUSE_URING_I_H_ +#define FUSE_URING_I_H_ + +#include "fuse_lowlevel.h" + +struct fuse_in_header; + +int fuse_uring_start(struct fuse_session *se); +int fuse_uring_stop(struct fuse_session *se); + +#endif // FUSE_URING_I_H_ diff --git a/lib/meson.build b/lib/meson.build index 6a52d06a9..fcd95741c 100644 --- a/lib/meson.build +++ b/lib/meson.build @@ -19,6 +19,14 @@ if private_cfg.get('HAVE_ICONV') endif endif +if private_cfg.get('HAVE_URING', false) + libfuse_sources += [ 'fuse_uring.c' ] + deps += [ dependency('liburing') ] + deps += [ dependency('numa') ] +endif + + + libdl = cc.find_library('dl', required: false) if libdl.found() deps += [ libdl ] diff --git a/meson.build b/meson.build index 3615a5a26..b6631098d 100644 --- a/meson.build +++ b/meson.build @@ -132,6 +132,32 @@ private_cfg.set('HAVE_STRUCT_STAT_ST_ATIMESPEC', private_cfg.set('USDT_ENABLED', get_option('enable-usdt')) +# Check for liburing with SQE128 support +code = ''' +#include <liburing.h> +#include <stdio.h> +int main(void) { + struct io_uring ring; + int ret = io_uring_queue_init(1, &ring, 0); +#ifndef IORING_SETUP_SQE128 +#error "No SQE128 support" +#endif + return ret; +}''' + +liburing = get_option('enable-io-uring') ? \ + dependency('liburing', required: false) :\ + dependency('', required: false) +libnuma = dependency('numa', required: false) + +if liburing.found() and libnuma.found() + if cc.links(code, + name: 'liburing linking and SQE128 support', + dependencies: [liburing]) + private_cfg.set('HAVE_URING', true) + endif +endif + # # Compiler configuration # diff --git a/meson_options.txt b/meson_options.txt index 957c5fc36..c1f8fe694 100644 --- a/meson_options.txt +++ b/meson_options.txt @@ -24,3 +24,6 @@ option('disable-libc-symbol-version', type : 'boolean', value : false, option('enable-usdt', type : 'boolean', value : false, description: 'Enable user statically defined tracepoints for extra observability') + +option('enable-io-uring', type: 'boolean', value: true, + description: 'Enable fuse-over-io-uring support') diff --git a/test/ci-build.sh b/test/ci-build.sh index 40bb79e8d..86230bdfd 100755 --- a/test/ci-build.sh +++ b/test/ci-build.sh @@ -146,6 +146,12 @@ export CC=clang export CXX=clang++ sanitized_build "-Ddisable-libc-symbol-version=true" +# Sanitized build without fuse-io-uring +export CC=clang +export CXX=clang++ +sanitized_build "-Denable-io-uring=false" + +# Build without any sanitizer non_sanitized_build # Documentation. From da355f79362f5724f75147dfd51d58d6ee3e552d Mon Sep 17 00:00:00 2001 From: Bernd Schubert <bschubert@ddn.com> Date: Mon, 24 Mar 2025 23:18:10 +0100 Subject: [PATCH 102/241] Add support for ring creation in fuse_lowlevel.c Signed-off-by: Bernd Schubert <bschubert@ddn.com> --- lib/fuse_i.h | 1 + lib/fuse_loop_mt.c | 5 +++++ lib/fuse_lowlevel.c | 31 +++++++++++++++++++++++++++++-- lib/fuse_uring_i.h | 27 +++++++++++++++++++++++++++ lib/util.h | 2 +- 5 files changed, 63 insertions(+), 3 deletions(-) diff --git a/lib/fuse_i.h b/lib/fuse_i.h index b643e9041..23e58ef13 100644 --- a/lib/fuse_i.h +++ b/lib/fuse_i.h @@ -61,6 +61,7 @@ struct fuse_notify_req { }; struct fuse_session_uring { + bool enable; unsigned int q_depth; struct fuse_ring_pool *pool; }; diff --git a/lib/fuse_loop_mt.c b/lib/fuse_loop_mt.c index 46e2d6eba..046256c20 100644 --- a/lib/fuse_loop_mt.c +++ b/lib/fuse_loop_mt.c @@ -15,6 +15,7 @@ #include "fuse_misc.h" #include "fuse_kernel.h" #include "fuse_i.h" +#include "fuse_uring_i.h" #include "util.h" #include <stdio.h> @@ -407,12 +408,16 @@ int err; fuse_join_worker(&mt, mt.main.next); err = mt.error; + + if (se->uring.pool) + fuse_uring_stop(se); } pthread_mutex_destroy(&se->mt_lock); if(se->error != 0) err = se->error; + if (created_config) { fuse_loop_cfg_destroy(config); config = NULL; diff --git a/lib/fuse_lowlevel.c b/lib/fuse_lowlevel.c index 4a4c71eae..433594fba 100644 --- a/lib/fuse_lowlevel.c +++ b/lib/fuse_lowlevel.c @@ -18,6 +18,7 @@ #include "fuse_misc.h" #include "mount_util.h" #include "util.h" +#include "fuse_uring_i.h" #include <pthread.h> #include <stdatomic.h> @@ -34,6 +35,7 @@ #include <assert.h> #include <sys/file.h> #include <sys/ioctl.h> +#include <stdalign.h> #ifdef USDT_ENABLED #include "usdt.h" @@ -46,7 +48,6 @@ #define F_SETPIPE_SZ (F_LINUX_SPECIFIC_BASE + 7) #endif - #define PARAM(inarg) (((char *)(inarg)) + sizeof(*(inarg))) #define OFFSET_MAX 0x7fffffffffffffffLL @@ -169,6 +170,10 @@ static void list_add_req(struct fuse_req *req, struct fuse_req *next) static void destroy_req(fuse_req_t req) { + if (req->is_uring) { + fuse_log(FUSE_LOG_ERR, "Refusing to destruct uring req\n"); + return; + } assert(req->ch == NULL); pthread_mutex_destroy(&req->lock); free(req); @@ -179,7 +184,11 @@ void fuse_free_req(fuse_req_t req) int ctr; struct fuse_session *se = req->se; - if (se->conn.no_interrupt) { + /* XXX: for now no support for interrupts with io-uring + * It actually might work alreasdy, though. But then would add + * a lock across ring queues. + */ + if (se->conn.no_interrupt || req->is_uring) { ctr = --req->ref_cnt; fuse_chan_put(req->ch); req->ch = NULL; @@ -209,6 +218,7 @@ static struct fuse_req *fuse_ll_alloc_req(struct fuse_session *se) req->ref_cnt = 1; list_init_req(req); pthread_mutex_init(&req->lock, NULL); + req->is_uring = false; } return req; @@ -2415,6 +2425,8 @@ _do_init(fuse_req_t req, const fuse_ino_t nodeid, const void *op_in, uint64_t outargflags = 0; bool buf_reallocable = se->buf_reallocable; (void) nodeid; + bool enable_io_uring = false; + if (se->debug) { fuse_log(FUSE_LOG_DEBUG, "INIT: %u.%u\n", arg->major, arg->minor); if (arg->major == 7 && arg->minor >= 6) { @@ -2673,6 +2685,10 @@ _do_init(fuse_req_t req, const fuse_ino_t nodeid, const void *op_in, } if (se->conn.want_ext & FUSE_CAP_NO_EXPORT_SUPPORT) outargflags |= FUSE_NO_EXPORT_SUPPORT; + if (se->uring.enable && se->conn.want_ext & FUSE_CAP_OVER_IO_URING) { + outargflags |= FUSE_OVER_IO_URING; + enable_io_uring = true; + } if (inargflags & FUSE_INIT_EXT) { outargflags |= FUSE_INIT_EXT; @@ -2721,6 +2737,17 @@ _do_init(fuse_req_t req, const fuse_ino_t nodeid, const void *op_in, outargsize = FUSE_COMPAT_22_INIT_OUT_SIZE; send_reply_ok(req, &outarg, outargsize); + + /* XXX: Split the start, and send SQEs only after send_reply_ok() */ + if (enable_io_uring) { + int ring_rc = fuse_uring_start(se); + + if (ring_rc != 0) { + fuse_log(FUSE_LOG_ERR, "fuse: failed to start io-uring: %s\n", + strerror(ring_rc)); + fuse_session_exit(se); + } + } } static __attribute__((no_sanitize("thread"))) void diff --git a/lib/fuse_uring_i.h b/lib/fuse_uring_i.h index fefb8a0a8..e9f298908 100644 --- a/lib/fuse_uring_i.h +++ b/lib/fuse_uring_i.h @@ -8,11 +8,38 @@ #ifndef FUSE_URING_I_H_ #define FUSE_URING_I_H_ +#include "fuse_config.h" #include "fuse_lowlevel.h" +#include "fuse_kernel.h" + +#ifndef HAVE_URING +#include "util.h" +#endif + +void fuse_session_process_uring_cqe(struct fuse_session *se, + struct fuse_req *req, + struct fuse_in_header *in, void *in_header, + void *in_payload, size_t payload_len); + +#ifdef HAVE_URING struct fuse_in_header; int fuse_uring_start(struct fuse_session *se); int fuse_uring_stop(struct fuse_session *se); +#else // HAVE_URING + +static inline int fuse_uring_start(struct fuse_session *se FUSE_VAR_UNUSED) +{ + return -ENOTSUP; +} + +static inline int fuse_uring_stop(struct fuse_session *se FUSE_VAR_UNUSED) +{ + return -ENOTSUP; +} + +#endif // HAVE_URING + #endif // FUSE_URING_I_H_ diff --git a/lib/util.h b/lib/util.h index d1939cfef..96b59d3af 100644 --- a/lib/util.h +++ b/lib/util.h @@ -28,7 +28,7 @@ static inline uint64_t fuse_higher_32_bits(uint64_t nr) } #ifndef FUSE_VAR_UNUSED -#define FUSE_VAR_UNUSED(var) (__attribute__((unused)) var) +#define FUSE_VAR_UNUSED __attribute__((__unused__)) #endif #define container_of(ptr, type, member) \ From 04ecefb9869791934fd496139d82cec1587d7751 Mon Sep 17 00:00:00 2001 From: Bernd Schubert <bschubert@ddn.com> Date: Mon, 24 Mar 2025 23:40:16 +0100 Subject: [PATCH 103/241] fuse_lowlevel: Split fuse_send_msg The actual function to write to /dev/fuse is offloaded into _fuse_send_msg() Signed-off-by: Bernd Schubert <bschubert@ddn.com> --- lib/fuse_lowlevel.c | 51 ++++++++++++++++++++++++++++----------------- 1 file changed, 32 insertions(+), 19 deletions(-) diff --git a/lib/fuse_lowlevel.c b/lib/fuse_lowlevel.c index 433594fba..b4458c4f6 100644 --- a/lib/fuse_lowlevel.c +++ b/lib/fuse_lowlevel.c @@ -224,11 +224,41 @@ static struct fuse_req *fuse_ll_alloc_req(struct fuse_session *se) return req; } +/* + * Send data to fuse-kernel using an fd of the fuse device. + */ +static int _fuse_send_msg(struct fuse_session *se, struct fuse_chan *ch, + struct iovec *iov, int count) +{ + ssize_t res; + int err; + + if (se->io != NULL) { + /* se->io->writev is never NULL if se->io is not NULL as + * specified by fuse_session_custom_io() + */ + res = se->io->writev(ch ? ch->fd : se->fd, iov, count, + se->userdata); + } else { + res = writev(ch ? ch->fd : se->fd, iov, count); + } + if (res == -1) { + /* ENOENT means the operation was interrupted */ + err = errno; + if (!fuse_session_exited(se) && err != ENOENT) + perror("fuse: writing device"); + return -err; + } + + return 0; +} + /* Send data. If *ch* is NULL, send via session master fd */ static int fuse_send_msg(struct fuse_session *se, struct fuse_chan *ch, struct iovec *iov, int count) { struct fuse_out_header *out = iov[0].iov_base; + int err; assert(se != NULL); out->len = iov_length(iov, count); @@ -248,26 +278,9 @@ static int fuse_send_msg(struct fuse_session *se, struct fuse_chan *ch, } } - ssize_t res; - if (se->io != NULL) - /* se->io->writev is never NULL if se->io is not NULL as - specified by fuse_session_custom_io()*/ - res = se->io->writev(ch ? ch->fd : se->fd, iov, count, - se->userdata); - else - res = writev(ch ? ch->fd : se->fd, iov, count); - - int err = errno; + err = _fuse_send_msg(se, ch, iov, count); trace_request_reply(out->unique, out->len, out->error, err); - - if (res == -1) { - /* ENOENT means the operation was interrupted */ - if (!fuse_session_exited(se) && err != ENOENT) - perror("fuse: writing device"); - return -err; - } - - return 0; + return err; } From 9367748bef846d8430f7e9ee0ea31fe806c5b9f2 Mon Sep 17 00:00:00 2001 From: Bernd Schubert <bschubert@ddn.com> Date: Tue, 25 Mar 2025 18:55:30 +0100 Subject: [PATCH 104/241] fuse_ll_ops: Make fuse_ino_t const It will never be modified, should be const. Signed-off-by: Bernd Schubert <bschubert@ddn.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 b4458c4f6..fdbb60cda 100644 --- a/lib/fuse_lowlevel.c +++ b/lib/fuse_lowlevel.c @@ -3142,7 +3142,7 @@ int fuse_req_interrupted(fuse_req_t req) } static struct { - void (*func)(fuse_req_t, fuse_ino_t, const void *); + void (*func)(fuse_req_t req, const fuse_ino_t node, const void *arg); const char *name; } fuse_ll_ops[] = { [FUSE_LOOKUP] = { do_lookup, "LOOKUP" }, From 913c3779886b0c9c4aa610eabf7c21eb8064f382 Mon Sep 17 00:00:00 2001 From: Bernd Schubert <bschubert@ddn.com> Date: Wed, 26 Mar 2025 00:57:08 +0100 Subject: [PATCH 105/241] Add a basic io-uring cqe handler Not function yet, just preparation. Signed-off-by: Bernd Schubert <bschubert@ddn.com> --- lib/fuse_uring.c | 95 ++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 92 insertions(+), 3 deletions(-) diff --git a/lib/fuse_uring.c b/lib/fuse_uring.c index ce567169a..47efa9517 100644 --- a/lib/fuse_uring.c +++ b/lib/fuse_uring.c @@ -336,12 +336,101 @@ static struct fuse_ring_pool *fuse_create_ring(struct fuse_session *se) return NULL; } -/* stub function */ +/* stub function*/ +static void fuse_session_process_uring_cqe(struct fuse_session *se, + struct fuse_req *req, + struct fuse_in_header *in, + void *op_in, void *op_payload, + size_t op_payload_sz) +{ + (void)se; + (void)req; + (void)in; + (void)op_in; + (void)op_payload; + (void)op_payload_sz; +} + +static void fuse_uring_handle_cqe(struct fuse_ring_queue *queue, + struct io_uring_cqe *cqe) +{ + struct fuse_ring_ent *ent = io_uring_cqe_get_data(cqe); + + if (!ent) { + fuse_log(FUSE_LOG_ERR, + "cqe=%p io_uring_cqe_get_data returned NULL\n", cqe); + return; + } + + struct fuse_req *req = &ent->req; + struct fuse_ring_pool *fuse_ring = queue->ring_pool; + struct fuse_uring_req_header *rrh = ent->req_header; + + struct fuse_in_header *in = (struct fuse_in_header *)&rrh->in_out; + struct fuse_uring_ent_in_out *ent_in_out = &rrh->ring_ent_in_out; + + ent->req_commit_id = ent_in_out->commit_id; + if (unlikely(ent->req_commit_id == 0)) { + /* + * If this happens kernel will not find the response - it will + * be stuck forever - better to abort immediately. + */ + fuse_log(FUSE_LOG_ERR, "Received invalid commit_id=0\n"); + abort(); + } + + req->is_uring = true; + req->ref_cnt++; + req->ch = NULL; /* not needed for uring */ + + fuse_session_process_uring_cqe(fuse_ring->se, req, in, &rrh->op_in, + ent->op_payload, ent_in_out->payload_sz); +} + static int fuse_uring_queue_handle_cqes(struct fuse_ring_queue *queue) { - (void)queue; + struct fuse_ring_pool *ring_pool = queue->ring_pool; + struct fuse_session *se = ring_pool->se; + size_t num_completed = 0; + struct io_uring_cqe *cqe; + unsigned int head; + int ret = 0; + + io_uring_for_each_cqe(&queue->ring, head, cqe) { + int err = 0; + + num_completed++; + + err = cqe->res; + if (err != 0) { + if (err > 0 && ((uintptr_t)io_uring_cqe_get_data(cqe) == + (unsigned int)queue->eventfd)) { + /* teardown from eventfd */ + return -ENOTCONN; + } + + // XXX: Needs rate limited logs, otherwise log spam + //fuse_log(FUSE_LOG_ERR, "cqe res: %d\n", cqe->res); + + /* -ENOTCONN is ok on umount */ + if (err != -EINTR && err != -EOPNOTSUPP && + err != -EAGAIN && err != -ENOTCONN) { + se->error = cqe->res; + + /* return first error */ + if (ret == 0) + ret = err; + } + + } else { + fuse_uring_handle_cqe(queue, cqe); + } + } - return 0; + if (num_completed) + io_uring_cq_advance(&queue->ring, num_completed); + + return ret == 0 ? 0 : num_completed; } /** From d4abf661f9948075e1f043a825c37bdaa660f911 Mon Sep 17 00:00:00 2001 From: Bernd Schubert <bschubert@ddn.com> Date: Wed, 26 Mar 2025 01:07:05 +0100 Subject: [PATCH 106/241] Add fuse_session_process_uring_cqe Signed-off-by: Bernd Schubert <bschubert@ddn.com> --- lib/fuse_lowlevel.c | 133 ++++++++++++++++++++++++++++++++++++-------- lib/fuse_uring.c | 15 ----- 2 files changed, 111 insertions(+), 37 deletions(-) diff --git a/lib/fuse_lowlevel.c b/lib/fuse_lowlevel.c index fdbb60cda..d6b254e5b 100644 --- a/lib/fuse_lowlevel.c +++ b/lib/fuse_lowlevel.c @@ -3255,6 +3255,58 @@ static struct { */ #define FUSE_MAXOP (CUSE_INIT + 1) + +/** + * + * @return 0 if sanity is ok, error otherwise + */ +static inline int +fuse_req_opcode_sanity_ok(struct fuse_session *se, enum fuse_opcode in_op) +{ + int err = EIO; + + if (!se->got_init) { + enum fuse_opcode expected; + + expected = se->cuse_data ? CUSE_INIT : FUSE_INIT; + if (in_op != expected) + return err; + } else if (in_op == FUSE_INIT || in_op == CUSE_INIT) + return err; + + return 0; +} + +static inline void +fuse_session_in2req(struct fuse_req *req, struct fuse_in_header *in) +{ + req->unique = in->unique; + req->ctx.uid = in->uid; + req->ctx.gid = in->gid; + req->ctx.pid = in->pid; +} + +/** + * Implement -o allow_root + */ +static inline int +fuse_req_check_allow_root(struct fuse_session *se, enum fuse_opcode in_op, + uid_t in_uid) +{ + int err = EACCES; + + if (se->deny_others && in_uid != se->owner && in_uid != 0 && + in_op != FUSE_INIT && in_op != FUSE_READ && + in_op != FUSE_WRITE && in_op != FUSE_FSYNC && + in_op != FUSE_RELEASE && in_op != FUSE_READDIR && + in_op != FUSE_FSYNCDIR && in_op != FUSE_RELEASEDIR && + in_op != FUSE_NOTIFY_REPLY && + in_op != FUSE_READDIRPLUS) + return err; + + return 0; +} + static const char *opname(enum fuse_opcode opcode) { if (opcode >= FUSE_MAXOP || !fuse_ll_ops[opcode].name) @@ -3323,7 +3375,7 @@ void fuse_session_process_buf_internal(struct fuse_session *se, if (se->debug) { fuse_log(FUSE_LOG_DEBUG, - "unique: %llu, opcode: %s (%i), nodeid: %llu, insize: %zu, pid: %u\n", + "dev unique: %llu, opcode: %s (%i), nodeid: %llu, insize: %zu, pid: %u\n", (unsigned long long) in->unique, opname((enum fuse_opcode) in->opcode), in->opcode, (unsigned long long) in->nodeid, buf->size, in->pid); @@ -3344,31 +3396,15 @@ void fuse_session_process_buf_internal(struct fuse_session *se, goto clear_pipe; } - req->unique = in->unique; - req->ctx.uid = in->uid; - req->ctx.gid = in->gid; - req->ctx.pid = in->pid; + fuse_session_in2req(req, in); req->ch = ch ? fuse_chan_get(ch) : NULL; - err = EIO; - if (!se->got_init) { - enum fuse_opcode expected; - - expected = se->cuse_data ? CUSE_INIT : FUSE_INIT; - if (in->opcode != expected) - goto reply_err; - } else if (in->opcode == FUSE_INIT || in->opcode == CUSE_INIT) + err = fuse_req_opcode_sanity_ok(se, in->opcode); + if (err) goto reply_err; - err = EACCES; - /* Implement -o allow_root */ - if (se->deny_others && in->uid != se->owner && in->uid != 0 && - in->opcode != FUSE_INIT && in->opcode != FUSE_READ && - in->opcode != FUSE_WRITE && in->opcode != FUSE_FSYNC && - in->opcode != FUSE_RELEASE && in->opcode != FUSE_READDIR && - in->opcode != FUSE_FSYNCDIR && in->opcode != FUSE_RELEASEDIR && - in->opcode != FUSE_NOTIFY_REPLY && - in->opcode != FUSE_READDIRPLUS) + err = fuse_req_check_allow_root(se, in->opcode, in->uid); + if (err) goto reply_err; err = ENOSYS; @@ -3432,6 +3468,59 @@ void fuse_session_process_buf_internal(struct fuse_session *se, goto out_free; } +void fuse_session_process_uring_cqe(struct fuse_session *se, + struct fuse_req *req, + struct fuse_in_header *in, void *op_in, + void *op_payload, size_t payload_len) +{ + int err; + + fuse_session_in2req(req, in); + + err = fuse_req_opcode_sanity_ok(se, in->opcode); + if (err) + goto reply_err; + + err = fuse_req_check_allow_root(se, in->opcode, in->uid); + if (err) + goto reply_err; + + err = ENOSYS; + if (in->opcode >= FUSE_MAXOP || !fuse_ll_ops[in->opcode].func) + goto reply_err; + + if (se->debug) { + fuse_log( + FUSE_LOG_DEBUG, + "cqe unique: %llu, opcode: %s (%i), nodeid: %llu, insize: %zu, pid: %u\n", + (unsigned long long)in->unique, + opname((enum fuse_opcode)in->opcode), in->opcode, + (unsigned long long)in->nodeid, payload_len, in->pid); + } + + if (in->opcode == FUSE_WRITE && se->op.write_buf) { + struct fuse_bufvec bufv = { + .buf[0] = { .size = payload_len, + .flags = 0, + .mem = op_payload }, + .count = 1, + }; + _do_write_buf(req, in->nodeid, op_in, &bufv); + } else if (in->opcode == FUSE_NOTIFY_REPLY) { + struct fuse_buf buf = { .size = payload_len, + .mem = op_payload }; + do_notify_reply(req, in->nodeid, op_in, &buf); + } else { + fuse_ll_ops2[in->opcode].func(req, in->nodeid, op_in, + op_payload); + } + + return; + +reply_err: + fuse_reply_err(req, err); +} + #define LL_OPTION(n,o,v) \ { n, offsetof(struct fuse_session, o), v } diff --git a/lib/fuse_uring.c b/lib/fuse_uring.c index 47efa9517..65ee9870f 100644 --- a/lib/fuse_uring.c +++ b/lib/fuse_uring.c @@ -336,21 +336,6 @@ static struct fuse_ring_pool *fuse_create_ring(struct fuse_session *se) return NULL; } -/* stub function*/ -static void fuse_session_process_uring_cqe(struct fuse_session *se, - struct fuse_req *req, - struct fuse_in_header *in, - void *op_in, void *op_payload, - size_t op_payload_sz) -{ - (void)se; - (void)req; - (void)in; - (void)op_in; - (void)op_payload; - (void)op_payload_sz; -} - static void fuse_uring_handle_cqe(struct fuse_ring_queue *queue, struct io_uring_cqe *cqe) { From 988ef1172255dfc3abfc99097ceddfa9e1da66cd Mon Sep 17 00:00:00 2001 From: Bernd Schubert <bschubert@ddn.com> Date: Wed, 26 Mar 2025 01:25:23 +0100 Subject: [PATCH 107/241] Add fuse-io-uring reply support Signed-off-by: Bernd Schubert <bschubert@ddn.com> --- lib/fuse_lowlevel.c | 70 +++++++++++++-------- lib/fuse_uring.c | 150 ++++++++++++++++++++++++++++++++++++++++++++ lib/fuse_uring_i.h | 29 +++++++++ 3 files changed, 222 insertions(+), 27 deletions(-) diff --git a/lib/fuse_lowlevel.c b/lib/fuse_lowlevel.c index d6b254e5b..03aeca7c7 100644 --- a/lib/fuse_lowlevel.c +++ b/lib/fuse_lowlevel.c @@ -227,21 +227,22 @@ static struct fuse_req *fuse_ll_alloc_req(struct fuse_session *se) /* * Send data to fuse-kernel using an fd of the fuse device. */ -static int _fuse_send_msg(struct fuse_session *se, struct fuse_chan *ch, - struct iovec *iov, int count) +static int fuse_write_msg_dev(struct fuse_session *se, struct fuse_chan *ch, + struct iovec *iov, int count) { ssize_t res; int err; - if (se->io != NULL) { + if (se->io != NULL) + /* se->io->writev is never NULL if se->io is not NULL as * specified by fuse_session_custom_io() */ res = se->io->writev(ch ? ch->fd : se->fd, iov, count, se->userdata); - } else { + else res = writev(ch ? ch->fd : se->fd, iov, count); - } + if (res == -1) { /* ENOENT means the operation was interrupted */ err = errno; @@ -253,15 +254,17 @@ static int _fuse_send_msg(struct fuse_session *se, struct fuse_chan *ch, return 0; } -/* Send data. If *ch* is NULL, send via session master fd */ static int fuse_send_msg(struct fuse_session *se, struct fuse_chan *ch, - struct iovec *iov, int count) + struct iovec *iov, int count, fuse_req_t req) { struct fuse_out_header *out = iov[0].iov_base; int err; + bool is_uring = req && req->is_uring ? true : false; - assert(se != NULL); + if (!is_uring) + assert(se != NULL); out->len = iov_length(iov, count); + if (se->debug) { if (out->unique == 0) { fuse_log(FUSE_LOG_DEBUG, "NOTIFY: code=%d length=%u\n", @@ -278,12 +281,15 @@ static int fuse_send_msg(struct fuse_session *se, struct fuse_chan *ch, } } - err = _fuse_send_msg(se, ch, iov, count); + if (is_uring) + err = fuse_send_msg_uring(req, iov, count); + else + err = fuse_write_msg_dev(se, ch, iov, count); + trace_request_reply(out->unique, out->len, out->error, err); return err; } - int fuse_send_reply_iov_nofree(fuse_req_t req, int error, struct iovec *iov, int count) { @@ -305,7 +311,7 @@ int fuse_send_reply_iov_nofree(fuse_req_t req, int error, struct iovec *iov, iov[0].iov_base = &out; iov[0].iov_len = sizeof(struct fuse_out_header); - return fuse_send_msg(req->se, req->ch, iov, count); + return fuse_send_msg(req->se, req->ch, iov, count, req); } static int send_reply_iov(fuse_req_t req, int error, struct iovec *iov, @@ -321,6 +327,9 @@ static int send_reply_iov(fuse_req_t req, int error, struct iovec *iov, static int send_reply(fuse_req_t req, int error, const void *arg, size_t argsize) { + if (req->is_uring) + return send_reply_uring(req, error, arg, argsize); + struct iovec iov[2]; int count = 1; if (argsize) { @@ -599,7 +608,7 @@ static int fuse_send_data_iov_fallback(struct fuse_session *se, struct fuse_chan *ch, struct iovec *iov, int iov_count, struct fuse_bufvec *buf, - size_t len) + size_t len, fuse_req_t req) { struct fuse_bufvec mem_buf = FUSE_BUFVEC_INIT(len); void *mbuf; @@ -614,7 +623,7 @@ static int fuse_send_data_iov_fallback(struct fuse_session *se, iov[iov_count].iov_base = buf->buf[0].mem; iov[iov_count].iov_len = len; iov_count++; - return fuse_send_msg(se, ch, iov, iov_count); + return fuse_send_msg(se, ch, iov, iov_count, req); } res = posix_memalign(&mbuf, pagesize, len); @@ -632,7 +641,7 @@ static int fuse_send_data_iov_fallback(struct fuse_session *se, iov[iov_count].iov_base = mbuf; iov[iov_count].iov_len = len; iov_count++; - res = fuse_send_msg(se, ch, iov, iov_count); + res = fuse_send_msg(se, ch, iov, iov_count, req); free(mbuf); return res; @@ -764,8 +773,9 @@ static int grow_pipe_to_max(int pipefd) } static int fuse_send_data_iov(struct fuse_session *se, struct fuse_chan *ch, - struct iovec *iov, int iov_count, - struct fuse_bufvec *buf, unsigned int flags) + struct iovec *iov, int iov_count, + struct fuse_bufvec *buf, unsigned int flags, + fuse_req_t req) { int res; size_t len = fuse_buf_size(buf); @@ -917,7 +927,7 @@ static int fuse_send_data_iov(struct fuse_session *se, struct fuse_chan *ch, iov[iov_count].iov_base = mbuf; iov[iov_count].iov_len = len; iov_count++; - res = fuse_send_msg(se, ch, iov, iov_count); + res = fuse_send_msg(se, ch, iov, iov_count, req); free(mbuf); return res; } @@ -964,17 +974,17 @@ static int fuse_send_data_iov(struct fuse_session *se, struct fuse_chan *ch, return res; fallback: - return fuse_send_data_iov_fallback(se, ch, iov, iov_count, buf, len); + return fuse_send_data_iov_fallback(se, ch, iov, iov_count, buf, len, req); } #else static int fuse_send_data_iov(struct fuse_session *se, struct fuse_chan *ch, struct iovec *iov, int iov_count, - struct fuse_bufvec *buf, unsigned int flags) + struct fuse_bufvec *req_data, unsigned int flags) { - size_t len = fuse_buf_size(buf); + size_t len = fuse_buf_size(req_data); (void) flags; - return fuse_send_data_iov_fallback(se, ch, iov, iov_count, buf, len); + return fuse_send_data_iov_fallback(se, ch, iov, iov_count, req_data, len); } #endif @@ -985,13 +995,16 @@ int fuse_reply_data(fuse_req_t req, struct fuse_bufvec *bufv, struct fuse_out_header out; int res; + if (req->is_uring) + return fuse_reply_data_uring(req, bufv, flags); + iov[0].iov_base = &out; iov[0].iov_len = sizeof(struct fuse_out_header); out.unique = req->unique; out.error = 0; - res = fuse_send_data_iov(req->se, req->ch, iov, 1, bufv, flags); + res = fuse_send_data_iov(req->se, req->ch, iov, 1, bufv, flags, req); if (res <= 0) { fuse_free_req(req); return res; @@ -1743,8 +1756,8 @@ static void do_write_buf(fuse_req_t req, const fuse_ino_t nodeid, sizeof(struct fuse_write_in); } if (bufv.buf[0].size < arg->size) { - fuse_log(FUSE_LOG_ERR, "fuse: %s: buffer size too small\n", - __func__); + fuse_log(FUSE_LOG_ERR, + "fuse: %s: buffer size too small\n", __func__); fuse_reply_err(req, EIO); goto out; } @@ -2576,6 +2589,7 @@ _do_init(fuse_req_t req, const fuse_ino_t nodeid, const void *op_in, LL_SET_DEFAULT(se->op.readdirplus, FUSE_CAP_READDIRPLUS); LL_SET_DEFAULT(se->op.readdirplus && se->op.readdir, FUSE_CAP_READDIRPLUS_AUTO); + LL_SET_DEFAULT(1, FUSE_CAP_OVER_IO_URING); /* This could safely become default, but libfuse needs an API extension * to support it @@ -2840,6 +2854,7 @@ static int send_notify_iov(struct fuse_session *se, int notify_code, struct iovec *iov, int count) { struct fuse_out_header out; + struct fuse_req *req = NULL; if (!se->got_init) return -ENOTCONN; @@ -2849,7 +2864,7 @@ static int send_notify_iov(struct fuse_session *se, int notify_code, iov[0].iov_base = &out; iov[0].iov_len = sizeof(struct fuse_out_header); - return fuse_send_msg(se, NULL, iov, count); + return fuse_send_msg(se, NULL, iov, count, req); } int fuse_lowlevel_notify_poll(struct fuse_pollhandle *ph) @@ -2991,6 +3006,7 @@ int fuse_lowlevel_notify_store(struct fuse_session *se, fuse_ino_t ino, struct iovec iov[3]; size_t size = fuse_buf_size(bufv); int res; + struct fuse_req *req = NULL; if (!se) return -EINVAL; @@ -3011,7 +3027,7 @@ int fuse_lowlevel_notify_store(struct fuse_session *se, fuse_ino_t ino, iov[1].iov_base = &outarg; iov[1].iov_len = sizeof(outarg); - res = fuse_send_data_iov(se, NULL, iov, 2, bufv, flags); + res = fuse_send_data_iov(se, NULL, iov, 2, bufv, flags, req); if (res > 0) res = -res; @@ -3392,7 +3408,7 @@ void fuse_session_process_buf_internal(struct fuse_session *se, .iov_len = sizeof(struct fuse_out_header), }; - fuse_send_msg(se, ch, &iov, 1); + fuse_send_msg(se, ch, &iov, 1, NULL); goto clear_pipe; } diff --git a/lib/fuse_uring.c b/lib/fuse_uring.c index 65ee9870f..55d68fa27 100644 --- a/lib/fuse_uring.c +++ b/lib/fuse_uring.c @@ -141,6 +141,156 @@ fuse_uring_sqe_prepare(struct io_uring_sqe *sqe, struct fuse_ring_ent *req, sqe->__pad1 = 0; } +static int fuse_uring_commit_sqe(struct fuse_ring_pool *ring_pool, + struct fuse_ring_queue *queue, + struct fuse_ring_ent *ring_ent) +{ + struct fuse_session *se = ring_pool->se; + struct fuse_uring_req_header *rrh = ring_ent->req_header; + struct fuse_out_header *out = (struct fuse_out_header *)&rrh->in_out; + struct fuse_uring_ent_in_out *ent_in_out = + (struct fuse_uring_ent_in_out *)&rrh->ring_ent_in_out; + struct io_uring_sqe *sqe = io_uring_get_sqe(&queue->ring); + + if (sqe == NULL) { + /* This is an impossible condition, unless there is a bug. + * The kernel sent back an SQEs, which is assigned to a request. + * There is no way to get out of SQEs, as the number of + * SQEs matches the number tof requests. + */ + + se->error = -EIO; + fuse_log(FUSE_LOG_ERR, "Failed to get a ring SQEs\n"); + + return -EIO; + } + + fuse_uring_sqe_prepare(sqe, ring_ent, + FUSE_IO_URING_CMD_COMMIT_AND_FETCH); + + fuse_uring_sqe_set_req_data(fuse_uring_get_sqe_cmd(sqe), queue->qid, + ring_ent->req_commit_id); + + if (se->debug) { + fuse_log(FUSE_LOG_DEBUG, " unique: %llu, result=%d\n", + out->unique, ent_in_out->payload_sz); + } + + /* XXX: This needs to be a ring config option */ + io_uring_submit(&queue->ring); + + return 0; +} + +int send_reply_uring(fuse_req_t req, int error, const void *arg, size_t argsize) +{ + int res; + struct fuse_ring_ent *ring_ent = + container_of(req, struct fuse_ring_ent, req); + struct fuse_uring_req_header *rrh = ring_ent->req_header; + struct fuse_out_header *out = (struct fuse_out_header *)&rrh->in_out; + struct fuse_uring_ent_in_out *ent_in_out = + (struct fuse_uring_ent_in_out *)&rrh->ring_ent_in_out; + + struct fuse_ring_queue *queue = ring_ent->ring_queue; + struct fuse_ring_pool *ring_pool = queue->ring_pool; + size_t max_payload_sz = ring_pool->max_req_payload_sz; + + if (argsize > max_payload_sz) { + fuse_log(FUSE_LOG_ERR, "argsize %zu exceeds buffer size %zu", + argsize, max_payload_sz); + error = -EINVAL; + } else if (argsize) { + memcpy(ring_ent->op_payload, arg, argsize); + } + ent_in_out->payload_sz = argsize; + + out->error = error; + out->unique = req->unique; + + res = fuse_uring_commit_sqe(ring_pool, queue, ring_ent); + + fuse_free_req(req); + + return res; +} + +int fuse_reply_data_uring(fuse_req_t req, struct fuse_bufvec *bufv, + enum fuse_buf_copy_flags flags) +{ + struct fuse_ring_ent *ring_ent = + container_of(req, struct fuse_ring_ent, req); + + struct fuse_ring_queue *queue = ring_ent->ring_queue; + struct fuse_ring_pool *ring_pool = queue->ring_pool; + struct fuse_uring_req_header *rrh = ring_ent->req_header; + struct fuse_out_header *out = (struct fuse_out_header *)&rrh->in_out; + struct fuse_uring_ent_in_out *ent_in_out = + (struct fuse_uring_ent_in_out *)&rrh->ring_ent_in_out; + size_t max_payload_sz = ring_ent->req_payload_sz; + struct fuse_bufvec dest_vec = FUSE_BUFVEC_INIT(max_payload_sz); + int res; + + dest_vec.buf[0].mem = ring_ent->op_payload; + dest_vec.buf[0].size = max_payload_sz; + + res = fuse_buf_copy(&dest_vec, bufv, flags); + + out->error = res < 0 ? res : 0; + out->unique = req->unique; + + ent_in_out->payload_sz = res > 0 ? res : 0; + + res = fuse_uring_commit_sqe(ring_pool, queue, ring_ent); + + fuse_free_req(req); + + return res; +} + +/** + * Copy the iov into the ring buffer and submit and commit/fetch sqe + */ +int fuse_send_msg_uring(fuse_req_t req, struct iovec *iov, int count) +{ + struct fuse_ring_ent *ring_ent = + container_of(req, struct fuse_ring_ent, req); + + struct fuse_ring_queue *queue = ring_ent->ring_queue; + struct fuse_ring_pool *ring_pool = queue->ring_pool; + struct fuse_uring_req_header *rrh = ring_ent->req_header; + struct fuse_out_header *out = (struct fuse_out_header *)&rrh->in_out; + struct fuse_uring_ent_in_out *ent_in_out = + (struct fuse_uring_ent_in_out *)&rrh->ring_ent_in_out; + size_t max_buf = ring_pool->max_req_payload_sz; + size_t len = 0; + int res = 0; + + /* copy iov into the payload, idx=0 is the header section */ + for (int idx = 1; idx < count; idx++) { + struct iovec *cur = &iov[idx]; + + if (len + cur->iov_len > max_buf) { + fuse_log(FUSE_LOG_ERR, + "iov[%d] exceeds buffer size %zu", + idx, max_buf); + res = -EINVAL; /* Gracefully handle this? */ + break; + } + + memcpy(ring_ent->op_payload + len, cur->iov_base, cur->iov_len); + len += cur->iov_len; + } + + ent_in_out->payload_sz = len; + + out->error = res; + out->unique = req->unique; + out->len = len; + + return fuse_uring_commit_sqe(ring_pool, queue, ring_ent); +} + static int fuse_queue_setup_io_uring(struct io_uring *ring, size_t qid, size_t depth, int fd, int evfd) { diff --git a/lib/fuse_uring_i.h b/lib/fuse_uring_i.h index e9f298908..40cda5ba9 100644 --- a/lib/fuse_uring_i.h +++ b/lib/fuse_uring_i.h @@ -27,6 +27,12 @@ struct fuse_in_header; int fuse_uring_start(struct fuse_session *se); int fuse_uring_stop(struct fuse_session *se); +int send_reply_uring(fuse_req_t req, int error, const void *arg, + size_t argsize); + +int fuse_reply_data_uring(fuse_req_t req, struct fuse_bufvec *bufv, + enum fuse_buf_copy_flags flags); +int fuse_send_msg_uring(fuse_req_t req, struct iovec *iov, int count); #else // HAVE_URING @@ -40,6 +46,29 @@ static inline int fuse_uring_stop(struct fuse_session *se FUSE_VAR_UNUSED) return -ENOTSUP; } +static inline int send_reply_uring(fuse_req_t req FUSE_VAR_UNUSED, + int error FUSE_VAR_UNUSED, + const void *arg FUSE_VAR_UNUSED, + size_t argsize FUSE_VAR_UNUSED) +{ + return -ENOTSUP; +} + +static inline int +fuse_reply_data_uring(fuse_req_t req FUSE_VAR_UNUSED, + struct fuse_bufvec *bufv FUSE_VAR_UNUSED, + enum fuse_buf_copy_flags flags FUSE_VAR_UNUSED) +{ + return -ENOTSUP; +} + +static inline int fuse_send_msg_uring(fuse_req_t req FUSE_VAR_UNUSED, + struct iovec *iov FUSE_VAR_UNUSED, + int count FUSE_VAR_UNUSED) +{ + return -ENOTSUP; +} + #endif // HAVE_URING #endif // FUSE_URING_I_H_ From 3824fabf8aaa6210876ac0519f451f4885c7ebbc Mon Sep 17 00:00:00 2001 From: Bernd Schubert <bschubert@ddn.com> Date: Tue, 15 Apr 2025 17:03:09 +0200 Subject: [PATCH 108/241] fuse_common.h: Prefix flags with 1UL Ensure it is correctly stored in an unsigned 32 bit int. Signed-off-by: Bernd Schubert <bschubert@ddn.com> --- include/fuse_common.h | 58 +++++++++++++++++++++---------------------- 1 file changed, 29 insertions(+), 29 deletions(-) diff --git a/include/fuse_common.h b/include/fuse_common.h index f1d4b170a..249e0c94f 100644 --- a/include/fuse_common.h +++ b/include/fuse_common.h @@ -182,7 +182,7 @@ struct fuse_loop_config_v1 { * * This feature is enabled by default when supported by the kernel. */ -#define FUSE_CAP_ASYNC_READ (1 << 0) +#define FUSE_CAP_ASYNC_READ (1UL << 0) /** * Indicates that the filesystem supports "remote" locking. @@ -190,7 +190,7 @@ struct fuse_loop_config_v1 { * 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) +#define FUSE_CAP_POSIX_LOCKS (1UL << 1) /** * Indicates that the filesystem supports the O_TRUNC open flag. If @@ -199,7 +199,7 @@ struct fuse_loop_config_v1 { * * This feature is enabled by default when supported by the kernel. */ -#define FUSE_CAP_ATOMIC_O_TRUNC (1 << 3) +#define FUSE_CAP_ATOMIC_O_TRUNC (1UL << 3) /** * Indicates that the filesystem supports lookups of "." and "..". @@ -211,7 +211,7 @@ struct fuse_loop_config_v1 { * * This feature is disabled by default. */ -#define FUSE_CAP_EXPORT_SUPPORT (1 << 4) +#define FUSE_CAP_EXPORT_SUPPORT (1UL << 4) /** * Indicates that the kernel should not apply the umask to the @@ -219,7 +219,7 @@ struct fuse_loop_config_v1 { * * This feature is disabled by default. */ -#define FUSE_CAP_DONT_MASK (1 << 6) +#define FUSE_CAP_DONT_MASK (1UL << 6) /** * Indicates that libfuse should try to use splice() when writing to @@ -227,7 +227,7 @@ struct fuse_loop_config_v1 { * * This feature is disabled by default. */ -#define FUSE_CAP_SPLICE_WRITE (1 << 7) +#define FUSE_CAP_SPLICE_WRITE (1UL << 7) /** * Indicates that libfuse should try to move pages instead of copying when @@ -235,7 +235,7 @@ struct fuse_loop_config_v1 { * * This feature is disabled by default. */ -#define FUSE_CAP_SPLICE_MOVE (1 << 8) +#define FUSE_CAP_SPLICE_MOVE (1UL << 8) /** * Indicates that libfuse should try to use splice() when reading from @@ -244,7 +244,7 @@ struct fuse_loop_config_v1 { * 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) +#define FUSE_CAP_SPLICE_READ (1UL << 9) /** * If set, the calls to flock(2) will be emulated using POSIX locks and must @@ -257,14 +257,14 @@ struct fuse_loop_config_v1 { * 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) +#define FUSE_CAP_FLOCK_LOCKS (1UL << 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) +#define FUSE_CAP_IOCTL_DIR (1UL << 11) /** * Traditionally, while a file is open the FUSE kernel module only @@ -286,7 +286,7 @@ struct fuse_loop_config_v1 { * * This feature is enabled by default when supported by the kernel. */ -#define FUSE_CAP_AUTO_INVAL_DATA (1 << 12) +#define FUSE_CAP_AUTO_INVAL_DATA (1UL << 12) /** * Indicates that the filesystem supports readdirplus. @@ -294,7 +294,7 @@ struct fuse_loop_config_v1 { * 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) +#define FUSE_CAP_READDIRPLUS (1UL << 13) /** * Indicates that the filesystem supports adaptive readdirplus. @@ -322,7 +322,7 @@ struct fuse_loop_config_v1 { * if the filesystem implements both a readdirplus() and a readdir() * handler. */ -#define FUSE_CAP_READDIRPLUS_AUTO (1 << 14) +#define FUSE_CAP_READDIRPLUS_AUTO (1UL << 14) /** * Indicates that the filesystem supports asynchronous direct I/O submission. @@ -333,7 +333,7 @@ struct fuse_loop_config_v1 { * * This feature is enabled by default when supported by the kernel. */ -#define FUSE_CAP_ASYNC_DIO (1 << 15) +#define FUSE_CAP_ASYNC_DIO (1UL << 15) /** * Indicates that writeback caching should be enabled. This means that @@ -342,7 +342,7 @@ struct fuse_loop_config_v1 { * * This feature is disabled by default. */ -#define FUSE_CAP_WRITEBACK_CACHE (1 << 16) +#define FUSE_CAP_WRITEBACK_CACHE (1UL << 16) /** * Indicates support for zero-message opens. If this flag is set in @@ -357,7 +357,7 @@ struct fuse_loop_config_v1 { * this behavior you must return `ENOSYS` from the open() handler on supporting * kernels. */ -#define FUSE_CAP_NO_OPEN_SUPPORT (1 << 17) +#define FUSE_CAP_NO_OPEN_SUPPORT (1UL << 17) /** * Indicates support for parallel directory operations. If this flag @@ -365,7 +365,7 @@ struct fuse_loop_config_v1 { * readdir() requests are never issued concurrently for the same * directory. */ -#define FUSE_CAP_PARALLEL_DIROPS (1 << 18) +#define FUSE_CAP_PARALLEL_DIROPS (1UL << 18) /** * Indicates support for POSIX ACLs. @@ -384,7 +384,7 @@ struct fuse_loop_config_v1 { * * This feature is disabled by default. */ -#define FUSE_CAP_POSIX_ACL (1 << 19) +#define FUSE_CAP_POSIX_ACL (1UL << 19) /** * Indicates that the filesystem is responsible for unsetting @@ -393,7 +393,7 @@ struct fuse_loop_config_v1 { * * This feature is disabled by default. */ -#define FUSE_CAP_HANDLE_KILLPRIV (1 << 20) +#define FUSE_CAP_HANDLE_KILLPRIV (1UL << 20) /** * Indicates that the filesystem is responsible for unsetting @@ -410,7 +410,7 @@ struct fuse_loop_config_v1 { * * This feature is disabled by default. */ -#define FUSE_CAP_HANDLE_KILLPRIV_V2 (1 << 21) +#define FUSE_CAP_HANDLE_KILLPRIV_V2 (1UL << 21) /** * Indicates that the kernel supports caching symlinks in its page cache. @@ -423,7 +423,7 @@ struct fuse_loop_config_v1 { * 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) +#define FUSE_CAP_CACHE_SYMLINKS (1UL << 23) /** * Indicates support for zero-message opendirs. If this flag is set in @@ -438,7 +438,7 @@ struct fuse_loop_config_v1 { * this behavior you must return `ENOSYS` from the opendir() handler on * supporting kernels. */ -#define FUSE_CAP_NO_OPENDIR_SUPPORT (1 << 24) +#define FUSE_CAP_NO_OPENDIR_SUPPORT (1UL << 24) /** * Indicates support for invalidating cached pages only on explicit request. @@ -461,7 +461,7 @@ struct fuse_loop_config_v1 { * * This feature is disabled by default. */ -#define FUSE_CAP_EXPLICIT_INVAL_DATA (1 << 25) +#define FUSE_CAP_EXPLICIT_INVAL_DATA (1UL << 25) /** * Indicates support that dentries can be expired. @@ -477,14 +477,14 @@ struct fuse_loop_config_v1 { * 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) +#define FUSE_CAP_EXPIRE_ONLY (1UL << 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) +#define FUSE_CAP_SETXATTR_EXT (1UL << 27) /** * Files opened with FUSE_DIRECT_IO do not support MAP_SHARED mmap. This restriction @@ -493,7 +493,7 @@ struct fuse_loop_config_v1 { * 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) +#define FUSE_CAP_DIRECT_IO_ALLOW_MMAP (1UL << 28) /** * Indicates support for passthrough mode access for read/write operations. @@ -505,7 +505,7 @@ struct fuse_loop_config_v1 { * * This feature is disabled by default. */ -#define FUSE_CAP_PASSTHROUGH (1 << 29) +#define FUSE_CAP_PASSTHROUGH (1UL << 29) /** * Indicates that the file system cannot handle NFS export @@ -513,12 +513,12 @@ struct fuse_loop_config_v1 { * 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) +#define FUSE_CAP_NO_EXPORT_SUPPORT (1UL << 30) /** * Indicates support for io-uring between fuse-server and fuse-client */ -#define FUSE_CAP_OVER_IO_URING (1 << 31) +#define FUSE_CAP_OVER_IO_URING (1UL << 31) /** * Ioctl flags From 71b6fb723aacd9c216c68db3cc1f4d87291cf408 Mon Sep 17 00:00:00 2001 From: Bernd Schubert <bschubert@ddn.com> Date: Tue, 15 Apr 2025 18:08:54 +0200 Subject: [PATCH 109/241] fuse: Add io-uring options Signed-off-by: Bernd Schubert <bschubert@ddn.com> --- lib/fuse_lowlevel.c | 10 +++++++++- lib/fuse_uring_i.h | 4 ++++ lib/helper.c | 6 +++++- 3 files changed, 18 insertions(+), 2 deletions(-) diff --git a/lib/fuse_lowlevel.c b/lib/fuse_lowlevel.c index 03aeca7c7..ad3641423 100644 --- a/lib/fuse_lowlevel.c +++ b/lib/fuse_lowlevel.c @@ -3545,6 +3545,8 @@ static const struct fuse_opt fuse_ll_opts[] = { LL_OPTION("-d", debug, 1), LL_OPTION("--debug", debug, 1), LL_OPTION("allow_root", deny_others, 1), + LL_OPTION("io_uring", uring.enable, 1), + LL_OPTION("io_uring_q_depth=%u", uring.q_depth, -1), FUSE_OPT_END }; @@ -3562,7 +3564,11 @@ void fuse_lowlevel_help(void) printf( " -o allow_other allow access by all users\n" " -o allow_root allow access by root\n" -" -o auto_unmount auto unmount on process termination\n"); +" -o auto_unmount auto unmount on process termination\n" +" -o auto_unmount auto unmount on process termination\n" +" -o io_uring enable io-uring\n" +" -o io_uring_q_depth=<n> io-uring queue depth\n" +); } void fuse_session_destroy(struct fuse_session *se) @@ -3892,6 +3898,8 @@ fuse_session_new_versioned(struct fuse_args *args, se->conn.max_write = FUSE_DEFAULT_MAX_PAGES_LIMIT * getpagesize(); se->bufsize = se->conn.max_write + FUSE_BUFFER_HEADER_SIZE; se->conn.max_readahead = UINT_MAX; + se->uring.enable = SESSION_DEF_URING_ENABLE; + se->uring.q_depth = SESSION_DEF_URING_Q_DEPTH; /* Parse options */ if(fuse_opt_parse(args, se, fuse_ll_opts, NULL) == -1) diff --git a/lib/fuse_uring_i.h b/lib/fuse_uring_i.h index 40cda5ba9..a65241798 100644 --- a/lib/fuse_uring_i.h +++ b/lib/fuse_uring_i.h @@ -16,6 +16,10 @@ #include "util.h" #endif +/* io-uring defaults */ +#define SESSION_DEF_URING_ENABLE (0) +#define SESSION_DEF_URING_Q_DEPTH (8) + void fuse_session_process_uring_cqe(struct fuse_session *se, struct fuse_req *req, struct fuse_in_header *in, void *in_header, diff --git a/lib/helper.c b/lib/helper.c index 0b0dad262..5811c53d3 100644 --- a/lib/helper.c +++ b/lib/helper.c @@ -139,7 +139,11 @@ void fuse_cmdline_help(void) " -o max_idle_threads the maximum number of idle worker threads\n" " allowed (default: -1)\n" " -o max_threads the maximum number of worker threads\n" - " allowed (default: 10)\n"); + " allowed (default: 10)\n" + /* fuse_ll_opts in fuse_lowlevel.c, FIXME, call into that file */ + " -o io_uring enable io-uring\n" + " -o io_uring_q_depth=<n> io-uring queue depth\n" +); } static int fuse_helper_opt_proc(void *data, const char *arg, int key, From 538b51feedbcdcd78beefd7517ff19c77c4e4526 Mon Sep 17 00:00:00 2001 From: Bernd Schubert <bschubert@ddn.com> Date: Tue, 15 Apr 2025 20:13:47 +0200 Subject: [PATCH 110/241] Add fuse_req_is_uring() to check if a req comes through io-uring This might be useful to optimize code paths. For example, with io-uring the request buffer is valid until the request is replied to, while without io-uring the request buffer is only valid in current thread context. Signed-off-by: Bernd Schubert <bschubert@ddn.com> --- include/fuse_lowlevel.h | 5 +++++ lib/fuse_lowlevel.c | 5 +++++ lib/fuse_versionscript | 5 +++++ 3 files changed, 15 insertions(+) diff --git a/include/fuse_lowlevel.h b/include/fuse_lowlevel.h index c7b44d963..138a78436 100644 --- a/include/fuse_lowlevel.h +++ b/include/fuse_lowlevel.h @@ -2315,6 +2315,11 @@ void fuse_session_process_buf(struct fuse_session *se, */ int fuse_session_receive_buf(struct fuse_session *se, struct fuse_buf *buf); +/** + * Check if the request is submitted through fuse-io-uring + */ +bool fuse_req_is_uring(fuse_req_t req); + #ifdef __cplusplus } #endif diff --git a/lib/fuse_lowlevel.c b/lib/fuse_lowlevel.c index ad3641423..c7cbebd26 100644 --- a/lib/fuse_lowlevel.c +++ b/lib/fuse_lowlevel.c @@ -3157,6 +3157,11 @@ int fuse_req_interrupted(fuse_req_t req) return interrupted; } +bool fuse_req_is_uring(fuse_req_t req) +{ + return req->is_uring; +} + static struct { void (*func)(fuse_req_t req, const fuse_ino_t node, const void *arg); const char *name; diff --git a/lib/fuse_versionscript b/lib/fuse_versionscript index 6c5fc83eb..22c59e1af 100644 --- a/lib/fuse_versionscript +++ b/lib/fuse_versionscript @@ -202,6 +202,11 @@ FUSE_3.17 { fuse_log_close_syslog; } FUSE_3.12; +FUSE_3.18 { + global: + fuse_req_is_uring; +} FUSE_3.17; + # Local Variables: # indent-tabs-mode: t # End: From 91c9803cb4dc5251abbb9af312c917dddfdd689e Mon Sep 17 00:00:00 2001 From: Bernd Schubert <bschubert@ddn.com> Date: Tue, 15 Apr 2025 20:42:19 +0200 Subject: [PATCH 111/241] passthrough_hp: Add io-uring options Signed-off-by: Bernd Schubert <bschubert@ddn.com> --- example/passthrough_hp.cc | 29 +++++++++++++++++++++++++---- 1 file changed, 25 insertions(+), 4 deletions(-) diff --git a/example/passthrough_hp.cc b/example/passthrough_hp.cc index 8b5214c21..b88c2c5dc 100644 --- a/example/passthrough_hp.cc +++ b/example/passthrough_hp.cc @@ -77,6 +77,9 @@ using namespace std; #define SFS_DEFAULT_THREADS "-1" // take libfuse value as default #define SFS_DEFAULT_CLONE_FD "0" +#define SFS_DEFAULT_URING "1" +#define SFS_DEFAULT_URING_Q_DEPTH "0" +#define SFS_DEFAULT_URING_ARGLEN "0" /* We are re-using pointers to our `struct sfs_inode` and `struct sfs_dirp` elements as inodes and file handles. This means that we @@ -155,6 +158,11 @@ struct Fs { bool nocache; size_t num_threads; bool clone_fd; + struct { + bool enable; + int queue_depth; + } uring; + std::string fuse_mount_options; bool direct_io; bool passthrough; @@ -1400,10 +1408,12 @@ static cxxopts::ParseResult parse_options(int argc, char **argv) { ("o", "Mount options (see mount.fuse(5) - only use if you know what " "you are doing)", cxxopts::value(mount_options)) ("num-threads", "Number of libfuse worker threads", - cxxopts::value<int>()->default_value(SFS_DEFAULT_THREADS)) + cxxopts::value<int>()->default_value(SFS_DEFAULT_THREADS)) ("clone-fd", "use separate fuse device fd for each thread") - ("direct-io", "enable fuse kernel internal direct-io"); - + ("direct-io", "enable fuse kernel internal direct-io") + ("uring", "use uring communication") + ("uring-q-depth", "io-uring queue depth", + cxxopts::value<int>()->default_value(SFS_DEFAULT_URING_Q_DEPTH)); // FIXME: Find a better way to limit the try clause to just // opt_parser.parse() (cf. https://github.com/jarro2783/cxxopts/issues/146) auto options = parse_wrapper(opt_parser, argc, argv); @@ -1435,6 +1445,10 @@ static cxxopts::ParseResult parse_options(int argc, char **argv) { fs.num_threads = options["num-threads"].as<int>(); fs.clone_fd = options.count("clone-fd"); fs.direct_io = options.count("direct-io"); + + fs.uring.enable = options.count("uring"); + fs.uring.queue_depth = options["uring-q-depth"].as<int>(); + char* resolved_path = realpath(argv[1], NULL); if (resolved_path == NULL) warn("WARNING: realpath() failed with"); @@ -1518,7 +1532,14 @@ int main(int argc, char *argv[]) { fuse_opt_add_arg(&args, "-o") || fuse_opt_add_arg(&args, fs.fuse_mount_options.c_str()) || (fs.debug_fuse && fuse_opt_add_arg(&args, "-odebug"))) - errx(3, "ERROR: Out of memory"); + errx(3, "ERROR: Out of memory adding arguments"); + + if (fs.uring.enable) { + if (fuse_opt_add_arg(&args, "-oio_uring") || + fuse_opt_add_arg(&args, ("-oio_uring_q_depth=" + + std::to_string(fs.uring.queue_depth)).c_str())) + errx(3, "ERROR: Out of memory adding io-uring arguments"); + } ret = -1; fuse_lowlevel_ops sfs_oper {}; From 3421cab7773268e0afb4b300775b4d60f2b7acf5 Mon Sep 17 00:00:00 2001 From: Bernd Schubert <bschubert@ddn.com> Date: Thu, 24 Apr 2025 19:38:21 +0200 Subject: [PATCH 112/241] env variables to override default io-uring enable and q-depth We want to especially test with and without io-uring being enabled. Ideally without modifying all tests - that is what the env variable can be used for. Signed-off-by: Bernd Schubert <bschubert@ddn.com> --- lib/fuse_lowlevel.c | 13 +++++++++++-- test/ci-build.sh | 7 +++++++ 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/lib/fuse_lowlevel.c b/lib/fuse_lowlevel.c index c7cbebd26..8c9cb9074 100644 --- a/lib/fuse_lowlevel.c +++ b/lib/fuse_lowlevel.c @@ -3903,8 +3903,17 @@ fuse_session_new_versioned(struct fuse_args *args, se->conn.max_write = FUSE_DEFAULT_MAX_PAGES_LIMIT * getpagesize(); se->bufsize = se->conn.max_write + FUSE_BUFFER_HEADER_SIZE; se->conn.max_readahead = UINT_MAX; - se->uring.enable = SESSION_DEF_URING_ENABLE; - se->uring.q_depth = SESSION_DEF_URING_Q_DEPTH; + + /* + * Allow overriding with env, mostly to avoid the need to modify + * all tests. I.e. to test with and without io-uring being enabled. + */ + se->uring.enable = getenv("FUSE_URING_ENABLE") ? + atoi(getenv("FUSE_URING_ENABLE")) : + SESSION_DEF_URING_ENABLE; + se->uring.q_depth = getenv("FUSE_URING_QUEUE_DEPTH") ? + atoi(getenv("FUSE_URING_QUEUE_DEPTH")) : + SESSION_DEF_URING_Q_DEPTH; /* Parse options */ if(fuse_opt_parse(args, se, fuse_ll_opts, NULL) == -1) diff --git a/test/ci-build.sh b/test/ci-build.sh index 86230bdfd..2bced379c 100755 --- a/test/ci-build.sh +++ b/test/ci-build.sh @@ -118,6 +118,13 @@ sanitized_build() sudo rm -fr ${PREFIX_DIR} ) +# Sanitized with io-uring +export CC=clang +export CXX=clang++ +export FUSE_URING_ENABLE=1 +sanitized_build +unset FUSE_URING_ENABLE + # 32-bit sanitized build export CC=clang export CXX=clang++ From e70f91cb6387a4e14e7a4a4ebe9dc1100cbc1126 Mon Sep 17 00:00:00 2001 From: Bernd Schubert <bschubert@ddn.com> Date: Tue, 15 Apr 2025 20:57:05 +0200 Subject: [PATCH 113/241] Set FUSE_CAP_OVER_IO_URING flag - allow io-uring mode So far it was disabled as the series was not complete yet. Signed-off-by: Bernd Schubert <bschubert@ddn.com> --- lib/fuse_lowlevel.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lib/fuse_lowlevel.c b/lib/fuse_lowlevel.c index 8c9cb9074..8c5d060dc 100644 --- a/lib/fuse_lowlevel.c +++ b/lib/fuse_lowlevel.c @@ -2546,6 +2546,9 @@ _do_init(fuse_req_t req, const fuse_ino_t nodeid, const void *op_in, se->conn.capable_ext |= FUSE_CAP_PASSTHROUGH; if (inargflags & FUSE_NO_EXPORT_SUPPORT) se->conn.capable_ext |= FUSE_CAP_NO_EXPORT_SUPPORT; + if (inargflags & FUSE_OVER_IO_URING) + se->conn.capable_ext |= FUSE_CAP_OVER_IO_URING; + } else { se->conn.max_readahead = 0; } From e122b3faf365df72ca397b8ed88e7dcff8789cca Mon Sep 17 00:00:00 2001 From: Bernd Schubert <bschubert@ddn.com> Date: Mon, 28 Apr 2025 21:47:27 +0200 Subject: [PATCH 114/241] Add a README for fuse-over-io-uring Also update the comment description of fuse_uring.c Signed-off-by: Bernd Schubert <bschubert@ddn.com> --- doc/README.fuse-io-uring | 38 ++++++++++++++++++++++++++++++++++++++ lib/fuse_uring.c | 3 +-- 2 files changed, 39 insertions(+), 2 deletions(-) create mode 100644 doc/README.fuse-io-uring diff --git a/doc/README.fuse-io-uring b/doc/README.fuse-io-uring new file mode 100644 index 000000000..7207c6e74 --- /dev/null +++ b/doc/README.fuse-io-uring @@ -0,0 +1,38 @@ +fuse-over-io-uring uses io-uring for transport of kernel/userspace +messages. See also https://docs.kernel.org/filesystems/fuse-io-uring.html + +In order to enable it, the kernel module needs to have it enabled: +echo 1 > /sys/module/fuse/parameters/enable_uring + +Additionally, FUSE_CAP_OVER_IO_URING needs to be set and +se->uring.enable has to be true. The latter can be +achieved with the libfuse option '-oio_uring'. + +Default queue-depth is 8 and can be changed with the parameter +'-oio_uring_q_depth'. + +As of now there is always one queue per core. A reduced number +of cores in development. + +Benefits: +- Improved performance by using io_uring for kernel/userspace communication +- Reduced system call overhead compared to traditional FUSE +- Asynchronous I/O operations + +Usage: +To enable io_uring support when mounting a FUSE filesystem: +1. Enable kernel support: echo 1 > /sys/module/fuse/parameters/enable_uring +2. Mount with io_uring option: -oio_uring +3. Optionally adjust queue depth: -oio_uring_q_depth=<depth> + +Example: +./my_fuse_fs /source /mountpoint -oio_uring -oio_uring_q_depth=16 + +Requirements: +- Linux kernel with io_uring and FUSE io_uring support enabled +- libfuse compiled with io_uring support + +Build Dependencies: +- liburing (for io_uring support) +- libnuma (required alongside liburing) +- meson build system with option: -Denable-io-uring=true diff --git a/lib/fuse_uring.c b/lib/fuse_uring.c index 55d68fa27..62c5a4d78 100644 --- a/lib/fuse_uring.c +++ b/lib/fuse_uring.c @@ -2,8 +2,7 @@ * FUSE: Filesystem in Userspace * Copyright (C) 2025 Bernd Schubert <bschubert@ddn.com> * - * Implementation of (most of) the low-level FUSE API. The session loop - * functions are implemented in separate files. + * Implementation of (most of) FUSE-over-io-uring. * * This program can be distributed under the terms of the GNU LGPLv2. * See the file COPYING.LIB From 3ea426d8eec1d1306ba8c125b0fd59e2360eb524 Mon Sep 17 00:00:00 2001 From: Joanne Koong <joannelkoong@gmail.com> Date: Tue, 18 Feb 2025 10:42:41 -0800 Subject: [PATCH 115/241] lib/fuse.c: refactor handler logic for readability Refactor handler logic to make it more readable. No functional changes. Signed-off-by: Joanne Koong <joannelkoong@gmail.com> --- lib/fuse.c | 849 +++++++++++++++++++++++++---------------------------- 1 file changed, 399 insertions(+), 450 deletions(-) diff --git a/lib/fuse.c b/lib/fuse.c index 9b9b98327..7f9c982fb 100644 --- a/lib/fuse.c +++ b/lib/fuse.c @@ -1592,146 +1592,133 @@ int fuse_fs_getattr(struct fuse_fs *fs, const char *path, struct stat *buf, struct fuse_file_info *fi) { fuse_get_context()->private_data = fs->user_data; - if (fs->op.getattr) { - if (fs->debug) { - char buf[10]; - fuse_log(FUSE_LOG_DEBUG, "getattr[%s] %s\n", - file_info_string(fi, buf, sizeof(buf)), - path); - } - return fs->op.getattr(path, buf, fi); - } else { + if (!fs->op.getattr) return -ENOSYS; + + if (fs->debug) { + char buf[10]; + + fuse_log(FUSE_LOG_DEBUG, "getattr[%s] %s\n", + file_info_string(fi, buf, sizeof(buf)), + path); } + return fs->op.getattr(path, buf, fi); } int fuse_fs_rename(struct fuse_fs *fs, const char *oldpath, const char *newpath, unsigned int flags) { fuse_get_context()->private_data = fs->user_data; - if (fs->op.rename) { - if (fs->debug) - fuse_log(FUSE_LOG_DEBUG, "rename %s %s 0x%x\n", oldpath, newpath, - flags); - - return fs->op.rename(oldpath, newpath, flags); - } else { + if (!fs->op.rename) return -ENOSYS; - } + if (fs->debug) + fuse_log(FUSE_LOG_DEBUG, "rename %s %s 0x%x\n", oldpath, newpath, + flags); + + return fs->op.rename(oldpath, newpath, flags); } int fuse_fs_unlink(struct fuse_fs *fs, const char *path) { fuse_get_context()->private_data = fs->user_data; - if (fs->op.unlink) { - if (fs->debug) - fuse_log(FUSE_LOG_DEBUG, "unlink %s\n", path); - - return fs->op.unlink(path); - } else { + if (!fs->op.unlink) return -ENOSYS; - } + if (fs->debug) + fuse_log(FUSE_LOG_DEBUG, "unlink %s\n", path); + + return fs->op.unlink(path); } int fuse_fs_rmdir(struct fuse_fs *fs, const char *path) { fuse_get_context()->private_data = fs->user_data; - if (fs->op.rmdir) { - if (fs->debug) - fuse_log(FUSE_LOG_DEBUG, "rmdir %s\n", path); - - return fs->op.rmdir(path); - } else { + if (!fs->op.rmdir) return -ENOSYS; - } + if (fs->debug) + fuse_log(FUSE_LOG_DEBUG, "rmdir %s\n", path); + + return fs->op.rmdir(path); } int fuse_fs_symlink(struct fuse_fs *fs, const char *linkname, const char *path) { fuse_get_context()->private_data = fs->user_data; - if (fs->op.symlink) { - if (fs->debug) - fuse_log(FUSE_LOG_DEBUG, "symlink %s %s\n", linkname, path); - - return fs->op.symlink(linkname, path); - } else { + if (!fs->op.symlink) return -ENOSYS; - } + if (fs->debug) + fuse_log(FUSE_LOG_DEBUG, "symlink %s %s\n", linkname, path); + + return fs->op.symlink(linkname, path); } int fuse_fs_link(struct fuse_fs *fs, const char *oldpath, const char *newpath) { fuse_get_context()->private_data = fs->user_data; - if (fs->op.link) { - if (fs->debug) - fuse_log(FUSE_LOG_DEBUG, "link %s %s\n", oldpath, newpath); - - return fs->op.link(oldpath, newpath); - } else { + if (!fs->op.link) return -ENOSYS; - } + if (fs->debug) + fuse_log(FUSE_LOG_DEBUG, "link %s %s\n", oldpath, newpath); + + return fs->op.link(oldpath, newpath); } int fuse_fs_release(struct fuse_fs *fs, const char *path, struct fuse_file_info *fi) { fuse_get_context()->private_data = fs->user_data; - if (fs->op.release) { - if (fs->debug) - fuse_log(FUSE_LOG_DEBUG, "release%s[%llu] flags: 0x%x\n", - fi->flush ? "+flush" : "", - (unsigned long long) fi->fh, fi->flags); - - return fs->op.release(path, fi); - } else { + if (!fs->op.release) return 0; - } + + if (fs->debug) + fuse_log(FUSE_LOG_DEBUG, "release%s[%llu] flags: 0x%x\n", + fi->flush ? "+flush" : "", + (unsigned long long) fi->fh, fi->flags); + + return fs->op.release(path, fi); } int fuse_fs_opendir(struct fuse_fs *fs, const char *path, struct fuse_file_info *fi) { + int err; + fuse_get_context()->private_data = fs->user_data; - if (fs->op.opendir) { - int err; + if (!fs->op.opendir) + return 0; - if (fs->debug) - fuse_log(FUSE_LOG_DEBUG, "opendir flags: 0x%x %s\n", fi->flags, - path); + if (fs->debug) + fuse_log(FUSE_LOG_DEBUG, "opendir flags: 0x%x %s\n", fi->flags, + path); - err = fs->op.opendir(path, fi); + err = fs->op.opendir(path, fi); - if (fs->debug && !err) - fuse_log(FUSE_LOG_DEBUG, " opendir[%llu] flags: 0x%x %s\n", - (unsigned long long) fi->fh, fi->flags, path); + if (fs->debug && !err) + fuse_log(FUSE_LOG_DEBUG, " opendir[%llu] flags: 0x%x %s\n", + (unsigned long long) fi->fh, fi->flags, path); - return err; - } else { - return 0; - } + return err; } int fuse_fs_open(struct fuse_fs *fs, const char *path, struct fuse_file_info *fi) { + int err; + fuse_get_context()->private_data = fs->user_data; - if (fs->op.open) { - int err; + if (!fs->op.open) + return 0; - if (fs->debug) - fuse_log(FUSE_LOG_DEBUG, "open flags: 0x%x %s\n", fi->flags, - path); + if (fs->debug) + fuse_log(FUSE_LOG_DEBUG, "open flags: 0x%x %s\n", fi->flags, + path); - err = fs->op.open(path, fi); + err = fs->op.open(path, fi); - if (fs->debug && !err) - fuse_log(FUSE_LOG_DEBUG, " open[%llu] flags: 0x%x %s\n", - (unsigned long long) fi->fh, fi->flags, path); + if (fs->debug && !err) + fuse_log(FUSE_LOG_DEBUG, " open[%llu] flags: 0x%x %s\n", + (unsigned long long) fi->fh, fi->flags, path); - return err; - } else { - return 0; - } + return err; } static void fuse_free_buf(struct fuse_bufvec *buf) @@ -1750,161 +1737,159 @@ int fuse_fs_read_buf(struct fuse_fs *fs, const char *path, struct fuse_bufvec **bufp, size_t size, off_t off, struct fuse_file_info *fi) { - fuse_get_context()->private_data = fs->user_data; - if (fs->op.read || fs->op.read_buf) { - int res; + int res; - if (fs->debug) - fuse_log(FUSE_LOG_DEBUG, - "read[%llu] %zu bytes from %llu flags: 0x%x\n", - (unsigned long long) fi->fh, - size, (unsigned long long) off, fi->flags); + fuse_get_context()->private_data = fs->user_data; + if (!fs->op.read && !fs->op.read_buf) + return -ENOSYS; - if (fs->op.read_buf) { - res = fs->op.read_buf(path, bufp, size, off, fi); - } else { - struct fuse_bufvec *buf; - void *mem; + if (fs->debug) + fuse_log(FUSE_LOG_DEBUG, + "read[%llu] %zu bytes from %llu flags: 0x%x\n", + (unsigned long long) fi->fh, + size, (unsigned long long) off, fi->flags); - buf = malloc(sizeof(struct fuse_bufvec)); - if (buf == NULL) - return -ENOMEM; + if (fs->op.read_buf) { + res = fs->op.read_buf(path, bufp, size, off, fi); + } else { + struct fuse_bufvec *buf; + void *mem; - mem = malloc(size); - if (mem == NULL) { - free(buf); - return -ENOMEM; - } - *buf = FUSE_BUFVEC_INIT(size); - buf->buf[0].mem = mem; - *bufp = buf; + buf = malloc(sizeof(struct fuse_bufvec)); + if (buf == NULL) + return -ENOMEM; - res = fs->op.read(path, mem, size, off, fi); - if (res >= 0) - buf->buf[0].size = res; + mem = malloc(size); + if (mem == NULL) { + free(buf); + return -ENOMEM; } + *buf = FUSE_BUFVEC_INIT(size); + buf->buf[0].mem = mem; + *bufp = buf; - if (fs->debug && res >= 0) - fuse_log(FUSE_LOG_DEBUG, " read[%llu] %zu bytes from %llu\n", - (unsigned long long) fi->fh, - fuse_buf_size(*bufp), - (unsigned long long) off); - if (res >= 0 && fuse_buf_size(*bufp) > size) - fuse_log(FUSE_LOG_ERR, "fuse: read too many bytes\n"); + res = fs->op.read(path, mem, size, off, fi); + if (res >= 0) + buf->buf[0].size = res; + } - if (res < 0) - return res; + if (fs->debug && res >= 0) + fuse_log(FUSE_LOG_DEBUG, " read[%llu] %zu bytes from %llu\n", + (unsigned long long) fi->fh, + fuse_buf_size(*bufp), + (unsigned long long) off); + if (res >= 0 && fuse_buf_size(*bufp) > size) + fuse_log(FUSE_LOG_ERR, "fuse: read too many bytes\n"); - return 0; - } else { - return -ENOSYS; - } + if (res < 0) + return res; + + return 0; } int fuse_fs_read(struct fuse_fs *fs, const char *path, char *mem, size_t size, off_t off, struct fuse_file_info *fi) { + int res; + fuse_get_context()->private_data = fs->user_data; - if (fs->op.read || fs->op.read_buf) { - int res; + if (!fs->op.read && !fs->op.read_buf) + return -ENOSYS; - if (fs->debug) - fuse_log(FUSE_LOG_DEBUG, - "read[%llu] %zu bytes from %llu flags: 0x%x\n", - (unsigned long long) fi->fh, - size, (unsigned long long) off, fi->flags); + if (fs->debug) + fuse_log(FUSE_LOG_DEBUG, + "read[%llu] %zu bytes from %llu flags: 0x%x\n", + (unsigned long long) fi->fh, + size, (unsigned long long) off, fi->flags); - if (fs->op.read_buf) { - struct fuse_bufvec *buf = NULL; + if (fs->op.read_buf) { + struct fuse_bufvec *buf = NULL; - res = fs->op.read_buf(path, &buf, size, off, fi); - if (res == 0) { - struct fuse_bufvec dst = FUSE_BUFVEC_INIT(size); + res = fs->op.read_buf(path, &buf, size, off, fi); + if (res == 0) { + struct fuse_bufvec dst = FUSE_BUFVEC_INIT(size); - dst.buf[0].mem = mem; - res = fuse_buf_copy(&dst, buf, 0); - } - fuse_free_buf(buf); - } else { - res = fs->op.read(path, mem, size, off, fi); + dst.buf[0].mem = mem; + res = fuse_buf_copy(&dst, buf, 0); } - - if (fs->debug && res >= 0) - fuse_log(FUSE_LOG_DEBUG, " read[%llu] %u bytes from %llu\n", - (unsigned long long) fi->fh, - res, - (unsigned long long) off); - if (res >= 0 && res > (int) size) - fuse_log(FUSE_LOG_ERR, "fuse: read too many bytes\n"); - - return res; + fuse_free_buf(buf); } else { - return -ENOSYS; + res = fs->op.read(path, mem, size, off, fi); } + + if (fs->debug && res >= 0) + fuse_log(FUSE_LOG_DEBUG, " read[%llu] %u bytes from %llu\n", + (unsigned long long) fi->fh, + res, + (unsigned long long) off); + if (res >= 0 && res > (int) size) + fuse_log(FUSE_LOG_ERR, "fuse: read too many bytes\n"); + + return res; } int fuse_fs_write_buf(struct fuse_fs *fs, const char *path, struct fuse_bufvec *buf, off_t off, struct fuse_file_info *fi) { + int res; + size_t size; + fuse_get_context()->private_data = fs->user_data; - if (fs->op.write_buf || fs->op.write) { - int res; - size_t size = fuse_buf_size(buf); + if (!fs->op.write_buf && !fs->op.write) + return -ENOSYS; - assert(buf->idx == 0 && buf->off == 0); - if (fs->debug) - fuse_log(FUSE_LOG_DEBUG, - "write%s[%llu] %zu bytes to %llu flags: 0x%x\n", - fi->writepage ? "page" : "", - (unsigned long long) fi->fh, - size, - (unsigned long long) off, - fi->flags); - - if (fs->op.write_buf) { - res = fs->op.write_buf(path, buf, off, fi); + size = fuse_buf_size(buf); + assert(buf->idx == 0 && buf->off == 0); + if (fs->debug) + fuse_log(FUSE_LOG_DEBUG, + "write%s[%llu] %zu bytes to %llu flags: 0x%x\n", + fi->writepage ? "page" : "", + (unsigned long long) fi->fh, + size, + (unsigned long long) off, + fi->flags); + + if (fs->op.write_buf) { + res = fs->op.write_buf(path, buf, off, fi); + } else { + void *mem = NULL; + struct fuse_buf *flatbuf; + struct fuse_bufvec tmp = FUSE_BUFVEC_INIT(size); + + if (buf->count == 1 && + !(buf->buf[0].flags & FUSE_BUF_IS_FD)) { + flatbuf = &buf->buf[0]; } else { - void *mem = NULL; - struct fuse_buf *flatbuf; - struct fuse_bufvec tmp = FUSE_BUFVEC_INIT(size); + res = -ENOMEM; + mem = malloc(size); + if (mem == NULL) + goto out; - if (buf->count == 1 && - !(buf->buf[0].flags & FUSE_BUF_IS_FD)) { - flatbuf = &buf->buf[0]; - } else { - res = -ENOMEM; - mem = malloc(size); - if (mem == NULL) - goto out; - - tmp.buf[0].mem = mem; - res = fuse_buf_copy(&tmp, buf, 0); - if (res <= 0) - goto out_free; - - tmp.buf[0].size = res; - flatbuf = &tmp.buf[0]; - } + tmp.buf[0].mem = mem; + res = fuse_buf_copy(&tmp, buf, 0); + if (res <= 0) + goto out_free; - res = fs->op.write(path, flatbuf->mem, flatbuf->size, - off, fi); -out_free: - free(mem); + tmp.buf[0].size = res; + flatbuf = &tmp.buf[0]; } -out: - if (fs->debug && res >= 0) - fuse_log(FUSE_LOG_DEBUG, " write%s[%llu] %u bytes to %llu\n", - fi->writepage ? "page" : "", - (unsigned long long) fi->fh, res, - (unsigned long long) off); - if (res > (int) size) - fuse_log(FUSE_LOG_ERR, "fuse: wrote too many bytes\n"); - return res; - } else { - return -ENOSYS; + res = fs->op.write(path, flatbuf->mem, flatbuf->size, + off, fi); +out_free: + free(mem); } +out: + if (fs->debug && res >= 0) + fuse_log(FUSE_LOG_DEBUG, " write%s[%llu] %u bytes to %llu\n", + fi->writepage ? "page" : "", + (unsigned long long) fi->fh, res, + (unsigned long long) off); + if (res > (int) size) + fuse_log(FUSE_LOG_ERR, "fuse: wrote too many bytes\n"); + + return res; } int fuse_fs_write(struct fuse_fs *fs, const char *path, const char *mem, @@ -1921,45 +1906,41 @@ int fuse_fs_fsync(struct fuse_fs *fs, const char *path, int datasync, struct fuse_file_info *fi) { fuse_get_context()->private_data = fs->user_data; - if (fs->op.fsync) { - if (fs->debug) - fuse_log(FUSE_LOG_DEBUG, "fsync[%llu] datasync: %i\n", - (unsigned long long) fi->fh, datasync); - - return fs->op.fsync(path, datasync, fi); - } else { + if (!fs->op.fsync) return -ENOSYS; - } + + if (fs->debug) + fuse_log(FUSE_LOG_DEBUG, "fsync[%llu] datasync: %i\n", + (unsigned long long) fi->fh, datasync); + + return fs->op.fsync(path, datasync, fi); } int fuse_fs_fsyncdir(struct fuse_fs *fs, const char *path, int datasync, struct fuse_file_info *fi) { fuse_get_context()->private_data = fs->user_data; - if (fs->op.fsyncdir) { - if (fs->debug) - fuse_log(FUSE_LOG_DEBUG, "fsyncdir[%llu] datasync: %i\n", - (unsigned long long) fi->fh, datasync); - - return fs->op.fsyncdir(path, datasync, fi); - } else { + if (!fs->op.fsyncdir) return -ENOSYS; - } + + if (fs->debug) + fuse_log(FUSE_LOG_DEBUG, "fsyncdir[%llu] datasync: %i\n", + (unsigned long long) fi->fh, datasync); + + return fs->op.fsyncdir(path, datasync, fi); } int fuse_fs_flush(struct fuse_fs *fs, const char *path, struct fuse_file_info *fi) { fuse_get_context()->private_data = fs->user_data; - if (fs->op.flush) { - if (fs->debug) - fuse_log(FUSE_LOG_DEBUG, "flush[%llu]\n", - (unsigned long long) fi->fh); - - return fs->op.flush(path, fi); - } else { + if (!fs->op.flush) return -ENOSYS; - } + if (fs->debug) + fuse_log(FUSE_LOG_DEBUG, "flush[%llu]\n", + (unsigned long long) fi->fh); + + return fs->op.flush(path, fi); } int fuse_fs_statfs(struct fuse_fs *fs, const char *path, struct statvfs *buf) @@ -1981,15 +1962,14 @@ int fuse_fs_releasedir(struct fuse_fs *fs, const char *path, struct fuse_file_info *fi) { fuse_get_context()->private_data = fs->user_data; - if (fs->op.releasedir) { - if (fs->debug) - fuse_log(FUSE_LOG_DEBUG, "releasedir[%llu] flags: 0x%x\n", - (unsigned long long) fi->fh, fi->flags); + if (!fs->op.releasedir) + return 0; - return fs->op.releasedir(path, fi); - } else { - return 0; - } + if (fs->debug) + fuse_log(FUSE_LOG_DEBUG, "releasedir[%llu] flags: 0x%x\n", + (unsigned long long) fi->fh, fi->flags); + + return fs->op.releasedir(path, fi); } int fuse_fs_readdir(struct fuse_fs *fs, const char *path, void *buf, @@ -1998,273 +1978,247 @@ int fuse_fs_readdir(struct fuse_fs *fs, const char *path, void *buf, enum fuse_readdir_flags flags) { fuse_get_context()->private_data = fs->user_data; - if (fs->op.readdir) { - if (fs->debug) { - fuse_log(FUSE_LOG_DEBUG, "readdir%s[%llu] from %llu\n", - (flags & FUSE_READDIR_PLUS) ? "plus" : "", - (unsigned long long) fi->fh, - (unsigned long long) off); - } - - return fs->op.readdir(path, buf, filler, off, fi, flags); - } else { + if (!fs->op.readdir) return -ENOSYS; + if (fs->debug) { + fuse_log(FUSE_LOG_DEBUG, "readdir%s[%llu] from %llu\n", + (flags & FUSE_READDIR_PLUS) ? "plus" : "", + (unsigned long long) fi->fh, + (unsigned long long) off); } + + return fs->op.readdir(path, buf, filler, off, fi, flags); } int fuse_fs_create(struct fuse_fs *fs, const char *path, mode_t mode, struct fuse_file_info *fi) { + int err; + fuse_get_context()->private_data = fs->user_data; - if (fs->op.create) { - int err; + if (!fs->op.create) + return -ENOSYS; - if (fs->debug) - fuse_log(FUSE_LOG_DEBUG, - "create flags: 0x%x %s 0%o umask=0%03o\n", - fi->flags, path, mode, - fuse_get_context()->umask); + if (fs->debug) + fuse_log(FUSE_LOG_DEBUG, + "create flags: 0x%x %s 0%o umask=0%03o\n", + fi->flags, path, mode, + fuse_get_context()->umask); - err = fs->op.create(path, mode, fi); + err = fs->op.create(path, mode, fi); - if (fs->debug && !err) - fuse_log(FUSE_LOG_DEBUG, " create[%llu] flags: 0x%x %s\n", - (unsigned long long) fi->fh, fi->flags, path); + if (fs->debug && !err) + fuse_log(FUSE_LOG_DEBUG, " create[%llu] flags: 0x%x %s\n", + (unsigned long long) fi->fh, fi->flags, path); - return err; - } else { - return -ENOSYS; - } + return err; } int fuse_fs_lock(struct fuse_fs *fs, const char *path, struct fuse_file_info *fi, int cmd, struct flock *lock) { fuse_get_context()->private_data = fs->user_data; - if (fs->op.lock) { - if (fs->debug) - fuse_log(FUSE_LOG_DEBUG, "lock[%llu] %s %s start: %llu len: %llu pid: %llu\n", - (unsigned long long) fi->fh, - (cmd == F_GETLK ? "F_GETLK" : - (cmd == F_SETLK ? "F_SETLK" : - (cmd == F_SETLKW ? "F_SETLKW" : "???"))), - (lock->l_type == F_RDLCK ? "F_RDLCK" : - (lock->l_type == F_WRLCK ? "F_WRLCK" : - (lock->l_type == F_UNLCK ? "F_UNLCK" : - "???"))), - (unsigned long long) lock->l_start, - (unsigned long long) lock->l_len, - (unsigned long long) lock->l_pid); - - return fs->op.lock(path, fi, cmd, lock); - } else { + if (!fs->op.lock) return -ENOSYS; - } + + if (fs->debug) + fuse_log(FUSE_LOG_DEBUG, "lock[%llu] %s %s start: %llu len: %llu pid: %llu\n", + (unsigned long long) fi->fh, + (cmd == F_GETLK ? "F_GETLK" : + (cmd == F_SETLK ? "F_SETLK" : + (cmd == F_SETLKW ? "F_SETLKW" : "???"))), + (lock->l_type == F_RDLCK ? "F_RDLCK" : + (lock->l_type == F_WRLCK ? "F_WRLCK" : + (lock->l_type == F_UNLCK ? "F_UNLCK" : + "???"))), + (unsigned long long) lock->l_start, + (unsigned long long) lock->l_len, + (unsigned long long) lock->l_pid); + + return fs->op.lock(path, fi, cmd, lock); } int fuse_fs_flock(struct fuse_fs *fs, const char *path, struct fuse_file_info *fi, int op) { fuse_get_context()->private_data = fs->user_data; - if (fs->op.flock) { - if (fs->debug) { - int xop = op & ~LOCK_NB; - - fuse_log(FUSE_LOG_DEBUG, "lock[%llu] %s%s\n", - (unsigned long long) fi->fh, - xop == LOCK_SH ? "LOCK_SH" : - (xop == LOCK_EX ? "LOCK_EX" : - (xop == LOCK_UN ? "LOCK_UN" : "???")), - (op & LOCK_NB) ? "|LOCK_NB" : ""); - } - return fs->op.flock(path, fi, op); - } else { + if (!fs->op.flock) return -ENOSYS; + + if (fs->debug) { + int xop = op & ~LOCK_NB; + + fuse_log(FUSE_LOG_DEBUG, "lock[%llu] %s%s\n", + (unsigned long long) fi->fh, + xop == LOCK_SH ? "LOCK_SH" : + (xop == LOCK_EX ? "LOCK_EX" : + (xop == LOCK_UN ? "LOCK_UN" : "???")), + (op & LOCK_NB) ? "|LOCK_NB" : ""); } + return fs->op.flock(path, fi, op); } int fuse_fs_chown(struct fuse_fs *fs, const char *path, uid_t uid, gid_t gid, struct fuse_file_info *fi) { fuse_get_context()->private_data = fs->user_data; - if (fs->op.chown) { - if (fs->debug) { - char buf[10]; - fuse_log(FUSE_LOG_DEBUG, "chown[%s] %s %lu %lu\n", - file_info_string(fi, buf, sizeof(buf)), - path, (unsigned long) uid, (unsigned long) gid); - } - return fs->op.chown(path, uid, gid, fi); - } else { + if (!fs->op.chown) return -ENOSYS; + if (fs->debug) { + char buf[10]; + + fuse_log(FUSE_LOG_DEBUG, "chown[%s] %s %lu %lu\n", + file_info_string(fi, buf, sizeof(buf)), + path, (unsigned long) uid, (unsigned long) gid); } + return fs->op.chown(path, uid, gid, fi); } int fuse_fs_truncate(struct fuse_fs *fs, const char *path, off_t size, struct fuse_file_info *fi) { fuse_get_context()->private_data = fs->user_data; - if (fs->op.truncate) { - if (fs->debug) { - char buf[10]; - fuse_log(FUSE_LOG_DEBUG, "truncate[%s] %llu\n", - file_info_string(fi, buf, sizeof(buf)), - (unsigned long long) size); - } - return fs->op.truncate(path, size, fi); - } else { + if (!fs->op.truncate) return -ENOSYS; + if (fs->debug) { + char buf[10]; + + fuse_log(FUSE_LOG_DEBUG, "truncate[%s] %llu\n", + file_info_string(fi, buf, sizeof(buf)), + (unsigned long long) size); } + return fs->op.truncate(path, size, fi); } int fuse_fs_utimens(struct fuse_fs *fs, const char *path, const struct timespec tv[2], struct fuse_file_info *fi) { fuse_get_context()->private_data = fs->user_data; - if (fs->op.utimens) { - if (fs->debug) { - char buf[10]; - fuse_log(FUSE_LOG_DEBUG, "utimens[%s] %s %li.%09lu %li.%09lu\n", - file_info_string(fi, buf, sizeof(buf)), - path, tv[0].tv_sec, tv[0].tv_nsec, - tv[1].tv_sec, tv[1].tv_nsec); - } - return fs->op.utimens(path, tv, fi); - } else { + if (!fs->op.utimens) return -ENOSYS; + if (fs->debug) { + char buf[10]; + + fuse_log(FUSE_LOG_DEBUG, "utimens[%s] %s %li.%09lu %li.%09lu\n", + file_info_string(fi, buf, sizeof(buf)), + path, tv[0].tv_sec, tv[0].tv_nsec, + tv[1].tv_sec, tv[1].tv_nsec); } + return fs->op.utimens(path, tv, fi); } int fuse_fs_access(struct fuse_fs *fs, const char *path, int mask) { fuse_get_context()->private_data = fs->user_data; - if (fs->op.access) { - if (fs->debug) - fuse_log(FUSE_LOG_DEBUG, "access %s 0%o\n", path, mask); - - return fs->op.access(path, mask); - } else { + if (!fs->op.access) return -ENOSYS; - } + if (fs->debug) + fuse_log(FUSE_LOG_DEBUG, "access %s 0%o\n", path, mask); + + return fs->op.access(path, mask); } int fuse_fs_readlink(struct fuse_fs *fs, const char *path, char *buf, size_t len) { fuse_get_context()->private_data = fs->user_data; - if (fs->op.readlink) { - if (fs->debug) - fuse_log(FUSE_LOG_DEBUG, "readlink %s %lu\n", path, - (unsigned long) len); - - return fs->op.readlink(path, buf, len); - } else { + if (!fs->op.readlink) return -ENOSYS; - } + if (fs->debug) + fuse_log(FUSE_LOG_DEBUG, "readlink %s %lu\n", path, + (unsigned long) len); + + return fs->op.readlink(path, buf, len); } int fuse_fs_mknod(struct fuse_fs *fs, const char *path, mode_t mode, dev_t rdev) { fuse_get_context()->private_data = fs->user_data; - if (fs->op.mknod) { - if (fs->debug) - fuse_log(FUSE_LOG_DEBUG, "mknod %s 0%o 0x%llx umask=0%03o\n", - path, mode, (unsigned long long) rdev, - fuse_get_context()->umask); - - return fs->op.mknod(path, mode, rdev); - } else { + if (!fs->op.mknod) return -ENOSYS; - } + if (fs->debug) + fuse_log(FUSE_LOG_DEBUG, "mknod %s 0%o 0x%llx umask=0%03o\n", + path, mode, (unsigned long long) rdev, + fuse_get_context()->umask); + + return fs->op.mknod(path, mode, rdev); } int fuse_fs_mkdir(struct fuse_fs *fs, const char *path, mode_t mode) { fuse_get_context()->private_data = fs->user_data; - if (fs->op.mkdir) { - if (fs->debug) - fuse_log(FUSE_LOG_DEBUG, "mkdir %s 0%o umask=0%03o\n", - path, mode, fuse_get_context()->umask); - - return fs->op.mkdir(path, mode); - } else { + if (!fs->op.mkdir) return -ENOSYS; - } + if (fs->debug) + fuse_log(FUSE_LOG_DEBUG, "mkdir %s 0%o umask=0%03o\n", + path, mode, fuse_get_context()->umask); + + return fs->op.mkdir(path, mode); } int fuse_fs_setxattr(struct fuse_fs *fs, const char *path, const char *name, const char *value, size_t size, int flags) { fuse_get_context()->private_data = fs->user_data; - if (fs->op.setxattr) { - if (fs->debug) - fuse_log(FUSE_LOG_DEBUG, "setxattr %s %s %lu 0x%x\n", - path, name, (unsigned long) size, flags); - - return fs->op.setxattr(path, name, value, size, flags); - } else { + if (!fs->op.setxattr) return -ENOSYS; - } + if (fs->debug) + fuse_log(FUSE_LOG_DEBUG, "setxattr %s %s %lu 0x%x\n", + path, name, (unsigned long) size, flags); + + return fs->op.setxattr(path, name, value, size, flags); } int fuse_fs_getxattr(struct fuse_fs *fs, const char *path, const char *name, char *value, size_t size) { fuse_get_context()->private_data = fs->user_data; - if (fs->op.getxattr) { - if (fs->debug) - fuse_log(FUSE_LOG_DEBUG, "getxattr %s %s %lu\n", - path, name, (unsigned long) size); - - return fs->op.getxattr(path, name, value, size); - } else { + if (!fs->op.getxattr) return -ENOSYS; - } + if (fs->debug) + fuse_log(FUSE_LOG_DEBUG, "getxattr %s %s %lu\n", + path, name, (unsigned long) size); + + return fs->op.getxattr(path, name, value, size); } int fuse_fs_listxattr(struct fuse_fs *fs, const char *path, char *list, size_t size) { fuse_get_context()->private_data = fs->user_data; - if (fs->op.listxattr) { - if (fs->debug) - fuse_log(FUSE_LOG_DEBUG, "listxattr %s %lu\n", - path, (unsigned long) size); - - return fs->op.listxattr(path, list, size); - } else { + if (!fs->op.listxattr) return -ENOSYS; - } + if (fs->debug) + fuse_log(FUSE_LOG_DEBUG, "listxattr %s %lu\n", + path, (unsigned long) size); + + return fs->op.listxattr(path, list, size); } int fuse_fs_bmap(struct fuse_fs *fs, const char *path, size_t blocksize, uint64_t *idx) { fuse_get_context()->private_data = fs->user_data; - if (fs->op.bmap) { - if (fs->debug) - fuse_log(FUSE_LOG_DEBUG, "bmap %s blocksize: %lu index: %llu\n", - path, (unsigned long) blocksize, - (unsigned long long) *idx); - - return fs->op.bmap(path, blocksize, idx); - } else { + if (!fs->op.bmap) return -ENOSYS; - } + if (fs->debug) + fuse_log(FUSE_LOG_DEBUG, "bmap %s blocksize: %lu index: %llu\n", + path, (unsigned long) blocksize, + (unsigned long long) *idx); + + return fs->op.bmap(path, blocksize, idx); } int fuse_fs_removexattr(struct fuse_fs *fs, const char *path, const char *name) { fuse_get_context()->private_data = fs->user_data; - if (fs->op.removexattr) { - if (fs->debug) - fuse_log(FUSE_LOG_DEBUG, "removexattr %s %s\n", path, name); - - return fs->op.removexattr(path, name); - } else { + if (!fs->op.removexattr) return -ENOSYS; - } + if (fs->debug) + fuse_log(FUSE_LOG_DEBUG, "removexattr %s %s\n", path, name); + + return fs->op.removexattr(path, name); } int fuse_fs_ioctl(struct fuse_fs *fs, const char *path, unsigned int cmd, @@ -2272,55 +2226,52 @@ int fuse_fs_ioctl(struct fuse_fs *fs, const char *path, unsigned int cmd, void *data) { fuse_get_context()->private_data = fs->user_data; - if (fs->op.ioctl) { - if (fs->debug) - fuse_log(FUSE_LOG_DEBUG, "ioctl[%llu] 0x%x flags: 0x%x\n", - (unsigned long long) fi->fh, cmd, flags); - - return fs->op.ioctl(path, cmd, arg, fi, flags, data); - } else + if (!fs->op.ioctl) return -ENOSYS; + if (fs->debug) + fuse_log(FUSE_LOG_DEBUG, "ioctl[%llu] 0x%x flags: 0x%x\n", + (unsigned long long) fi->fh, cmd, flags); + + return fs->op.ioctl(path, cmd, arg, fi, flags, data); } int fuse_fs_poll(struct fuse_fs *fs, const char *path, struct fuse_file_info *fi, struct fuse_pollhandle *ph, unsigned *reventsp) { - fuse_get_context()->private_data = fs->user_data; - if (fs->op.poll) { - int res; + int res; - if (fs->debug) - fuse_log(FUSE_LOG_DEBUG, "poll[%llu] ph: %p, events 0x%x\n", - (unsigned long long) fi->fh, ph, - fi->poll_events); + fuse_get_context()->private_data = fs->user_data; + if (!fs->op.poll) + return -ENOSYS; + if (fs->debug) + fuse_log(FUSE_LOG_DEBUG, "poll[%llu] ph: %p, events 0x%x\n", + (unsigned long long) fi->fh, ph, + fi->poll_events); - res = fs->op.poll(path, fi, ph, reventsp); + res = fs->op.poll(path, fi, ph, reventsp); - if (fs->debug && !res) - fuse_log(FUSE_LOG_DEBUG, " poll[%llu] revents: 0x%x\n", - (unsigned long long) fi->fh, *reventsp); + if (fs->debug && !res) + fuse_log(FUSE_LOG_DEBUG, " poll[%llu] revents: 0x%x\n", + (unsigned long long) fi->fh, *reventsp); - return res; - } else - return -ENOSYS; + return res; } int fuse_fs_fallocate(struct fuse_fs *fs, const char *path, int mode, off_t offset, off_t length, struct fuse_file_info *fi) { fuse_get_context()->private_data = fs->user_data; - if (fs->op.fallocate) { - if (fs->debug) - fuse_log(FUSE_LOG_DEBUG, "fallocate %s mode %x, offset: %llu, length: %llu\n", - path, - mode, - (unsigned long long) offset, - (unsigned long long) length); - - return fs->op.fallocate(path, mode, offset, length, fi); - } else + if (!fs->op.fallocate) return -ENOSYS; + if (fs->debug) + fuse_log(FUSE_LOG_DEBUG, "fallocate %s mode %x, offset: %llu, length: %llu\n", + path, + mode, + (unsigned long long) offset, + (unsigned long long) length); + + return fs->op.fallocate(path, mode, offset, length, fi); } ssize_t fuse_fs_copy_file_range(struct fuse_fs *fs, const char *path_in, @@ -2330,37 +2281,35 @@ ssize_t fuse_fs_copy_file_range(struct fuse_fs *fs, const char *path_in, size_t len, int flags) { fuse_get_context()->private_data = fs->user_data; - if (fs->op.copy_file_range) { - if (fs->debug) - fuse_log(FUSE_LOG_DEBUG, "copy_file_range from %s:%llu to " - "%s:%llu, length: %llu\n", - path_in, - (unsigned long long) off_in, - path_out, - (unsigned long long) off_out, - (unsigned long long) len); - - return fs->op.copy_file_range(path_in, fi_in, off_in, path_out, - fi_out, off_out, len, flags); - } else + if (!fs->op.copy_file_range) return -ENOSYS; + if (fs->debug) + fuse_log(FUSE_LOG_DEBUG, "copy_file_range from %s:%llu to " + "%s:%llu, length: %llu\n", + path_in, + (unsigned long long) off_in, + path_out, + (unsigned long long) off_out, + (unsigned long long) len); + + return fs->op.copy_file_range(path_in, fi_in, off_in, path_out, + fi_out, off_out, len, flags); } off_t fuse_fs_lseek(struct fuse_fs *fs, const char *path, off_t off, int whence, struct fuse_file_info *fi) { fuse_get_context()->private_data = fs->user_data; - if (fs->op.lseek) { - if (fs->debug) { - char buf[10]; - fuse_log(FUSE_LOG_DEBUG, "lseek[%s] %llu %d\n", - file_info_string(fi, buf, sizeof(buf)), - (unsigned long long) off, whence); - } - return fs->op.lseek(path, off, whence, fi); - } else { + if (!fs->op.lseek) return -ENOSYS; + if (fs->debug) { + char buf[10]; + + fuse_log(FUSE_LOG_DEBUG, "lseek[%s] %llu %d\n", + file_info_string(fi, buf, sizeof(buf)), + (unsigned long long) off, whence); } + return fs->op.lseek(path, off, whence, fi); } static int is_open(struct fuse *f, fuse_ino_t dir, const char *name) @@ -2796,17 +2745,17 @@ int fuse_fs_chmod(struct fuse_fs *fs, const char *path, mode_t mode, struct fuse_file_info *fi) { fuse_get_context()->private_data = fs->user_data; - if (fs->op.chmod) { - if (fs->debug) { - char buf[10]; - fuse_log(FUSE_LOG_DEBUG, "chmod[%s] %s %llo\n", - file_info_string(fi, buf, sizeof(buf)), - path, (unsigned long long) mode); - } - return fs->op.chmod(path, mode, fi); - } - else + if (!fs->op.chmod) return -ENOSYS; + + if (fs->debug) { + char buf[10]; + + fuse_log(FUSE_LOG_DEBUG, "chmod[%s] %s %llo\n", + file_info_string(fi, buf, sizeof(buf)), + path, (unsigned long long) mode); + } + return fs->op.chmod(path, mode, fi); } static void fuse_lib_setattr(fuse_req_t req, fuse_ino_t ino, struct stat *attr, From aa2e891b0d9f2fa82ae1a2d3b61f09cb0d5db480 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 28 Apr 2025 23:54:56 +0000 Subject: [PATCH 116/241] build(deps): bump github/codeql-action from 3.28.15 to 3.28.16 Bumps [github/codeql-action](https://github.com/github/codeql-action) from 3.28.15 to 3.28.16. - [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/45775bd8235c68ba998cffa5171334d58593da47...28deaeda66b76a05916b6923827895f2b14ab387) --- updated-dependencies: - dependency-name: github/codeql-action dependency-version: 3.28.16 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 de23031a7..130ae449b 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@45775bd8235c68ba998cffa5171334d58593da47 # v3.28.15 + uses: github/codeql-action/init@28deaeda66b76a05916b6923827895f2b14ab387 # v3.28.16 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@45775bd8235c68ba998cffa5171334d58593da47 # v3.28.15 + uses: github/codeql-action/analyze@28deaeda66b76a05916b6923827895f2b14ab387 # v3.28.16 with: category: "/language:${{matrix.language}}" From 83e7dce82b11dc077517b066eb639ee00ce1eb77 Mon Sep 17 00:00:00 2001 From: izxl007 <zeng.zheng@zte.com.cn> Date: Tue, 29 Apr 2025 23:54:45 +0800 Subject: [PATCH 117/241] fuse_lowlevel.c: Fix a small spelling mistake Signed-off-by: izxl007 <zeng.zheng@zte.com.cn> --- 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 8c5d060dc..e10b937f0 100644 --- a/lib/fuse_lowlevel.c +++ b/lib/fuse_lowlevel.c @@ -185,7 +185,7 @@ void fuse_free_req(fuse_req_t req) struct fuse_session *se = req->se; /* XXX: for now no support for interrupts with io-uring - * It actually might work alreasdy, though. But then would add + * It actually might work already, though. But then would add * a lock across ring queues. */ if (se->conn.no_interrupt || req->is_uring) { From cd503168287e82e1698fb2fdf6397f1dbb751e75 Mon Sep 17 00:00:00 2001 From: Joanne Koong <joannelkoong@gmail.com> Date: Thu, 23 Jan 2025 14:32:17 -0800 Subject: [PATCH 118/241] Update include/fuse_kernel.h to version 7.43 Sync include/fuse_kernel.h with the most up to date fuse uapi headers in the kernel. Signed-off-by: Joanne Koong <joannelkoong@gmail.com> --- include/fuse_kernel.h | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/include/fuse_kernel.h b/include/fuse_kernel.h index 5e0eb41d9..42db04c0c 100644 --- a/include/fuse_kernel.h +++ b/include/fuse_kernel.h @@ -229,6 +229,9 @@ * - FUSE_URING_IN_OUT_HEADER_SZ * - FUSE_URING_OP_IN_OUT_SZ * - enum fuse_uring_cmd + * + * 7.43 + * - add FUSE_REQUEST_TIMEOUT */ #ifndef _LINUX_FUSE_H @@ -435,6 +438,8 @@ struct fuse_file_lock { * of the request ID indicates resend requests * FUSE_ALLOW_IDMAP: allow creation of idmapped mounts * FUSE_OVER_IO_URING: Indicate that client supports io-uring + * FUSE_REQUEST_TIMEOUT: kernel supports timing out requests. + * init_out.request_timeout contains the timeout (in secs) */ #define FUSE_ASYNC_READ (1 << 0) #define FUSE_POSIX_LOCKS (1 << 1) @@ -477,11 +482,11 @@ struct fuse_file_lock { #define FUSE_PASSTHROUGH (1ULL << 37) #define FUSE_NO_EXPORT_SUPPORT (1ULL << 38) #define FUSE_HAS_RESEND (1ULL << 39) - /* Obsolete alias for FUSE_DIRECT_IO_ALLOW_MMAP */ #define FUSE_DIRECT_IO_RELAX FUSE_DIRECT_IO_ALLOW_MMAP #define FUSE_ALLOW_IDMAP (1ULL << 40) #define FUSE_OVER_IO_URING (1ULL << 41) +#define FUSE_REQUEST_TIMEOUT (1ULL << 42) /** * CUSE INIT request/reply flags @@ -909,7 +914,8 @@ struct fuse_init_out { uint16_t map_alignment; uint32_t flags2; uint32_t max_stack_depth; - uint32_t unused[6]; + uint16_t request_timeout; + uint16_t unused[11]; }; #define CUSE_INIT_INFO_MAX 4096 From 84a27de6a644163eda97b7052e064774ec73f925 Mon Sep 17 00:00:00 2001 From: Joanne Koong <joannelkoong@gmail.com> Date: Wed, 17 Jul 2024 13:42:14 -0700 Subject: [PATCH 119/241] Support request timeouts This adds the libfuse changes needed to support request timeouts. A timeout may be set by the server in its init call. If a request is not completed by the timeout, the connection will be aborted by the kernel. Signed-off-by: Joanne Koong <joannelkoong@gmail.com> --- include/fuse_common.h | 8 +++++++- lib/fuse_lowlevel.c | 5 +++++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/include/fuse_common.h b/include/fuse_common.h index 249e0c94f..0b0ea4cfc 100644 --- a/include/fuse_common.h +++ b/include/fuse_common.h @@ -708,10 +708,16 @@ struct fuse_conn_info { */ uint64_t want_ext; + /** + * Request timeout (in seconds). If the request is not answered by + * this timeout, the connection will be aborted by the kernel. + */ + uint16_t request_timeout; + /** * For future use. */ - uint32_t reserved[16]; + uint16_t reserved[31]; }; fuse_static_assert(sizeof(struct fuse_conn_info) == 128, "Size of struct fuse_conn_info must be 128 bytes"); diff --git a/lib/fuse_lowlevel.c b/lib/fuse_lowlevel.c index e10b937f0..619edfc3e 100644 --- a/lib/fuse_lowlevel.c +++ b/lib/fuse_lowlevel.c @@ -2720,6 +2720,11 @@ _do_init(fuse_req_t req, const fuse_ino_t nodeid, const void *op_in, enable_io_uring = true; } + if ((inargflags & FUSE_REQUEST_TIMEOUT) && se->conn.request_timeout) { + outargflags |= FUSE_REQUEST_TIMEOUT; + outarg.request_timeout = se->conn.request_timeout; + } + if (inargflags & FUSE_INIT_EXT) { outargflags |= FUSE_INIT_EXT; outarg.flags2 = outargflags >> 32; From 7812b66cb7325c22efe699bff2475b519931cea4 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 5 May 2025 23:09:32 +0000 Subject: [PATCH 120/241] build(deps): bump github/codeql-action from 3.28.16 to 3.28.17 Bumps [github/codeql-action](https://github.com/github/codeql-action) from 3.28.16 to 3.28.17. - [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/28deaeda66b76a05916b6923827895f2b14ab387...60168efe1c415ce0f5521ea06d5c2062adbeed1b) --- updated-dependencies: - dependency-name: github/codeql-action dependency-version: 3.28.17 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 130ae449b..85accb651 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@28deaeda66b76a05916b6923827895f2b14ab387 # v3.28.16 + uses: github/codeql-action/init@60168efe1c415ce0f5521ea06d5c2062adbeed1b # v3.28.17 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@28deaeda66b76a05916b6923827895f2b14ab387 # v3.28.16 + uses: github/codeql-action/analyze@60168efe1c415ce0f5521ea06d5c2062adbeed1b # v3.28.17 with: category: "/language:${{matrix.language}}" From 65b388f0d6b414253a5804a9bbd36040a2158045 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 19 May 2025 22:57:16 +0000 Subject: [PATCH 121/241] build(deps): bump github/codeql-action from 3.28.17 to 3.28.18 Bumps [github/codeql-action](https://github.com/github/codeql-action) from 3.28.17 to 3.28.18. - [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/60168efe1c415ce0f5521ea06d5c2062adbeed1b...ff0a06e83cb2de871e5a09832bc6a81e7276941f) --- updated-dependencies: - dependency-name: github/codeql-action dependency-version: 3.28.18 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 85accb651..9f2a97c6f 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@60168efe1c415ce0f5521ea06d5c2062adbeed1b # v3.28.17 + uses: github/codeql-action/init@ff0a06e83cb2de871e5a09832bc6a81e7276941f # v3.28.18 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@60168efe1c415ce0f5521ea06d5c2062adbeed1b # v3.28.17 + uses: github/codeql-action/analyze@ff0a06e83cb2de871e5a09832bc6a81e7276941f # v3.28.18 with: category: "/language:${{matrix.language}}" From eadd6a5454373c7c404463c81be652275da5e07b Mon Sep 17 00:00:00 2001 From: Bernd Schubert <bschubert@ddn.com> Date: Sat, 17 May 2025 23:52:47 +0200 Subject: [PATCH 122/241] 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 7f9c982fb..ccf4aeb61 100644 --- a/lib/fuse.c +++ b/lib/fuse.c @@ -2563,25 +2563,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 23e58ef13..14d99ad9f 100644 --- a/lib/fuse_i.h +++ b/lib/fuse_i.h @@ -268,11 +268,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 619edfc3e..9fb793bd3 100644 --- a/lib/fuse_lowlevel.c +++ b/lib/fuse_lowlevel.c @@ -2605,7 +2605,6 @@ _do_init(fuse_req_t req, const fuse_ino_t nodeid, const void *op_in, 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); @@ -2618,14 +2617,8 @@ _do_init(fuse_req_t req, const fuse_ino_t nodeid, const void *op_in, * 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 baadab0492a495fda98216b351976d2e5d6d0866 Mon Sep 17 00:00:00 2001 From: Bernd Schubert <bschubert@ddn.com> Date: Sun, 18 May 2025 00:24:07 +0200 Subject: [PATCH 123/241] 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> --- include/fuse_common.h | 50 +++++++++++-------- lib/fuse.c | 9 +--- lib/fuse_i.h | 38 +++------------ lib/fuse_lowlevel.c | 78 ++++++++++++++++++++++++++--- lib/fuse_versionscript | 6 +++ lib/helper.c | 4 +- lib/util.c | 8 +++ lib/util.h | 3 ++ test/test_want_conversion.c | 97 ++++++++++++++++++++++--------------- 9 files changed, 187 insertions(+), 106 deletions(-) diff --git a/include/fuse_common.h b/include/fuse_common.h index 0b0ea4cfc..054c61874 100644 --- a/include/fuse_common.h +++ b/include/fuse_common.h @@ -1107,28 +1107,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 ccf4aeb61..b116661df 100644 --- a/lib/fuse.c +++ b/lib/fuse.c @@ -2560,15 +2560,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 14d99ad9f..80ec80315 100644 --- a/lib/fuse_i.h +++ b/lib/fuse_i.h @@ -106,6 +106,13 @@ struct fuse_session { /* io_uring */ struct fuse_session_uring uring; + + /* + * 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 { @@ -250,36 +257,5 @@ 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; -} #endif /* LIB_FUSE_I_H_*/ diff --git a/lib/fuse_lowlevel.c b/lib/fuse_lowlevel.c index 9fb793bd3..c2f3ccb38 100644 --- a/lib/fuse_lowlevel.c +++ b/lib/fuse_lowlevel.c @@ -2435,6 +2435,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"))) void @@ -2603,12 +2674,8 @@ _do_init(fuse_req_t req, const fuse_ino_t nodeid, const void *op_in, 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); @@ -2617,8 +2684,7 @@ _do_init(fuse_req_t req, const fuse_ino_t nodeid, const void *op_in, * 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 22c59e1af..ab57d7c08 100644 --- a/lib/fuse_versionscript +++ b/lib/fuse_versionscript @@ -205,6 +205,12 @@ FUSE_3.17 { FUSE_3.18 { global: fuse_req_is_uring; + 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: diff --git a/lib/helper.c b/lib/helper.c index 5811c53d3..ced70a224 100644 --- a/lib/helper.c +++ b/lib/helper.c @@ -430,13 +430,13 @@ void fuse_apply_conn_info_opts(struct fuse_conn_info_opts *opts, #define LL_ENABLE(cond, cap) \ do { \ if (cond) \ - conn->want_ext |= (cap); \ + fuse_set_feature_flag(conn, cap); \ } while (0) #define LL_DISABLE(cond, cap) \ do { \ if (cond) \ - conn->want_ext &= ~(cap); \ + fuse_unset_feature_flag(conn, cap); \ } while (0) LL_ENABLE(opts->splice_read, FUSE_CAP_SPLICE_READ); diff --git a/lib/util.c b/lib/util.c index 00f1e1adc..7583d47f7 100644 --- a/lib/util.c +++ b/lib/util.c @@ -10,7 +10,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) { @@ -44,3 +51,4 @@ void fuse_set_thread_name(unsigned long tid, const char *name) (void)name; #endif } + diff --git a/lib/util.h b/lib/util.h index 96b59d3af..d91a5e91f 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); void fuse_set_thread_name(unsigned long tid, const char *name); 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 cea9b33acc13e7683e3646fca04953d14e3211b7 Mon Sep 17 00:00:00 2001 From: Luis Henriques <luis@igalia.com> Date: Thu, 22 May 2025 15:55:27 +0100 Subject: [PATCH 124/241] Fix build in musl libc Function fuse_set_thread_name() assumes that pthread_t is an unsigned long and fails to compile in musl libc with the following: ../lib/util.c: In function 'fuse_set_thread_name': ../lib/util.c:48:28: error: passing argument 1 of 'pthread_setname_np' makes \ pointer from integer without a cast [-Wint-conversion] Fix fuse_set_thread_name() by dropping the 'tid' parameter, as it is always set to pthread_self(). Signed-off-by: Luis Henriques <luis@igalia.com> --- lib/fuse.c | 2 +- lib/fuse_loop_mt.c | 2 +- lib/fuse_uring.c | 2 +- lib/util.c | 5 ++--- lib/util.h | 2 +- 5 files changed, 6 insertions(+), 7 deletions(-) diff --git a/lib/fuse.c b/lib/fuse.c index b116661df..1f01351c5 100644 --- a/lib/fuse.c +++ b/lib/fuse.c @@ -4840,7 +4840,7 @@ static void *fuse_prune_nodes(void *fuse) struct fuse *f = fuse; int sleep_time; - fuse_set_thread_name(pthread_self(), "fuse_prune_nodes"); + fuse_set_thread_name("fuse_prune_nodes"); while(1) { sleep_time = fuse_clean_cache(f); diff --git a/lib/fuse_loop_mt.c b/lib/fuse_loop_mt.c index 046256c20..e75580cf1 100644 --- a/lib/fuse_loop_mt.c +++ b/lib/fuse_loop_mt.c @@ -132,7 +132,7 @@ static void *fuse_do_work(void *data) struct fuse_mt *mt = w->mt; struct fuse_session *se = mt->se; - fuse_set_thread_name(pthread_self(), "fuse_worker"); + fuse_set_thread_name("fuse_worker"); while (!fuse_session_exited(se)) { int isforget = 0; diff --git a/lib/fuse_uring.c b/lib/fuse_uring.c index 62c5a4d78..fb5cd8f3f 100644 --- a/lib/fuse_uring.c +++ b/lib/fuse_uring.c @@ -679,7 +679,7 @@ static void *fuse_uring_thread(void *arg) snprintf(thread_name, 16, "fuse-ring-%d", queue->qid); thread_name[15] = '\0'; - fuse_set_thread_name(pthread_self(), thread_name); + fuse_set_thread_name(thread_name); fuse_uring_set_thread_core(queue->qid); diff --git a/lib/util.c b/lib/util.c index 7583d47f7..2914f1cfc 100644 --- a/lib/util.c +++ b/lib/util.c @@ -42,12 +42,11 @@ int libfuse_strtol(const char *str, long *res) return 0; } -void fuse_set_thread_name(unsigned long tid, const char *name) +void fuse_set_thread_name(const char *name) { #ifdef HAVE_PTHREAD_SETNAME_NP - pthread_setname_np(tid, name); + pthread_setname_np(pthread_self(), name); #else - (void)tid; (void)name; #endif } diff --git a/lib/util.h b/lib/util.h index d91a5e91f..aae40e21d 100644 --- a/lib/util.h +++ b/lib/util.h @@ -12,7 +12,7 @@ struct fuse_conn_info; int libfuse_strtol(const char *str, long *res); -void fuse_set_thread_name(unsigned long tid, const char *name); +void fuse_set_thread_name(const char *name); /** * Return the low bits of a number From fc71597c8dba7ca8da474cbae3509552e30cbd46 Mon Sep 17 00:00:00 2001 From: izxl007 <zeng.zheng@zte.com.cn> Date: Wed, 28 May 2025 21:54:15 +0800 Subject: [PATCH 125/241] fuse_lowlevel.c: Remove duplicate descriptions of auto_unmount Signed-off-by: izxl007 <zeng.zheng@zte.com.cn> --- lib/fuse_lowlevel.c | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/fuse_lowlevel.c b/lib/fuse_lowlevel.c index c2f3ccb38..556878630 100644 --- a/lib/fuse_lowlevel.c +++ b/lib/fuse_lowlevel.c @@ -3637,7 +3637,6 @@ void fuse_lowlevel_help(void) " -o allow_other allow access by all users\n" " -o allow_root allow access by root\n" " -o auto_unmount auto unmount on process termination\n" -" -o auto_unmount auto unmount on process termination\n" " -o io_uring enable io-uring\n" " -o io_uring_q_depth=<n> io-uring queue depth\n" ); From b773020464641d3e9cec5ad5fa35e7153e54e118 Mon Sep 17 00:00:00 2001 From: izxl007 <zeng.zheng@zte.com.cn> Date: Tue, 3 Jun 2025 22:04:52 +0800 Subject: [PATCH 126/241] Update how to use the io_uring and io_uring_q_depth mount options MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This patch updates how to use the io_uring and io_uring_q_depth mount options. There are two reasons for this: 1. It is recommended for end-users to use a format like “-o io_uring" (with a space) in README.fuse-io-uring. This is because, in the passthrough_hp example, cxxopts does not support the "-oio_uring" format (without a space), and using it would result in a syntax error. 2. Remove redundant "--uring" and "--uring-q-depth=" from passthrough_hp example. Signed-off-by: izxl007 <zeng.zheng@zte.com.cn> --- doc/README.fuse-io-uring | 8 ++++---- example/passthrough_hp.cc | 23 ++--------------------- 2 files changed, 6 insertions(+), 25 deletions(-) diff --git a/doc/README.fuse-io-uring b/doc/README.fuse-io-uring index 7207c6e74..51348903d 100644 --- a/doc/README.fuse-io-uring +++ b/doc/README.fuse-io-uring @@ -6,7 +6,7 @@ echo 1 > /sys/module/fuse/parameters/enable_uring Additionally, FUSE_CAP_OVER_IO_URING needs to be set and se->uring.enable has to be true. The latter can be -achieved with the libfuse option '-oio_uring'. +achieved with the libfuse option '-o io_uring'. Default queue-depth is 8 and can be changed with the parameter '-oio_uring_q_depth'. @@ -22,11 +22,11 @@ Benefits: Usage: To enable io_uring support when mounting a FUSE filesystem: 1. Enable kernel support: echo 1 > /sys/module/fuse/parameters/enable_uring -2. Mount with io_uring option: -oio_uring -3. Optionally adjust queue depth: -oio_uring_q_depth=<depth> +2. Mount with io_uring option: -o io_uring +3. Optionally adjust queue depth: -o io_uring_q_depth=<depth> Example: -./my_fuse_fs /source /mountpoint -oio_uring -oio_uring_q_depth=16 +./my_fuse_fs /source /mountpoint -o io_uring -o io_uring_q_depth=16 Requirements: - Linux kernel with io_uring and FUSE io_uring support enabled diff --git a/example/passthrough_hp.cc b/example/passthrough_hp.cc index b88c2c5dc..f1fc92795 100644 --- a/example/passthrough_hp.cc +++ b/example/passthrough_hp.cc @@ -77,9 +77,6 @@ using namespace std; #define SFS_DEFAULT_THREADS "-1" // take libfuse value as default #define SFS_DEFAULT_CLONE_FD "0" -#define SFS_DEFAULT_URING "1" -#define SFS_DEFAULT_URING_Q_DEPTH "0" -#define SFS_DEFAULT_URING_ARGLEN "0" /* We are re-using pointers to our `struct sfs_inode` and `struct sfs_dirp` elements as inodes and file handles. This means that we @@ -158,10 +155,6 @@ struct Fs { bool nocache; size_t num_threads; bool clone_fd; - struct { - bool enable; - int queue_depth; - } uring; std::string fuse_mount_options; bool direct_io; @@ -1410,10 +1403,8 @@ static cxxopts::ParseResult parse_options(int argc, char **argv) { ("num-threads", "Number of libfuse worker threads", cxxopts::value<int>()->default_value(SFS_DEFAULT_THREADS)) ("clone-fd", "use separate fuse device fd for each thread") - ("direct-io", "enable fuse kernel internal direct-io") - ("uring", "use uring communication") - ("uring-q-depth", "io-uring queue depth", - cxxopts::value<int>()->default_value(SFS_DEFAULT_URING_Q_DEPTH)); + ("direct-io", "enable fuse kernel internal direct-io"); + // FIXME: Find a better way to limit the try clause to just // opt_parser.parse() (cf. https://github.com/jarro2783/cxxopts/issues/146) auto options = parse_wrapper(opt_parser, argc, argv); @@ -1446,9 +1437,6 @@ static cxxopts::ParseResult parse_options(int argc, char **argv) { fs.clone_fd = options.count("clone-fd"); fs.direct_io = options.count("direct-io"); - fs.uring.enable = options.count("uring"); - fs.uring.queue_depth = options["uring-q-depth"].as<int>(); - char* resolved_path = realpath(argv[1], NULL); if (resolved_path == NULL) warn("WARNING: realpath() failed with"); @@ -1534,13 +1522,6 @@ int main(int argc, char *argv[]) { (fs.debug_fuse && fuse_opt_add_arg(&args, "-odebug"))) errx(3, "ERROR: Out of memory adding arguments"); - if (fs.uring.enable) { - if (fuse_opt_add_arg(&args, "-oio_uring") || - fuse_opt_add_arg(&args, ("-oio_uring_q_depth=" + - std::to_string(fs.uring.queue_depth)).c_str())) - errx(3, "ERROR: Out of memory adding io-uring arguments"); - } - ret = -1; fuse_lowlevel_ops sfs_oper {}; assign_operations(sfs_oper); From 22f6062f07fbffa9eeb6d491e6e5e3b19395fd46 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 9 Jun 2025 22:51:35 +0000 Subject: [PATCH 127/241] build(deps): bump github/codeql-action from 3.28.18 to 3.28.19 Bumps [github/codeql-action](https://github.com/github/codeql-action) from 3.28.18 to 3.28.19. - [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/ff0a06e83cb2de871e5a09832bc6a81e7276941f...fca7ace96b7d713c7035871441bd52efbe39e27e) --- updated-dependencies: - dependency-name: github/codeql-action dependency-version: 3.28.19 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 9f2a97c6f..714c20ad8 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@ff0a06e83cb2de871e5a09832bc6a81e7276941f # v3.28.18 + uses: github/codeql-action/init@fca7ace96b7d713c7035871441bd52efbe39e27e # v3.28.19 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@ff0a06e83cb2de871e5a09832bc6a81e7276941f # v3.28.18 + uses: github/codeql-action/analyze@fca7ace96b7d713c7035871441bd52efbe39e27e # v3.28.19 with: category: "/language:${{matrix.language}}" From 05cbdf637483157a8455f19ec37f62c22ad24e08 Mon Sep 17 00:00:00 2001 From: Gleb Popov <6yearold@gmail.com> Date: Thu, 5 Jun 2025 17:03:25 +0300 Subject: [PATCH 128/241] mount_bsd.c: Fix race between actual mounting and returning to the caller Signed-off-by: Gleb Popov <6yearold@gmail.com> --- lib/mount_bsd.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/mount_bsd.c b/lib/mount_bsd.c index bd95a76d3..28a9ae569 100644 --- a/lib/mount_bsd.c +++ b/lib/mount_bsd.c @@ -187,7 +187,7 @@ static int fuse_mount_core(const char *mountpoint, const char *opts) if (pid == 0) { const char *argv[32]; int a = 0; - int ret = -1; + int ret = -1; if (! fdnam) { @@ -214,6 +214,7 @@ static int fuse_mount_core(const char *mountpoint, const char *opts) _exit(EXIT_FAILURE); } + waitpid(pid, &status, 0); _exit(EXIT_SUCCESS); } From 3d8aba629504d8a17bcd93fc70dc760791e7786a Mon Sep 17 00:00:00 2001 From: Georgi Valkov <gvalkov@gmail.com> Date: Thu, 12 Jun 2025 07:58:26 +0300 Subject: [PATCH 129/241] gitignore: add .DS_Store .DS_Store files are created all over the place on macOS Signed-off-by: Georgi Valkov <gvalkov@gmail.com> --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 97045f64b..30b16a236 100644 --- a/.gitignore +++ b/.gitignore @@ -13,6 +13,7 @@ \#*# *.orig *~ +.DS_Store Makefile.in Makefile *.m4 From 6787608e5655b3d833a541a9f0d5a3159ae51f0e Mon Sep 17 00:00:00 2001 From: Georgi Valkov <gvalkov@gmail.com> Date: Thu, 12 Jun 2025 07:36:14 +0300 Subject: [PATCH 130/241] 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> --- lib/fuse_signals.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/fuse_signals.c b/lib/fuse_signals.c index 3848aec65..53129f0ca 100644 --- a/lib/fuse_signals.c +++ b/lib/fuse_signals.c @@ -32,8 +32,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 324f1c7f4aa3f5ad0f88a56794fa3db9e55da430 Mon Sep 17 00:00:00 2001 From: Bernd Schubert <bschubert@ddn.com> Date: Wed, 4 Jun 2025 17:36:02 +0200 Subject: [PATCH 131/241] tests: Add an exception for output "error" The sigterm test triggers debug output like dev unique: 6, opcode: STATFS (17), nodeid: 1, insize: 40, pid: 1808 unique: 6, success, outsize: 96 dev unique: 8, opcode: GETATTR (3), nodeid: 1, insize: 56, pid: 1808 exit_handler called with sig 15 fuse: session exited, terminating workers unique: 8, error: -2 (No such file or directory), outsize: 16 Which then triggers test failures, because the test was acting on the word 'error'. Signed-off-by: Bernd Schubert <bschubert@ddn.com> --- test/conftest.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/test/conftest.py b/test/conftest.py index f52818928..291c9199b 100644 --- a/test/conftest.py +++ b/test/conftest.py @@ -77,8 +77,11 @@ def _check(self): cp = re.compile(pattern, re.IGNORECASE | re.MULTILINE) hit = cp.search(buf) if hit: - raise AssertionError('Suspicious output to stderr (matched "%s")' - % hit.group(0)) + # Skip FUSE error messages in the format "unique: X, error: -Y (...), outsize: Z" + # These are no errors, but just fuse debug messages with the return code + if re.search(r'unique: \d+, error: -\d+ \(.*\), outsize: \d+', hit.group(0)): + continue + raise AssertionError(f'Suspicious output to stderr (matched "{hit.group(0)}")') @pytest.fixture() def output_checker(request): From e64a6164ff71911e4c3bd492ceb0532705c1e733 Mon Sep 17 00:00:00 2001 From: izxl007 <zeng.zheng@zte.com.cn> Date: Mon, 9 Jun 2025 23:19:14 +0800 Subject: [PATCH 132/241] help: Remove duplicate io_uring options from fuse_cmdline_help The io_uring options are currently shown in both fuse_cmdline_help() and fuse_lowlevel_help(), which creates unnecessary duplication. Since io_uring is a low-level I/O feature, it makes more sense to only show these options in fuse_lowlevel_help(). This change: - Removes io_uring options from fuse_cmdline_help() - Keeps them in fuse_lowlevel_help() where they belong - Removes the FIXME comment that is no longer needed This is purely a documentation improvement and does not affect any functionality. Users will still see all available options when using --help, just organized in a more logical way. Signed-off-by: izxl007 <zeng.zheng@zte.com.cn> --- lib/helper.c | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/lib/helper.c b/lib/helper.c index ced70a224..aceff9fd5 100644 --- a/lib/helper.c +++ b/lib/helper.c @@ -139,11 +139,7 @@ void fuse_cmdline_help(void) " -o max_idle_threads the maximum number of idle worker threads\n" " allowed (default: -1)\n" " -o max_threads the maximum number of worker threads\n" - " allowed (default: 10)\n" - /* fuse_ll_opts in fuse_lowlevel.c, FIXME, call into that file */ - " -o io_uring enable io-uring\n" - " -o io_uring_q_depth=<n> io-uring queue depth\n" -); + " allowed (default: 10)\n"); } static int fuse_helper_opt_proc(void *data, const char *arg, int key, From 5724a99068d0a3ebc32c123532942bc8efe11cf1 Mon Sep 17 00:00:00 2001 From: Georgi Valkov <gvalkov@gmail.com> Date: Fri, 13 Jun 2025 10:05:29 +0300 Subject: [PATCH 133/241] gitignore: add *.patch When working with patches, git shows them as uncommitted changes. Ignore *.patch to keep the list of changes tidy. Signed-off-by: Georgi Valkov <gvalkov@gmail.com> --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 30b16a236..0412bc73f 100644 --- a/.gitignore +++ b/.gitignore @@ -12,6 +12,7 @@ *.gz \#*# *.orig +*.patch *~ .DS_Store Makefile.in From 77e7df4516a0f0484cd6b013ecfd238534818db3 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 16 Jun 2025 23:55:51 +0000 Subject: [PATCH 134/241] build(deps): bump github/codeql-action from 3.28.19 to 3.29.0 Bumps [github/codeql-action](https://github.com/github/codeql-action) from 3.28.19 to 3.29.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/fca7ace96b7d713c7035871441bd52efbe39e27e...ce28f5bb42b7a9f2c824e633a3f6ee835bab6858) --- updated-dependencies: - dependency-name: github/codeql-action dependency-version: 3.29.0 dependency-type: direct:production update-type: version-update:semver-minor ... 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 714c20ad8..3c37133b0 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@fca7ace96b7d713c7035871441bd52efbe39e27e # v3.28.19 + uses: github/codeql-action/init@ce28f5bb42b7a9f2c824e633a3f6ee835bab6858 # v3.29.0 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@fca7ace96b7d713c7035871441bd52efbe39e27e # v3.28.19 + uses: github/codeql-action/analyze@ce28f5bb42b7a9f2c824e633a3f6ee835bab6858 # v3.29.0 with: category: "/language:${{matrix.language}}" From 00dd846588e3d6a235e3f430f4063b43686adbc3 Mon Sep 17 00:00:00 2001 From: Bernd Schubert <bschubert@ddn.com> Date: Tue, 17 Jun 2025 17:04:02 +0200 Subject: [PATCH 135/241] Reformat passthrough_hp with clang-format Not ideal for git history, but the different formatting within the project is really disturbing. Signed-off-by: Bernd Schubert <bschubert@ddn.com> --- example/passthrough_hp.cc | 2560 +++++++++++++++++++------------------ 1 file changed, 1300 insertions(+), 1260 deletions(-) diff --git a/example/passthrough_hp.cc b/example/passthrough_hp.cc index f1fc92795..dd3a45ae9 100644 --- a/example/passthrough_hp.cc +++ b/example/passthrough_hp.cc @@ -82,15 +82,14 @@ using namespace std; sfs_dirp` elements as inodes and file handles. This means that we must be able to store pointer a pointer in both a fuse_ino_t variable and a uint64_t variable (used for file handles). */ -static_assert(sizeof(fuse_ino_t) >= sizeof(void*), - "void* must fit into fuse_ino_t"); +static_assert(sizeof(fuse_ino_t) >= sizeof(void *), + "void* must fit into fuse_ino_t"); static_assert(sizeof(fuse_ino_t) >= sizeof(uint64_t), - "fuse_ino_t must be at least 64 bits"); - + "fuse_ino_t must be at least 64 bits"); /* Forward declarations */ struct Inode; -static Inode& get_inode(fuse_ino_t ino); +static Inode &get_inode(fuse_ino_t ino); static void forget_one(fuse_ino_t ino, uint64_t n); // Uniquely identifies a file in the source directory tree. This could @@ -103,1476 +102,1517 @@ static void forget_one(fuse_ino_t ino, uint64_t n); typedef std::pair<ino_t, dev_t> SrcId; // Define a hash function for SrcId -namespace std { - template<> - struct hash<SrcId> { - size_t operator()(const SrcId& id) const { - return hash<ino_t>{}(id.first) ^ hash<dev_t>{}(id.second); - } - }; +namespace std +{ +template <> struct hash<SrcId> { + size_t operator()(const SrcId &id) const + { + return hash<ino_t>{}(id.first) ^ hash<dev_t>{}(id.second); + } +}; } // Maps files in the source directory tree to inodes typedef std::unordered_map<SrcId, Inode> InodeMap; struct Inode { - int fd {-1}; - dev_t src_dev {0}; - ino_t src_ino {0}; - int generation {0}; - int backing_id {0}; - uint64_t nopen {0}; - uint64_t nlookup {0}; - std::mutex m; - - // Delete copy constructor and assignments. We could implement - // move if we need it. - Inode() = default; - Inode(const Inode&) = delete; - Inode(Inode&& inode) = delete; - Inode& operator=(Inode&& inode) = delete; - Inode& operator=(const Inode&) = delete; - - ~Inode() { - if(fd > 0) - close(fd); - } + int fd{ -1 }; + dev_t src_dev{ 0 }; + ino_t src_ino{ 0 }; + int generation{ 0 }; + int backing_id{ 0 }; + uint64_t nopen{ 0 }; + uint64_t nlookup{ 0 }; + std::mutex m; + + // Delete copy constructor and assignments. We could implement + // move if we need it. + Inode() = default; + Inode(const Inode &) = delete; + Inode(Inode &&inode) = delete; + Inode &operator=(Inode &&inode) = delete; + Inode &operator=(const Inode &) = delete; + + ~Inode() + { + if (fd > 0) + close(fd); + } }; struct Fs { - // Must be acquired *after* any Inode.m locks. - std::mutex mutex; - InodeMap inodes; // protected by mutex - Inode root; - double timeout; - bool debug; - bool debug_fuse; - bool foreground; - std::string source; - size_t blocksize; - dev_t src_dev; - bool nosplice; - bool nocache; - size_t num_threads; - bool clone_fd; - - std::string fuse_mount_options; - bool direct_io; - bool passthrough; + // Must be acquired *after* any Inode.m locks. + std::mutex mutex; + InodeMap inodes; // protected by mutex + Inode root; + double timeout; + bool debug; + bool debug_fuse; + bool foreground; + std::string source; + size_t blocksize; + dev_t src_dev; + bool nosplice; + bool nocache; + size_t num_threads; + bool clone_fd; + + std::string fuse_mount_options; + bool direct_io; + bool passthrough; }; static Fs fs{}; +#define FUSE_BUF_COPY_FLAGS \ + (fs.nosplice ? FUSE_BUF_NO_SPLICE : \ + static_cast<fuse_buf_copy_flags>(FUSE_BUF_SPLICE_MOVE)) -#define FUSE_BUF_COPY_FLAGS \ - (fs.nosplice ? \ - FUSE_BUF_NO_SPLICE : \ - static_cast<fuse_buf_copy_flags>(FUSE_BUF_SPLICE_MOVE)) - - -static Inode& get_inode(fuse_ino_t ino) { - if (ino == FUSE_ROOT_ID) - return fs.root; - - Inode* inode = reinterpret_cast<Inode*>(ino); - if(inode->fd == -1) { - cerr << "INTERNAL ERROR: Unknown inode " << ino << endl; - abort(); - } - return *inode; +static Inode &get_inode(fuse_ino_t ino) +{ + if (ino == FUSE_ROOT_ID) + return fs.root; + + Inode *inode = reinterpret_cast<Inode *>(ino); + if (inode->fd == -1) { + cerr << "INTERNAL ERROR: Unknown inode " << ino << endl; + abort(); + } + return *inode; } - -static int get_fs_fd(fuse_ino_t ino) { - int fd = get_inode(ino).fd; - return fd; +static int get_fs_fd(fuse_ino_t ino) +{ + int fd = get_inode(ino).fd; + return fd; } - -static void sfs_init(void *userdata, fuse_conn_info *conn) { - (void)userdata; - - if (!fuse_set_feature_flag(conn, FUSE_CAP_PASSTHROUGH)) - fs.passthrough = false; - - /* Passthrough and writeback cache are conflicting modes */ - if (fs.timeout && !fs.passthrough) - fuse_set_feature_flag(conn, FUSE_CAP_WRITEBACK_CACHE); - - fuse_set_feature_flag(conn, FUSE_CAP_FLOCK_LOCKS); - - if (fs.nosplice) { - // FUSE_CAP_SPLICE_READ is enabled in libfuse3 by default, - // see do_init() in in fuse_lowlevel.c - // 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 */ - fuse_set_feature_flag(conn, FUSE_CAP_DIRECT_IO_ALLOW_MMAP); - - /* Disable NFS export support, which also disabled name_to_handle_at. - * Goal is to make xfstests that test name_to_handle_at to fail with - * the right error code (EOPNOTSUPP) than to open_by_handle_at to fail with - * ESTALE and let those test fail. - * Perfect NFS export support is not possible with this FUSE filesystem needs - * more kernel work, in order to passthrough nfs handle encode/decode to - * fuse-server/daemon. - */ - fuse_set_feature_flag(conn, FUSE_CAP_NO_EXPORT_SUPPORT); - - /* 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; +static void sfs_init(void *userdata, fuse_conn_info *conn) +{ + (void)userdata; + + if (!fuse_set_feature_flag(conn, FUSE_CAP_PASSTHROUGH)) + fs.passthrough = false; + + /* Passthrough and writeback cache are conflicting modes */ + if (fs.timeout && !fs.passthrough) + fuse_set_feature_flag(conn, FUSE_CAP_WRITEBACK_CACHE); + + fuse_set_feature_flag(conn, FUSE_CAP_FLOCK_LOCKS); + + if (fs.nosplice) { + // FUSE_CAP_SPLICE_READ is enabled in libfuse3 by default, + // see do_init() in fuse_lowlevel.c + // 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 */ + fuse_set_feature_flag(conn, FUSE_CAP_DIRECT_IO_ALLOW_MMAP); + + /* Disable NFS export support, which also disabled name_to_handle_at. + * Goal is to make xfstests that test name_to_handle_at to fail with + * the right error code (EOPNOTSUPP) than to open_by_handle_at to fail with + * ESTALE and let those test fail. + * Perfect NFS export support is not possible with this FUSE filesystem needs + * more kernel work, in order to passthrough nfs handle encode/decode to + * fuse-server/daemon. + */ + fuse_set_feature_flag(conn, FUSE_CAP_NO_EXPORT_SUPPORT); + + /* 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; } - static void sfs_getattr(fuse_req_t req, fuse_ino_t ino, fuse_file_info *fi) { - struct stat attr; - int fd = fi ? fi->fh : get_inode(ino).fd; - - auto res = fstatat(fd, "", &attr, AT_EMPTY_PATH | AT_SYMLINK_NOFOLLOW); - if (res == -1) { - fuse_reply_err(req, errno); - return; - } - fuse_reply_attr(req, &attr, fs.timeout); + struct stat attr; + int fd = fi ? fi->fh : get_inode(ino).fd; + + auto res = fstatat(fd, "", &attr, AT_EMPTY_PATH | AT_SYMLINK_NOFOLLOW); + if (res == -1) { + fuse_reply_err(req, errno); + return; + } + fuse_reply_attr(req, &attr, fs.timeout); } - static void do_setattr(fuse_req_t req, fuse_ino_t ino, struct stat *attr, - int valid, struct fuse_file_info* fi) { - Inode& inode = get_inode(ino); - int ifd = inode.fd; - int res; - - if (valid & FUSE_SET_ATTR_MODE) { - if (fi) { - res = fchmod(fi->fh, attr->st_mode); - } else { - char procname[64]; - sprintf(procname, "/proc/self/fd/%i", ifd); - res = chmod(procname, attr->st_mode); - } - if (res == -1) - goto out_err; - } - if (valid & (FUSE_SET_ATTR_UID | FUSE_SET_ATTR_GID)) { - uid_t uid = (valid & FUSE_SET_ATTR_UID) ? attr->st_uid : static_cast<uid_t>(-1); - gid_t gid = (valid & FUSE_SET_ATTR_GID) ? attr->st_gid : static_cast<gid_t>(-1); - - res = fchownat(ifd, "", uid, gid, AT_EMPTY_PATH | AT_SYMLINK_NOFOLLOW); - if (res == -1) - goto out_err; - } - if (valid & FUSE_SET_ATTR_SIZE) { - if (fi) { - res = ftruncate(fi->fh, attr->st_size); - } else { - char procname[64]; - sprintf(procname, "/proc/self/fd/%i", ifd); - res = truncate(procname, attr->st_size); - } - if (res == -1) - goto out_err; - } - if (valid & (FUSE_SET_ATTR_ATIME | FUSE_SET_ATTR_MTIME)) { - struct timespec tv[2]; - - tv[0].tv_sec = 0; - tv[1].tv_sec = 0; - tv[0].tv_nsec = UTIME_OMIT; - tv[1].tv_nsec = UTIME_OMIT; - - if (valid & FUSE_SET_ATTR_ATIME_NOW) - tv[0].tv_nsec = UTIME_NOW; - else if (valid & FUSE_SET_ATTR_ATIME) - tv[0] = attr->st_atim; - - if (valid & FUSE_SET_ATTR_MTIME_NOW) - tv[1].tv_nsec = UTIME_NOW; - else if (valid & FUSE_SET_ATTR_MTIME) - tv[1] = attr->st_mtim; - - if (fi) - res = futimens(fi->fh, tv); - else { + int valid, struct fuse_file_info *fi) +{ + Inode &inode = get_inode(ino); + int ifd = inode.fd; + int res; + + if (valid & FUSE_SET_ATTR_MODE) { + if (fi) { + res = fchmod(fi->fh, attr->st_mode); + } else { + char procname[64]; + sprintf(procname, "/proc/self/fd/%i", ifd); + res = chmod(procname, attr->st_mode); + } + if (res == -1) + goto out_err; + } + if (valid & (FUSE_SET_ATTR_UID | FUSE_SET_ATTR_GID)) { + uid_t uid = (valid & FUSE_SET_ATTR_UID) ? + attr->st_uid : + static_cast<uid_t>(-1); + gid_t gid = (valid & FUSE_SET_ATTR_GID) ? + attr->st_gid : + static_cast<gid_t>(-1); + + res = fchownat(ifd, "", uid, gid, + AT_EMPTY_PATH | AT_SYMLINK_NOFOLLOW); + if (res == -1) + goto out_err; + } + if (valid & FUSE_SET_ATTR_SIZE) { + if (fi) { + res = ftruncate(fi->fh, attr->st_size); + } else { + char procname[64]; + sprintf(procname, "/proc/self/fd/%i", ifd); + res = truncate(procname, attr->st_size); + } + if (res == -1) + goto out_err; + } + if (valid & (FUSE_SET_ATTR_ATIME | FUSE_SET_ATTR_MTIME)) { + struct timespec tv[2]; + + tv[0].tv_sec = 0; + tv[1].tv_sec = 0; + tv[0].tv_nsec = UTIME_OMIT; + tv[1].tv_nsec = UTIME_OMIT; + + if (valid & FUSE_SET_ATTR_ATIME_NOW) + tv[0].tv_nsec = UTIME_NOW; + else if (valid & FUSE_SET_ATTR_ATIME) + tv[0] = attr->st_atim; + + if (valid & FUSE_SET_ATTR_MTIME_NOW) + tv[1].tv_nsec = UTIME_NOW; + else if (valid & FUSE_SET_ATTR_MTIME) + tv[1] = attr->st_mtim; + + if (fi) + res = futimens(fi->fh, tv); + else { #ifdef HAVE_UTIMENSAT - char procname[64]; - sprintf(procname, "/proc/self/fd/%i", ifd); - res = utimensat(AT_FDCWD, procname, tv, 0); + char procname[64]; + sprintf(procname, "/proc/self/fd/%i", ifd); + res = utimensat(AT_FDCWD, procname, tv, 0); #else - res = -1; - errno = EOPNOTSUPP; + res = -1; + errno = EOPNOTSUPP; #endif - } - if (res == -1) - goto out_err; - } - return sfs_getattr(req, ino, fi); + } + if (res == -1) + goto out_err; + } + return sfs_getattr(req, ino, fi); out_err: - fuse_reply_err(req, errno); + fuse_reply_err(req, errno); } - static void sfs_setattr(fuse_req_t req, fuse_ino_t ino, struct stat *attr, - int valid, fuse_file_info *fi) { - (void) ino; - do_setattr(req, ino, attr, valid, fi); + int valid, fuse_file_info *fi) +{ + (void)ino; + do_setattr(req, ino, attr, valid, fi); } - -static int do_lookup(fuse_ino_t parent, const char *name, - fuse_entry_param *e) { - if (fs.debug) - cerr << "DEBUG: lookup(): name=" << name - << ", parent=" << parent << endl; - memset(e, 0, sizeof(*e)); - e->attr_timeout = fs.timeout; - e->entry_timeout = fs.timeout; - - auto newfd = openat(get_fs_fd(parent), name, O_PATH | O_NOFOLLOW); - if (newfd == -1) - return errno; - - auto res = fstatat(newfd, "", &e->attr, AT_EMPTY_PATH | AT_SYMLINK_NOFOLLOW); - if (res == -1) { - auto saveerr = errno; - close(newfd); - if (fs.debug) - cerr << "DEBUG: lookup(): fstatat failed" << endl; - return saveerr; - } - - if (e->attr.st_dev != fs.src_dev) { - cerr << "WARNING: Mountpoints in the source directory tree will be hidden." << endl; - return ENOTSUP; - } else if (e->attr.st_ino == FUSE_ROOT_ID) { - cerr << "ERROR: Source directory tree must not include inode " - << FUSE_ROOT_ID << endl; - return EIO; - } - - SrcId id {e->attr.st_ino, e->attr.st_dev}; - unique_lock<mutex> fs_lock {fs.mutex}; - Inode* inode_p; - try { - inode_p = &fs.inodes[id]; - } catch (std::bad_alloc&) { - return ENOMEM; - } - e->ino = reinterpret_cast<fuse_ino_t>(inode_p); - Inode& inode {*inode_p}; - e->generation = inode.generation; - - if (inode.fd == -ENOENT) { // found unlinked inode - if (fs.debug) - cerr << "DEBUG: lookup(): inode " << e->attr.st_ino - << " recycled; generation=" << inode.generation << endl; - /* fallthrough to new inode but keep existing inode.nlookup */ - } - - if (inode.fd > 0) { // found existing inode - fs_lock.unlock(); - if (fs.debug) - cerr << "DEBUG: lookup(): inode " << e->attr.st_ino - << " (userspace) already known; fd = " << inode.fd << endl; - lock_guard<mutex> g {inode.m}; - - inode.nlookup++; - if (fs.debug) - cerr << "DEBUG:" << __func__ << ":" << __LINE__ << " " - << "inode " << inode.src_ino - << " count " << inode.nlookup << endl; - - - close(newfd); - } else { // no existing inode - /* This is just here to make Helgrind happy. It violates the - lock ordering requirement (inode.m must be acquired before - fs.mutex), but this is of no consequence because at this - point no other thread has access to the inode mutex */ - lock_guard<mutex> g {inode.m}; - inode.src_ino = e->attr.st_ino; - inode.src_dev = e->attr.st_dev; - - inode.nlookup++; - if (fs.debug) - cerr << "DEBUG:" << __func__ << ":" << __LINE__ << " " - << "inode " << inode.src_ino - << " count " << inode.nlookup << endl; - - inode.fd = newfd; - fs_lock.unlock(); - - if (fs.debug) - cerr << "DEBUG: lookup(): created userspace inode " << e->attr.st_ino - << "; fd = " << inode.fd << endl; - } - - return 0; +static int do_lookup(fuse_ino_t parent, const char *name, fuse_entry_param *e) +{ + if (fs.debug) + cerr << "DEBUG: lookup(): name=" << name + << ", parent=" << parent << endl; + memset(e, 0, sizeof(*e)); + e->attr_timeout = fs.timeout; + e->entry_timeout = fs.timeout; + + auto newfd = openat(get_fs_fd(parent), name, O_PATH | O_NOFOLLOW); + if (newfd == -1) + return errno; + + auto res = fstatat(newfd, "", &e->attr, + AT_EMPTY_PATH | AT_SYMLINK_NOFOLLOW); + if (res == -1) { + auto saveerr = errno; + close(newfd); + if (fs.debug) + cerr << "DEBUG: lookup(): fstatat failed" << endl; + return saveerr; + } + + if (e->attr.st_dev != fs.src_dev) { + cerr << "WARNING: Mountpoints in the source directory tree will be hidden." + << endl; + return ENOTSUP; + } else if (e->attr.st_ino == FUSE_ROOT_ID) { + cerr << "ERROR: Source directory tree must not include inode " + << FUSE_ROOT_ID << endl; + return EIO; + } + + SrcId id{ e->attr.st_ino, e->attr.st_dev }; + unique_lock<mutex> fs_lock{ fs.mutex }; + Inode *inode_p; + try { + inode_p = &fs.inodes[id]; + } catch (std::bad_alloc &) { + return ENOMEM; + } + e->ino = reinterpret_cast<fuse_ino_t>(inode_p); + Inode &inode{ *inode_p }; + e->generation = inode.generation; + + if (inode.fd == -ENOENT) { // found unlinked inode + if (fs.debug) + cerr << "DEBUG: lookup(): inode " << e->attr.st_ino + << " recycled; generation=" << inode.generation + << endl; + /* fallthrough to new inode but keep existing inode.nlookup */ + } + + if (inode.fd > 0) { // found existing inode + fs_lock.unlock(); + if (fs.debug) + cerr << "DEBUG: lookup(): inode " << e->attr.st_ino + << " (userspace) already known; fd = " << inode.fd + << endl; + lock_guard<mutex> g{ inode.m }; + + inode.nlookup++; + if (fs.debug) + cerr << "DEBUG:" << __func__ << ":" << __LINE__ << " " + << "inode " << inode.src_ino << " count " + << inode.nlookup << endl; + + close(newfd); + } else { // no existing inode + /* This is just here to make Helgrind happy. It violates the + * lock ordering requirement (inode.m must be acquired before + * fs.mutex), but this is of no consequence because at this + * point no other thread has access to the inode mutex + */ + lock_guard<mutex> g{ inode.m }; + inode.src_ino = e->attr.st_ino; + inode.src_dev = e->attr.st_dev; + + inode.nlookup++; + if (fs.debug) + cerr << "DEBUG:" << __func__ << ":" << __LINE__ << " " + << "inode " << inode.src_ino << " count " + << inode.nlookup << endl; + + inode.fd = newfd; + fs_lock.unlock(); + + if (fs.debug) + cerr << "DEBUG: lookup(): created userspace inode " + << e->attr.st_ino << "; fd = " << inode.fd << endl; + } + + return 0; } - -static void sfs_lookup(fuse_req_t req, fuse_ino_t parent, const char *name) { - fuse_entry_param e {}; - auto err = do_lookup(parent, name, &e); - if (err == ENOENT) { - e.attr_timeout = fs.timeout; - e.entry_timeout = fs.timeout; - e.ino = e.attr.st_ino = 0; - fuse_reply_entry(req, &e); - } else if (err) { - if (err == ENFILE || err == EMFILE) - cerr << "ERROR: Reached maximum number of file descriptors." << endl; - fuse_reply_err(req, err); - } else { - fuse_reply_entry(req, &e); - } +static void sfs_lookup(fuse_req_t req, fuse_ino_t parent, const char *name) +{ + fuse_entry_param e{}; + auto err = do_lookup(parent, name, &e); + if (err == ENOENT) { + e.attr_timeout = fs.timeout; + e.entry_timeout = fs.timeout; + e.ino = e.attr.st_ino = 0; + fuse_reply_entry(req, &e); + } else if (err) { + if (err == ENFILE || err == EMFILE) + cerr << "ERROR: Reached maximum number of file descriptors." + << endl; + fuse_reply_err(req, err); + } else { + fuse_reply_entry(req, &e); + } } - -static void mknod_symlink(fuse_req_t req, fuse_ino_t parent, - const char *name, mode_t mode, dev_t rdev, - const char *link) { - int res; - Inode& inode_p = get_inode(parent); - auto saverr = ENOMEM; - - if (S_ISDIR(mode)) - res = mkdirat(inode_p.fd, name, mode); - else if (S_ISLNK(mode)) - res = symlinkat(link, inode_p.fd, name); - else - res = mknodat(inode_p.fd, name, mode, rdev); - saverr = errno; - if (res == -1) - goto out; - - fuse_entry_param e; - saverr = do_lookup(parent, name, &e); - if (saverr) - goto out; - - fuse_reply_entry(req, &e); - return; +static void mknod_symlink(fuse_req_t req, fuse_ino_t parent, const char *name, + mode_t mode, dev_t rdev, const char *link) +{ + int res; + Inode &inode_p = get_inode(parent); + auto saverr = ENOMEM; + + if (S_ISDIR(mode)) + res = mkdirat(inode_p.fd, name, mode); + else if (S_ISLNK(mode)) + res = symlinkat(link, inode_p.fd, name); + else + res = mknodat(inode_p.fd, name, mode, rdev); + saverr = errno; + if (res == -1) + goto out; + + fuse_entry_param e; + saverr = do_lookup(parent, name, &e); + if (saverr) + goto out; + + fuse_reply_entry(req, &e); + return; out: - if (saverr == ENFILE || saverr == EMFILE) - cerr << "ERROR: Reached maximum number of file descriptors." << endl; - fuse_reply_err(req, saverr); + if (saverr == ENFILE || saverr == EMFILE) + cerr << "ERROR: Reached maximum number of file descriptors." + << endl; + fuse_reply_err(req, saverr); } - static void sfs_mknod(fuse_req_t req, fuse_ino_t parent, const char *name, - mode_t mode, dev_t rdev) { - mknod_symlink(req, parent, name, mode, rdev, nullptr); + mode_t mode, dev_t rdev) +{ + mknod_symlink(req, parent, name, mode, rdev, nullptr); } - static void sfs_mkdir(fuse_req_t req, fuse_ino_t parent, const char *name, - mode_t mode) { - mknod_symlink(req, parent, name, S_IFDIR | mode, 0, nullptr); + mode_t mode) +{ + mknod_symlink(req, parent, name, S_IFDIR | mode, 0, nullptr); } - static void sfs_symlink(fuse_req_t req, const char *link, fuse_ino_t parent, - const char *name) { - mknod_symlink(req, parent, name, S_IFLNK, 0, link); + const char *name) +{ + mknod_symlink(req, parent, name, S_IFLNK, 0, link); } - static void sfs_link(fuse_req_t req, fuse_ino_t ino, fuse_ino_t parent, - const char *name) { - Inode& inode = get_inode(ino); - Inode& inode_p = get_inode(parent); - fuse_entry_param e {}; - - e.attr_timeout = fs.timeout; - e.entry_timeout = fs.timeout; - - char procname[64]; - sprintf(procname, "/proc/self/fd/%i", inode.fd); - auto res = linkat(AT_FDCWD, procname, inode_p.fd, name, AT_SYMLINK_FOLLOW); - if (res == -1) { - fuse_reply_err(req, errno); - return; - } - - res = fstatat(inode.fd, "", &e.attr, AT_EMPTY_PATH | AT_SYMLINK_NOFOLLOW); - if (res == -1) { - fuse_reply_err(req, errno); - return; - } - e.ino = reinterpret_cast<fuse_ino_t>(&inode); - { - lock_guard<mutex> g {inode.m}; - inode.nlookup++; - if (fs.debug) - cerr << "DEBUG:" << __func__ << ":" << __LINE__ << " " - << "inode " << inode.src_ino - << " count " << inode.nlookup << endl; - } - - fuse_reply_entry(req, &e); - return; + const char *name) +{ + Inode &inode = get_inode(ino); + Inode &inode_p = get_inode(parent); + fuse_entry_param e{}; + + e.attr_timeout = fs.timeout; + e.entry_timeout = fs.timeout; + + char procname[64]; + sprintf(procname, "/proc/self/fd/%i", inode.fd); + auto res = + linkat(AT_FDCWD, procname, inode_p.fd, name, AT_SYMLINK_FOLLOW); + if (res == -1) { + fuse_reply_err(req, errno); + return; + } + + res = fstatat(inode.fd, "", &e.attr, + AT_EMPTY_PATH | AT_SYMLINK_NOFOLLOW); + if (res == -1) { + fuse_reply_err(req, errno); + return; + } + e.ino = reinterpret_cast<fuse_ino_t>(&inode); + { + lock_guard<mutex> g{ inode.m }; + inode.nlookup++; + if (fs.debug) + cerr << "DEBUG:" << __func__ << ":" << __LINE__ << " " + << "inode " << inode.src_ino << " count " + << inode.nlookup << endl; + } + + fuse_reply_entry(req, &e); + return; } - -static void sfs_rmdir(fuse_req_t req, fuse_ino_t parent, const char *name) { - Inode& inode_p = get_inode(parent); - lock_guard<mutex> g {inode_p.m}; - auto res = unlinkat(inode_p.fd, name, AT_REMOVEDIR); - fuse_reply_err(req, res == -1 ? errno : 0); +static void sfs_rmdir(fuse_req_t req, fuse_ino_t parent, const char *name) +{ + Inode &inode_p = get_inode(parent); + lock_guard<mutex> g{ inode_p.m }; + auto res = unlinkat(inode_p.fd, name, AT_REMOVEDIR); + fuse_reply_err(req, res == -1 ? errno : 0); } - static void sfs_rename(fuse_req_t req, fuse_ino_t parent, const char *name, - fuse_ino_t newparent, const char *newname, - unsigned int flags) { - Inode& inode_p = get_inode(parent); - Inode& inode_np = get_inode(newparent); - if (flags) { - fuse_reply_err(req, EINVAL); - return; - } - - auto res = renameat(inode_p.fd, name, inode_np.fd, newname); - fuse_reply_err(req, res == -1 ? errno : 0); + fuse_ino_t newparent, const char *newname, + unsigned int flags) +{ + Inode &inode_p = get_inode(parent); + Inode &inode_np = get_inode(newparent); + if (flags) { + fuse_reply_err(req, EINVAL); + return; + } + + auto res = renameat(inode_p.fd, name, inode_np.fd, newname); + fuse_reply_err(req, res == -1 ? errno : 0); } - -static void sfs_unlink(fuse_req_t req, fuse_ino_t parent, const char *name) { - Inode& inode_p = get_inode(parent); - // Release inode.fd before last unlink like nfsd EXPORT_OP_CLOSE_BEFORE_UNLINK - // to test reused inode numbers. - // Skip this when inode has an open file and when writeback cache is enabled. - if (!fs.timeout) { - fuse_entry_param e; - auto err = do_lookup(parent, name, &e); - if (err) { - fuse_reply_err(req, err); - return; - } - if (e.attr.st_nlink == 1) { - Inode& inode = get_inode(e.ino); - lock_guard<mutex> g {inode.m}; - if (inode.fd > 0 && !inode.nopen) { - if (fs.debug) - cerr << "DEBUG: unlink: release inode " << e.attr.st_ino - << "; fd=" << inode.fd << endl; - lock_guard<mutex> g_fs {fs.mutex}; - close(inode.fd); - inode.fd = -ENOENT; - inode.generation++; - } - } - - // decrease the ref which lookup above had increased - forget_one(e.ino, 1); - } - auto res = unlinkat(inode_p.fd, name, 0); - fuse_reply_err(req, res == -1 ? errno : 0); +static void sfs_unlink(fuse_req_t req, fuse_ino_t parent, const char *name) +{ + Inode &inode_p = get_inode(parent); + // Release inode.fd before last unlink like nfsd EXPORT_OP_CLOSE_BEFORE_UNLINK + // to test reused inode numbers. + // Skip this when inode has an open file and when writeback cache is enabled. + if (!fs.timeout) { + fuse_entry_param e; + auto err = do_lookup(parent, name, &e); + if (err) { + fuse_reply_err(req, err); + return; + } + if (e.attr.st_nlink == 1) { + Inode &inode = get_inode(e.ino); + lock_guard<mutex> g{ inode.m }; + if (inode.fd > 0 && !inode.nopen) { + if (fs.debug) + cerr << "DEBUG: unlink: release inode " + << e.attr.st_ino + << "; fd=" << inode.fd << endl; + lock_guard<mutex> g_fs{ fs.mutex }; + close(inode.fd); + inode.fd = -ENOENT; + inode.generation++; + } + } + + // decrease the ref which lookup above had increased + forget_one(e.ino, 1); + } + auto res = unlinkat(inode_p.fd, name, 0); + fuse_reply_err(req, res == -1 ? errno : 0); } - -static void forget_one(fuse_ino_t ino, uint64_t n) { - Inode& inode = get_inode(ino); - unique_lock<mutex> l {inode.m}; - - if(n > inode.nlookup) { - cerr << "INTERNAL ERROR: Negative lookup count for inode " - << inode.src_ino << endl; - abort(); - } - inode.nlookup -= n; - - if (fs.debug) - cerr << "DEBUG:" << __func__ << ":" << __LINE__ << " " - << "inode " << inode.src_ino - << " count " << inode.nlookup << endl; - - if (!inode.nlookup) { - if (fs.debug) - cerr << "DEBUG: forget: cleaning up inode " << inode.src_ino << endl; - { - lock_guard<mutex> g_fs {fs.mutex}; - l.unlock(); - fs.inodes.erase({inode.src_ino, inode.src_dev}); - } - } else if (fs.debug) - cerr << "DEBUG: forget: inode " << inode.src_ino - << " lookup count now " << inode.nlookup << endl; +static void forget_one(fuse_ino_t ino, uint64_t n) +{ + Inode &inode = get_inode(ino); + unique_lock<mutex> l{ inode.m }; + + if (n > inode.nlookup) { + cerr << "INTERNAL ERROR: Negative lookup count for inode " + << inode.src_ino << endl; + abort(); + } + inode.nlookup -= n; + + if (fs.debug) + cerr << "DEBUG:" << __func__ << ":" << __LINE__ << " " + << "inode " << inode.src_ino << " count " << inode.nlookup + << endl; + + if (!inode.nlookup) { + if (fs.debug) + cerr << "DEBUG: forget: cleaning up inode " + << inode.src_ino << endl; + { + lock_guard<mutex> g_fs{ fs.mutex }; + l.unlock(); + fs.inodes.erase({ inode.src_ino, inode.src_dev }); + } + } else if (fs.debug) + cerr << "DEBUG: forget: inode " << inode.src_ino + << " lookup count now " << inode.nlookup << endl; } -static void sfs_forget(fuse_req_t req, fuse_ino_t ino, uint64_t nlookup) { - forget_one(ino, nlookup); - fuse_reply_none(req); +static void sfs_forget(fuse_req_t req, fuse_ino_t ino, uint64_t nlookup) +{ + forget_one(ino, nlookup); + fuse_reply_none(req); } - static void sfs_forget_multi(fuse_req_t req, size_t count, - fuse_forget_data *forgets) { - for (int i = 0; i < count; i++) - forget_one(forgets[i].ino, forgets[i].nlookup); - fuse_reply_none(req); + fuse_forget_data *forgets) +{ + for (int i = 0; i < count; i++) + forget_one(forgets[i].ino, forgets[i].nlookup); + fuse_reply_none(req); } - -static void sfs_readlink(fuse_req_t req, fuse_ino_t ino) { - Inode& inode = get_inode(ino); - char buf[PATH_MAX + 1]; - auto res = readlinkat(inode.fd, "", buf, sizeof(buf)); - if (res == -1) - fuse_reply_err(req, errno); - else if (res == sizeof(buf)) - fuse_reply_err(req, ENAMETOOLONG); - else { - buf[res] = '\0'; - fuse_reply_readlink(req, buf); - } +static void sfs_readlink(fuse_req_t req, fuse_ino_t ino) +{ + Inode &inode = get_inode(ino); + char buf[PATH_MAX + 1]; + auto res = readlinkat(inode.fd, "", buf, sizeof(buf)); + if (res == -1) + fuse_reply_err(req, errno); + else if (res == sizeof(buf)) + fuse_reply_err(req, ENAMETOOLONG); + else { + buf[res] = '\0'; + fuse_reply_readlink(req, buf); + } } - struct DirHandle { - DIR *dp {nullptr}; - off_t offset; - - DirHandle() = default; - DirHandle(const DirHandle&) = delete; - DirHandle& operator=(const DirHandle&) = delete; - - ~DirHandle() { - if(dp) - closedir(dp); - } + DIR *dp{ nullptr }; + off_t offset; + + DirHandle() = default; + DirHandle(const DirHandle &) = delete; + DirHandle &operator=(const DirHandle &) = delete; + + ~DirHandle() + { + if (dp) + closedir(dp); + } }; - -static DirHandle *get_dir_handle(fuse_file_info *fi) { - return reinterpret_cast<DirHandle*>(fi->fh); +static DirHandle *get_dir_handle(fuse_file_info *fi) +{ + return reinterpret_cast<DirHandle *>(fi->fh); } - -static void sfs_opendir(fuse_req_t req, fuse_ino_t ino, fuse_file_info *fi) { - Inode& inode = get_inode(ino); - auto d = new (nothrow) DirHandle; - if (d == nullptr) { - fuse_reply_err(req, ENOMEM); - return; - } - - // Make Helgrind happy - it can't know that there's an implicit - // synchronization due to the fact that other threads cannot - // access d until we've called fuse_reply_*. - lock_guard<mutex> g {inode.m}; - - auto fd = openat(inode.fd, ".", O_RDONLY); - if (fd == -1) - goto out_errno; - - // On success, dir stream takes ownership of fd, so we - // do not have to close it. - d->dp = fdopendir(fd); - if(d->dp == nullptr) - goto out_errno; - - d->offset = 0; - - fi->fh = reinterpret_cast<uint64_t>(d); - if(fs.timeout) { - fi->keep_cache = 1; - fi->cache_readdir = 1; - } - fuse_reply_open(req, fi); - return; +static void sfs_opendir(fuse_req_t req, fuse_ino_t ino, fuse_file_info *fi) +{ + Inode &inode = get_inode(ino); + auto d = new (nothrow) DirHandle; + if (d == nullptr) { + fuse_reply_err(req, ENOMEM); + return; + } + + // Make Helgrind happy - it can't know that there's an implicit + // synchronization due to the fact that other threads cannot + // access d until we've called fuse_reply_*. + lock_guard<mutex> g{ inode.m }; + + auto fd = openat(inode.fd, ".", O_RDONLY); + if (fd == -1) + goto out_errno; + + // On success, dir stream takes ownership of fd, so we + // do not have to close it. + d->dp = fdopendir(fd); + if (d->dp == nullptr) + goto out_errno; + + d->offset = 0; + + fi->fh = reinterpret_cast<uint64_t>(d); + if (fs.timeout) { + fi->keep_cache = 1; + fi->cache_readdir = 1; + } + fuse_reply_open(req, fi); + return; out_errno: - auto error = errno; - delete d; - if (error == ENFILE || error == EMFILE) - cerr << "ERROR: Reached maximum number of file descriptors." << endl; - fuse_reply_err(req, error); + auto error = errno; + delete d; + if (error == ENFILE || error == EMFILE) + cerr << "ERROR: Reached maximum number of file descriptors." + << endl; + fuse_reply_err(req, error); } - -static bool is_dot_or_dotdot(const char *name) { - return name[0] == '.' && - (name[1] == '\0' || (name[1] == '.' && name[2] == '\0')); +static bool is_dot_or_dotdot(const char *name) +{ + return name[0] == '.' && + (name[1] == '\0' || (name[1] == '.' && name[2] == '\0')); } - static void do_readdir(fuse_req_t req, fuse_ino_t ino, size_t size, - off_t offset, fuse_file_info *fi, const int plus) { - auto d = get_dir_handle(fi); - Inode& inode = get_inode(ino); - lock_guard<mutex> g {inode.m}; - char *p; - auto rem = size; - int err = 0, count = 0; - - if (fs.debug) - cerr << "DEBUG: readdir(): started with offset " - << offset << endl; - - auto buf = new (nothrow) char[size]; - if (!buf) { - fuse_reply_err(req, ENOMEM); - return; - } - p = buf; - - if (offset != d->offset) { - if (fs.debug) - cerr << "DEBUG: readdir(): seeking to " << offset << endl; - seekdir(d->dp, offset); - d->offset = offset; - } - - while (1) { - bool did_lookup = false; - struct dirent *entry; - errno = 0; - entry = readdir(d->dp); - if (!entry) { - if(errno) { - err = errno; - if (fs.debug) - warn("DEBUG: readdir(): readdir failed with"); - goto error; - } - break; // End of stream - } - d->offset = entry->d_off; - - fuse_entry_param e{}; - size_t entsize; - if (plus) { - if (is_dot_or_dotdot(entry->d_name)) { - /* fuse kernel ignores attributes for these and also does - * not increase lookup count (see fuse_direntplus_link) */ - e.attr.st_ino = entry->d_ino; - e.attr.st_mode = entry->d_type << 12; - } else { - err = do_lookup(ino, entry->d_name, &e); - if (err) - goto error; - did_lookup = true; - } - entsize = fuse_add_direntry_plus(req, p, rem, entry->d_name, &e, entry->d_off); - } else { - e.attr.st_ino = entry->d_ino; - e.attr.st_mode = entry->d_type << 12; - entsize = fuse_add_direntry(req, p, rem, entry->d_name, &e.attr, entry->d_off); - } - - if (entsize > rem) { - if (fs.debug) - cerr << "DEBUG: readdir(): buffer full, returning data. " << endl; - if (did_lookup) - forget_one(e.ino, 1); - break; - } - - p += entsize; - rem -= entsize; - count++; - if (fs.debug) { - cerr << "DEBUG: readdir(): added to buffer: " << entry->d_name - << ", ino " << e.attr.st_ino << ", offset " << entry->d_off << endl; - } - } - err = 0; + off_t offset, fuse_file_info *fi, const int plus) +{ + auto d = get_dir_handle(fi); + Inode &inode = get_inode(ino); + lock_guard<mutex> g{ inode.m }; + char *p; + auto rem = size; + int err = 0, count = 0; + + if (fs.debug) + cerr << "DEBUG: readdir(): started with offset " << offset + << endl; + + auto buf = new (nothrow) char[size]; + if (!buf) { + fuse_reply_err(req, ENOMEM); + return; + } + p = buf; + + if (offset != d->offset) { + if (fs.debug) + cerr << "DEBUG: readdir(): seeking to " << offset + << endl; + seekdir(d->dp, offset); + d->offset = offset; + } + + while (1) { + bool did_lookup = false; + struct dirent *entry; + errno = 0; + entry = readdir(d->dp); + if (!entry) { + if (errno) { + err = errno; + if (fs.debug) + warn("DEBUG: readdir(): readdir failed with"); + goto error; + } + break; // End of stream + } + d->offset = entry->d_off; + + fuse_entry_param e{}; + size_t entsize; + if (plus) { + if (is_dot_or_dotdot(entry->d_name)) { + /* fuse kernel ignores attributes for these and also does + * not increase lookup count (see fuse_direntplus_link) + */ + e.attr.st_ino = entry->d_ino; + e.attr.st_mode = entry->d_type << 12; + } else { + err = do_lookup(ino, entry->d_name, &e); + if (err) + goto error; + did_lookup = true; + } + entsize = fuse_add_direntry_plus( + req, p, rem, entry->d_name, &e, entry->d_off); + } else { + e.attr.st_ino = entry->d_ino; + e.attr.st_mode = entry->d_type << 12; + entsize = fuse_add_direntry(req, p, rem, entry->d_name, + &e.attr, entry->d_off); + } + + if (entsize > rem) { + if (fs.debug) + cerr << "DEBUG: readdir(): buffer full, returning data. " + << endl; + if (did_lookup) + forget_one(e.ino, 1); + break; + } + + p += entsize; + rem -= entsize; + count++; + if (fs.debug) { + cerr << "DEBUG: readdir(): added to buffer: " + << entry->d_name << ", ino " << e.attr.st_ino + << ", offset " << entry->d_off << endl; + } + } + err = 0; error: - // If there's an error, we can only signal it if we haven't stored - // any entries yet - otherwise we'd end up with wrong lookup - // counts for the entries that are already in the buffer. So we - // return what we've collected until that point. - if (err && rem == size) { - if (err == ENFILE || err == EMFILE) - cerr << "ERROR: Reached maximum number of file descriptors." << endl; - fuse_reply_err(req, err); - } else { - if (fs.debug) - cerr << "DEBUG: readdir(): returning " << count - << " entries, curr offset " << d->offset << endl; - fuse_reply_buf(req, buf, size - rem); - } - delete[] buf; - return; + // If there's an error, we can only signal it if we haven't stored + // any entries yet - otherwise we'd end up with wrong lookup + // counts for the entries that are already in the buffer. So we + // return what we've collected until that point. + if (err && rem == size) { + if (err == ENFILE || err == EMFILE) + cerr << "ERROR: Reached maximum number of file descriptors." + << endl; + fuse_reply_err(req, err); + } else { + if (fs.debug) + cerr << "DEBUG: readdir(): returning " << count + << " entries, curr offset " << d->offset << endl; + fuse_reply_buf(req, buf, size - rem); + } + delete[] buf; + return; } - static void sfs_readdir(fuse_req_t req, fuse_ino_t ino, size_t size, - off_t offset, fuse_file_info *fi) { - // operation logging is done in readdir to reduce code duplication - do_readdir(req, ino, size, offset, fi, 0); + off_t offset, fuse_file_info *fi) +{ + // operation logging is done in readdir to reduce code duplication + do_readdir(req, ino, size, offset, fi, 0); } - static void sfs_readdirplus(fuse_req_t req, fuse_ino_t ino, size_t size, - off_t offset, fuse_file_info *fi) { - // operation logging is done in readdir to reduce code duplication - do_readdir(req, ino, size, offset, fi, 1); + off_t offset, fuse_file_info *fi) +{ + // operation logging is done in readdir to reduce code duplication + do_readdir(req, ino, size, offset, fi, 1); } - -static void sfs_releasedir(fuse_req_t req, fuse_ino_t ino, fuse_file_info *fi) { - (void) ino; - auto d = get_dir_handle(fi); - delete d; - fuse_reply_err(req, 0); +static void sfs_releasedir(fuse_req_t req, fuse_ino_t ino, fuse_file_info *fi) +{ + (void)ino; + auto d = get_dir_handle(fi); + delete d; + fuse_reply_err(req, 0); } - static void do_passthrough_open(fuse_req_t req, fuse_ino_t ino, int fd, - fuse_file_info *fi) { - Inode& inode = get_inode(ino); - /* Setup a shared backing file on first open of an inode */ - if (inode.backing_id) { - if (fs.debug) - cerr << "DEBUG: reusing shared backing file " - << inode.backing_id << " for inode " << ino << endl; - fi->backing_id = inode.backing_id; - } else if (!(inode.backing_id = fuse_passthrough_open(req, fd))) { - cerr << "DEBUG: fuse_passthrough_open failed for inode " << ino - << ", disabling rw passthrough." << endl; - fs.passthrough = false; - } else { - if (fs.debug) - cerr << "DEBUG: setup shared backing file " - << inode.backing_id << " for inode " << ino << endl; - fi->backing_id = inode.backing_id; - } - /* open in passthrough mode must drop old page cache */ - if (fi->backing_id) - fi->keep_cache = false; + fuse_file_info *fi) +{ + Inode &inode = get_inode(ino); + /* Setup a shared backing file on first open of an inode */ + if (inode.backing_id) { + if (fs.debug) + cerr << "DEBUG: reusing shared backing file " + << inode.backing_id << " for inode " << ino + << endl; + fi->backing_id = inode.backing_id; + } else if (!(inode.backing_id = fuse_passthrough_open(req, fd))) { + cerr << "DEBUG: fuse_passthrough_open failed for inode " << ino + << ", disabling rw passthrough." << endl; + fs.passthrough = false; + } else { + if (fs.debug) + cerr << "DEBUG: setup shared backing file " + << inode.backing_id << " for inode " << ino + << endl; + fi->backing_id = inode.backing_id; + } + /* open in passthrough mode must drop old page cache */ + if (fi->backing_id) + fi->keep_cache = false; } static void sfs_create_open_flags(fuse_file_info *fi) { - if (fs.direct_io) - fi->direct_io = 1; - - /* - * fi->direct_io (FOPEN_DIRECT_IO) is set to benefit from - * parallel_direct_writes, which kernel cannot do for plain O_DIRECT. - * However, passthrough is preferred, but which is not possible when - * FOPEN_DIRECT_IO is set. - */ - if (!fs.passthrough) { - if (fi->flags & O_DIRECT) - fi->direct_io = 1; - } - - /* parallel_direct_writes feature depends on direct_io features. - To make parallel_direct_writes valid, need set fi->direct_io - in current function. */ - fi->parallel_direct_writes = 1; - - fi->keep_cache = (fs.timeout != 0); - fi->noflush = (fs.timeout == 0 && (fi->flags & O_ACCMODE) == O_RDONLY); + if (fs.direct_io) + fi->direct_io = 1; + + /* + * fi->direct_io (FOPEN_DIRECT_IO) is set to benefit from + * parallel_direct_writes, which kernel cannot do for plain O_DIRECT. + * However, passthrough is preferred, but which is not possible when + * FOPEN_DIRECT_IO is set. + */ + if (!fs.passthrough) { + if (fi->flags & O_DIRECT) + fi->direct_io = 1; + } + + /* parallel_direct_writes feature depends on direct_io features. + * To make parallel_direct_writes valid, need set fi->direct_io + * in current function. + */ + fi->parallel_direct_writes = 1; + + fi->keep_cache = (fs.timeout != 0); + fi->noflush = (fs.timeout == 0 && (fi->flags & O_ACCMODE) == O_RDONLY); } static void sfs_create(fuse_req_t req, fuse_ino_t parent, const char *name, - mode_t mode, fuse_file_info *fi) { - Inode& inode_p = get_inode(parent); - - auto fd = openat(inode_p.fd, name, - (fi->flags | O_CREAT) & ~O_NOFOLLOW, mode); - if (fd == -1) { - auto err = errno; - if (err == ENFILE || err == EMFILE) - cerr << "ERROR: Reached maximum number of file descriptors." << endl; - fuse_reply_err(req, err); - return; - } - - fi->fh = fd; - fuse_entry_param e; - auto err = do_lookup(parent, name, &e); - if (err) { - if (err == ENFILE || err == EMFILE) - cerr << "ERROR: Reached maximum number of file descriptors." << endl; - fuse_reply_err(req, err); - return; - } - - Inode& inode = get_inode(e.ino); - lock_guard<mutex> g {inode.m}; - inode.nopen++; - - sfs_create_open_flags(fi); - - if (fs.passthrough) - do_passthrough_open(req, e.ino, fd, fi); - fuse_reply_create(req, &e, fi); + mode_t mode, fuse_file_info *fi) +{ + Inode &inode_p = get_inode(parent); + + auto fd = openat(inode_p.fd, name, (fi->flags | O_CREAT) & ~O_NOFOLLOW, + mode); + if (fd == -1) { + auto err = errno; + if (err == ENFILE || err == EMFILE) + cerr << "ERROR: Reached maximum number of file descriptors." + << endl; + fuse_reply_err(req, err); + return; + } + + fi->fh = fd; + fuse_entry_param e; + auto err = do_lookup(parent, name, &e); + if (err) { + if (err == ENFILE || err == EMFILE) + cerr << "ERROR: Reached maximum number of file descriptors." + << endl; + fuse_reply_err(req, err); + return; + } + + Inode &inode = get_inode(e.ino); + lock_guard<mutex> g{ inode.m }; + inode.nopen++; + + sfs_create_open_flags(fi); + + if (fs.passthrough) + do_passthrough_open(req, e.ino, fd, fi); + fuse_reply_create(req, &e, fi); } static Inode *create_new_inode(int fd, fuse_entry_param *e) { - memset(e, 0, sizeof(*e)); - e->attr_timeout = fs.timeout; - e->entry_timeout = fs.timeout; - - auto res = fstatat(fd, "", &e->attr, AT_EMPTY_PATH | AT_SYMLINK_NOFOLLOW); - if (res == -1) { - if (fs.debug) - cerr << "DEBUG: lookup(): fstatat failed" << endl; - return NULL; - } - - SrcId id {e->attr.st_ino, e->attr.st_dev}; - unique_lock<mutex> fs_lock {fs.mutex}; - Inode* p_inode; - try { - p_inode = &fs.inodes[id]; - } catch (std::bad_alloc&) { - return NULL; - } - - e->ino = reinterpret_cast<fuse_ino_t>(p_inode); - e->generation = p_inode->generation; - - lock_guard<mutex> g {p_inode->m}; - p_inode->src_ino = e->attr.st_ino; - p_inode->src_dev = e->attr.st_dev; - - p_inode->nlookup++; - if (fs.debug) - cerr << "DEBUG:" << __func__ << ":" << __LINE__ << " " - << "inode " << p_inode->src_ino - << " count " << p_inode->nlookup << endl; - - p_inode->fd = fd; - fs_lock.unlock(); - - if (fs.debug) - cerr << "DEBUG: lookup(): created userspace inode " << e->attr.st_ino - << "; fd = " << p_inode->fd << endl; - return p_inode; -} - -static void sfs_tmpfile(fuse_req_t req, fuse_ino_t parent, - mode_t mode, fuse_file_info *fi) { - Inode& parent_inode = get_inode(parent); - - auto fd = openat(parent_inode.fd, ".", - (fi->flags | O_TMPFILE) & ~O_NOFOLLOW, mode); - if (fd == -1) { - auto err = errno; - if (err == ENFILE || err == EMFILE) - cerr << "ERROR: Reached maximum number of file descriptors." << endl; - fuse_reply_err(req, err); - return; - } - - fi->fh = fd; - fuse_entry_param e; - - Inode *inode = create_new_inode(dup(fd), &e); - if (inode == NULL) { - auto err = errno; - cerr << "ERROR: could not create new inode." << endl; - close(fd); - fuse_reply_err(req, err); - return; - } - - lock_guard<mutex> g {inode->m}; - - sfs_create_open_flags(fi); - - if (fs.passthrough) - do_passthrough_open(req, e.ino, fd, fi); - - fuse_reply_create(req, &e, fi); + memset(e, 0, sizeof(*e)); + e->attr_timeout = fs.timeout; + e->entry_timeout = fs.timeout; + + auto res = + fstatat(fd, "", &e->attr, AT_EMPTY_PATH | AT_SYMLINK_NOFOLLOW); + if (res == -1) { + if (fs.debug) + cerr << "DEBUG: lookup(): fstatat failed" << endl; + return NULL; + } + + SrcId id{ e->attr.st_ino, e->attr.st_dev }; + unique_lock<mutex> fs_lock{ fs.mutex }; + Inode *p_inode; + try { + p_inode = &fs.inodes[id]; + } catch (std::bad_alloc &) { + return NULL; + } + + e->ino = reinterpret_cast<fuse_ino_t>(p_inode); + e->generation = p_inode->generation; + + lock_guard<mutex> g{ p_inode->m }; + p_inode->src_ino = e->attr.st_ino; + p_inode->src_dev = e->attr.st_dev; + + p_inode->nlookup++; + if (fs.debug) + cerr << "DEBUG:" << __func__ << ":" << __LINE__ << " " + << "inode " << p_inode->src_ino << " count " + << p_inode->nlookup << endl; + + p_inode->fd = fd; + fs_lock.unlock(); + + if (fs.debug) + cerr << "DEBUG: lookup(): created userspace inode " + << e->attr.st_ino << "; fd = " << p_inode->fd << endl; + return p_inode; } +static void sfs_tmpfile(fuse_req_t req, fuse_ino_t parent, mode_t mode, + fuse_file_info *fi) +{ + Inode &parent_inode = get_inode(parent); + + auto fd = openat(parent_inode.fd, ".", + (fi->flags | O_TMPFILE) & ~O_NOFOLLOW, mode); + if (fd == -1) { + auto err = errno; + if (err == ENFILE || err == EMFILE) + cerr << "ERROR: Reached maximum number of file descriptors." + << endl; + fuse_reply_err(req, err); + return; + } + + fi->fh = fd; + fuse_entry_param e; + + Inode *inode = create_new_inode(dup(fd), &e); + if (inode == NULL) { + auto err = errno; + cerr << "ERROR: could not create new inode." << endl; + close(fd); + fuse_reply_err(req, err); + return; + } + + lock_guard<mutex> g{ inode->m }; + + sfs_create_open_flags(fi); + + if (fs.passthrough) + do_passthrough_open(req, e.ino, fd, fi); + + fuse_reply_create(req, &e, fi); +} static void sfs_fsyncdir(fuse_req_t req, fuse_ino_t ino, int datasync, - fuse_file_info *fi) { - (void) ino; - int res; - int fd = dirfd(get_dir_handle(fi)->dp); - if (datasync) - res = fdatasync(fd); - else - res = fsync(fd); - fuse_reply_err(req, res == -1 ? errno : 0); + fuse_file_info *fi) +{ + (void)ino; + int res; + int fd = dirfd(get_dir_handle(fi)->dp); + if (datasync) + res = fdatasync(fd); + else + res = fsync(fd); + fuse_reply_err(req, res == -1 ? errno : 0); } +static void sfs_open(fuse_req_t req, fuse_ino_t ino, fuse_file_info *fi) +{ + Inode &inode = get_inode(ino); -static void sfs_open(fuse_req_t req, fuse_ino_t ino, fuse_file_info *fi) { - Inode& inode = get_inode(ino); - - /* With writeback cache, kernel may send read requests even + /* With writeback cache, kernel may send read requests even when userspace opened write-only */ - if (fs.timeout && (fi->flags & O_ACCMODE) == O_WRONLY) { - fi->flags &= ~O_ACCMODE; - fi->flags |= O_RDWR; - } - - /* With writeback cache, O_APPEND is handled by the kernel. This - breaks atomicity (since the file may change in the underlying - filesystem, so that the kernel's idea of the end of the file - isn't accurate anymore). However, no process should modify the - file in the underlying filesystem once it has been read, so - this is not a problem. */ - if (fs.timeout && fi->flags & O_APPEND) - fi->flags &= ~O_APPEND; - - /* Unfortunately we cannot use inode.fd, because this was opened + if (fs.timeout && (fi->flags & O_ACCMODE) == O_WRONLY) { + fi->flags &= ~O_ACCMODE; + fi->flags |= O_RDWR; + } + + /* With writeback cache, O_APPEND is handled by the kernel. This + * breaks atomicity (since the file may change in the underlying + * filesystem, so that the kernel's idea of the end of the file + * isn't accurate anymore). However, no process should modify the + * file in the underlying filesystem once it has been read, so + * this is not a problem. + */ + if (fs.timeout && fi->flags & O_APPEND) + fi->flags &= ~O_APPEND; + + /* Unfortunately we cannot use inode.fd, because this was opened with O_PATH (so it doesn't allow read/write access). */ - char buf[64]; - sprintf(buf, "/proc/self/fd/%i", inode.fd); - auto fd = open(buf, fi->flags & ~O_NOFOLLOW); - if (fd == -1) { - auto err = errno; - if (err == ENFILE || err == EMFILE) - cerr << "ERROR: Reached maximum number of file descriptors." << endl; - fuse_reply_err(req, err); - return; - } - - lock_guard<mutex> g {inode.m}; - inode.nopen++; - - sfs_create_open_flags(fi); - - fi->fh = fd; - if (fs.passthrough) - do_passthrough_open(req, ino, fd, fi); - fuse_reply_open(req, fi); + char buf[64]; + sprintf(buf, "/proc/self/fd/%i", inode.fd); + auto fd = open(buf, fi->flags & ~O_NOFOLLOW); + if (fd == -1) { + auto err = errno; + if (err == ENFILE || err == EMFILE) + cerr << "ERROR: Reached maximum number of file descriptors." + << endl; + fuse_reply_err(req, err); + return; + } + + lock_guard<mutex> g{ inode.m }; + inode.nopen++; + + sfs_create_open_flags(fi); + + fi->fh = fd; + if (fs.passthrough) + do_passthrough_open(req, ino, fd, fi); + fuse_reply_open(req, fi); } - -static void sfs_release(fuse_req_t req, fuse_ino_t ino, fuse_file_info *fi) { - Inode& inode = get_inode(ino); - lock_guard<mutex> g {inode.m}; - inode.nopen--; - - /* Close the shared backing file on last file close of an inode */ - if (inode.backing_id && !inode.nopen) { - if (fuse_passthrough_close(req, inode.backing_id) < 0) { - cerr << "DEBUG: fuse_passthrough_close failed for inode " - << ino << " backing file " << inode.backing_id << endl; - } else if (fs.debug) { - cerr << "DEBUG: closed backing file " << inode.backing_id - << " for inode " << ino << endl; - } - inode.backing_id = 0; - } - - close(fi->fh); - fuse_reply_err(req, 0); +static void sfs_release(fuse_req_t req, fuse_ino_t ino, fuse_file_info *fi) +{ + Inode &inode = get_inode(ino); + lock_guard<mutex> g{ inode.m }; + inode.nopen--; + + /* Close the shared backing file on last file close of an inode */ + if (inode.backing_id && !inode.nopen) { + if (fuse_passthrough_close(req, inode.backing_id) < 0) { + cerr << "DEBUG: fuse_passthrough_close failed for inode " + << ino << " backing file " << inode.backing_id + << endl; + } else if (fs.debug) { + cerr << "DEBUG: closed backing file " + << inode.backing_id << " for inode " << ino + << endl; + } + inode.backing_id = 0; + } + + close(fi->fh); + fuse_reply_err(req, 0); } - -static void sfs_flush(fuse_req_t req, fuse_ino_t ino, fuse_file_info *fi) { - (void) ino; - auto res = close(dup(fi->fh)); - fuse_reply_err(req, res == -1 ? errno : 0); +static void sfs_flush(fuse_req_t req, fuse_ino_t ino, fuse_file_info *fi) +{ + (void)ino; + auto res = close(dup(fi->fh)); + fuse_reply_err(req, res == -1 ? errno : 0); } - static void sfs_fsync(fuse_req_t req, fuse_ino_t ino, int datasync, - fuse_file_info *fi) { - (void) ino; - int res; - if (datasync) - res = fdatasync(fi->fh); - else - res = fsync(fi->fh); - fuse_reply_err(req, res == -1 ? errno : 0); + fuse_file_info *fi) +{ + (void)ino; + int res; + if (datasync) + res = fdatasync(fi->fh); + else + res = fsync(fi->fh); + fuse_reply_err(req, res == -1 ? errno : 0); } +static void do_read(fuse_req_t req, size_t size, off_t off, fuse_file_info *fi) +{ + fuse_bufvec buf = FUSE_BUFVEC_INIT(size); + buf.buf[0].flags = + static_cast<fuse_buf_flags>(FUSE_BUF_IS_FD | FUSE_BUF_FD_SEEK); + buf.buf[0].fd = fi->fh; + buf.buf[0].pos = off; -static void do_read(fuse_req_t req, size_t size, off_t off, fuse_file_info *fi) { - - fuse_bufvec buf = FUSE_BUFVEC_INIT(size); - buf.buf[0].flags = static_cast<fuse_buf_flags>( - FUSE_BUF_IS_FD | FUSE_BUF_FD_SEEK); - buf.buf[0].fd = fi->fh; - buf.buf[0].pos = off; - - fuse_reply_data(req, &buf, FUSE_BUF_COPY_FLAGS); + fuse_reply_data(req, &buf, FUSE_BUF_COPY_FLAGS); } static void sfs_read(fuse_req_t req, fuse_ino_t ino, size_t size, off_t off, - fuse_file_info *fi) { - (void) ino; - if (fs.passthrough && !fs.direct_io) { - cerr << "ERROR: fuse_passthrough read failed." << endl; - fuse_reply_err(req, EIO); - return; - } - do_read(req, size, off, fi); + fuse_file_info *fi) +{ + (void)ino; + if (fs.passthrough && !fs.direct_io) { + cerr << "ERROR: fuse_passthrough read failed." << endl; + fuse_reply_err(req, EIO); + return; + } + do_read(req, size, off, fi); } - static void do_write_buf(fuse_req_t req, size_t size, off_t off, - fuse_bufvec *in_buf, fuse_file_info *fi) { - fuse_bufvec out_buf = FUSE_BUFVEC_INIT(size); - out_buf.buf[0].flags = static_cast<fuse_buf_flags>( - FUSE_BUF_IS_FD | FUSE_BUF_FD_SEEK); - out_buf.buf[0].fd = fi->fh; - out_buf.buf[0].pos = off; - - auto res = fuse_buf_copy(&out_buf, in_buf, FUSE_BUF_COPY_FLAGS); - if (res < 0) - fuse_reply_err(req, -res); - else - fuse_reply_write(req, (size_t)res); + fuse_bufvec *in_buf, fuse_file_info *fi) +{ + fuse_bufvec out_buf = FUSE_BUFVEC_INIT(size); + out_buf.buf[0].flags = + static_cast<fuse_buf_flags>(FUSE_BUF_IS_FD | FUSE_BUF_FD_SEEK); + out_buf.buf[0].fd = fi->fh; + out_buf.buf[0].pos = off; + + auto res = fuse_buf_copy(&out_buf, in_buf, FUSE_BUF_COPY_FLAGS); + if (res < 0) + fuse_reply_err(req, -res); + else + fuse_reply_write(req, (size_t)res); } - static void sfs_write_buf(fuse_req_t req, fuse_ino_t ino, fuse_bufvec *in_buf, - off_t off, fuse_file_info *fi) { - (void) ino; - if (fs.passthrough && !fs.direct_io) { - cerr << "ERROR: fuse_passthrough write failed." << endl; - fuse_reply_err(req, EIO); - return; - } - auto size {fuse_buf_size(in_buf)}; - do_write_buf(req, size, off, in_buf, fi); + off_t off, fuse_file_info *fi) +{ + (void)ino; + if (fs.passthrough && !fs.direct_io) { + cerr << "ERROR: fuse_passthrough write failed." << endl; + fuse_reply_err(req, EIO); + return; + } + auto size{ fuse_buf_size(in_buf) }; + do_write_buf(req, size, off, in_buf, fi); } +static void sfs_statfs(fuse_req_t req, fuse_ino_t ino) +{ + struct statvfs stbuf; -static void sfs_statfs(fuse_req_t req, fuse_ino_t ino) { - struct statvfs stbuf; - - auto res = fstatvfs(get_fs_fd(ino), &stbuf); - if (res == -1) - fuse_reply_err(req, errno); - else - fuse_reply_statfs(req, &stbuf); + auto res = fstatvfs(get_fs_fd(ino), &stbuf); + if (res == -1) + fuse_reply_err(req, errno); + else + fuse_reply_statfs(req, &stbuf); } - #ifdef HAVE_POSIX_FALLOCATE static void sfs_fallocate(fuse_req_t req, fuse_ino_t ino, int mode, - off_t offset, off_t length, fuse_file_info *fi) { - (void) ino; - if (mode) { - fuse_reply_err(req, EOPNOTSUPP); - return; - } - - auto err = posix_fallocate(fi->fh, offset, length); - fuse_reply_err(req, err); + off_t offset, off_t length, fuse_file_info *fi) +{ + (void)ino; + if (mode) { + fuse_reply_err(req, EOPNOTSUPP); + return; + } + + auto err = posix_fallocate(fi->fh, offset, length); + fuse_reply_err(req, err); } #endif static void sfs_flock(fuse_req_t req, fuse_ino_t ino, fuse_file_info *fi, - int op) { - (void) ino; - auto res = flock(fi->fh, op); - fuse_reply_err(req, res == -1 ? errno : 0); + int op) +{ + (void)ino; + auto res = flock(fi->fh, op); + fuse_reply_err(req, res == -1 ? errno : 0); } - #ifdef HAVE_SETXATTR static void sfs_getxattr(fuse_req_t req, fuse_ino_t ino, const char *name, - size_t size) { - char *value = nullptr; - Inode& inode = get_inode(ino); - ssize_t ret; - int saverr; - - char procname[64]; - sprintf(procname, "/proc/self/fd/%i", inode.fd); - - if (size) { - value = new (nothrow) char[size]; - if (value == nullptr) { - saverr = ENOMEM; - goto out; - } - - ret = getxattr(procname, name, value, size); - if (ret == -1) - goto out_err; - saverr = 0; - if (ret == 0) - goto out; - - fuse_reply_buf(req, value, ret); - } else { - ret = getxattr(procname, name, nullptr, 0); - if (ret == -1) - goto out_err; - - fuse_reply_xattr(req, ret); - } + size_t size) +{ + char *value = nullptr; + Inode &inode = get_inode(ino); + ssize_t ret; + int saverr; + + char procname[64]; + sprintf(procname, "/proc/self/fd/%i", inode.fd); + + if (size) { + value = new (nothrow) char[size]; + if (value == nullptr) { + saverr = ENOMEM; + goto out; + } + + ret = getxattr(procname, name, value, size); + if (ret == -1) + goto out_err; + saverr = 0; + if (ret == 0) + goto out; + + fuse_reply_buf(req, value, ret); + } else { + ret = getxattr(procname, name, nullptr, 0); + if (ret == -1) + goto out_err; + + fuse_reply_xattr(req, ret); + } out_free: - delete[] value; - return; + delete[] value; + return; out_err: - saverr = errno; + saverr = errno; out: - fuse_reply_err(req, saverr); - goto out_free; + fuse_reply_err(req, saverr); + goto out_free; } - -static void sfs_listxattr(fuse_req_t req, fuse_ino_t ino, size_t size) { - char *value = nullptr; - Inode& inode = get_inode(ino); - ssize_t ret; - int saverr; - - char procname[64]; - sprintf(procname, "/proc/self/fd/%i", inode.fd); - - if (size) { - value = new (nothrow) char[size]; - if (value == nullptr) { - saverr = ENOMEM; - goto out; - } - - ret = listxattr(procname, value, size); - if (ret == -1) - goto out_err; - saverr = 0; - if (ret == 0) - goto out; - - fuse_reply_buf(req, value, ret); - } else { - ret = listxattr(procname, nullptr, 0); - if (ret == -1) - goto out_err; - - fuse_reply_xattr(req, ret); - } +static void sfs_listxattr(fuse_req_t req, fuse_ino_t ino, size_t size) +{ + char *value = nullptr; + Inode &inode = get_inode(ino); + ssize_t ret; + int saverr; + + char procname[64]; + sprintf(procname, "/proc/self/fd/%i", inode.fd); + + if (size) { + value = new (nothrow) char[size]; + if (value == nullptr) { + saverr = ENOMEM; + goto out; + } + + ret = listxattr(procname, value, size); + if (ret == -1) + goto out_err; + saverr = 0; + if (ret == 0) + goto out; + + fuse_reply_buf(req, value, ret); + } else { + ret = listxattr(procname, nullptr, 0); + if (ret == -1) + goto out_err; + + fuse_reply_xattr(req, ret); + } out_free: - delete[] value; - return; + delete[] value; + return; out_err: - saverr = errno; + saverr = errno; out: - fuse_reply_err(req, saverr); - goto out_free; + fuse_reply_err(req, saverr); + goto out_free; } - static void sfs_setxattr(fuse_req_t req, fuse_ino_t ino, const char *name, - const char *value, size_t size, int flags) { - Inode& inode = get_inode(ino); - ssize_t ret; - int saverr; + const char *value, size_t size, int flags) +{ + Inode &inode = get_inode(ino); + ssize_t ret; + int saverr; - char procname[64]; - sprintf(procname, "/proc/self/fd/%i", inode.fd); + char procname[64]; + sprintf(procname, "/proc/self/fd/%i", inode.fd); - ret = setxattr(procname, name, value, size, flags); - saverr = ret == -1 ? errno : 0; + ret = setxattr(procname, name, value, size, flags); + saverr = ret == -1 ? errno : 0; - fuse_reply_err(req, saverr); + fuse_reply_err(req, saverr); } +static void sfs_removexattr(fuse_req_t req, fuse_ino_t ino, const char *name) +{ + char procname[64]; + Inode &inode = get_inode(ino); + ssize_t ret; + int saverr; -static void sfs_removexattr(fuse_req_t req, fuse_ino_t ino, const char *name) { - char procname[64]; - Inode& inode = get_inode(ino); - ssize_t ret; - int saverr; - - sprintf(procname, "/proc/self/fd/%i", inode.fd); - ret = removexattr(procname, name); - saverr = ret == -1 ? errno : 0; + sprintf(procname, "/proc/self/fd/%i", inode.fd); + ret = removexattr(procname, name); + saverr = ret == -1 ? errno : 0; - fuse_reply_err(req, saverr); + fuse_reply_err(req, saverr); } #endif - -static void assign_operations(fuse_lowlevel_ops &sfs_oper) { - sfs_oper.init = sfs_init; - sfs_oper.lookup = sfs_lookup; - sfs_oper.mkdir = sfs_mkdir; - sfs_oper.mknod = sfs_mknod; - sfs_oper.symlink = sfs_symlink; - sfs_oper.link = sfs_link; - sfs_oper.unlink = sfs_unlink; - sfs_oper.rmdir = sfs_rmdir; - sfs_oper.rename = sfs_rename; - sfs_oper.forget = sfs_forget; - sfs_oper.forget_multi = sfs_forget_multi; - sfs_oper.getattr = sfs_getattr; - sfs_oper.setattr = sfs_setattr; - sfs_oper.readlink = sfs_readlink; - sfs_oper.opendir = sfs_opendir; - sfs_oper.readdir = sfs_readdir; - sfs_oper.readdirplus = sfs_readdirplus; - sfs_oper.releasedir = sfs_releasedir; - sfs_oper.fsyncdir = sfs_fsyncdir; - sfs_oper.create = sfs_create; - sfs_oper.tmpfile = sfs_tmpfile; - sfs_oper.open = sfs_open; - sfs_oper.release = sfs_release; - sfs_oper.flush = sfs_flush; - sfs_oper.fsync = sfs_fsync; - sfs_oper.read = sfs_read; - sfs_oper.write_buf = sfs_write_buf; - sfs_oper.statfs = sfs_statfs; +static void assign_operations(fuse_lowlevel_ops &sfs_oper) +{ + sfs_oper.init = sfs_init; + sfs_oper.lookup = sfs_lookup; + sfs_oper.mkdir = sfs_mkdir; + sfs_oper.mknod = sfs_mknod; + sfs_oper.symlink = sfs_symlink; + sfs_oper.link = sfs_link; + sfs_oper.unlink = sfs_unlink; + sfs_oper.rmdir = sfs_rmdir; + sfs_oper.rename = sfs_rename; + sfs_oper.forget = sfs_forget; + sfs_oper.forget_multi = sfs_forget_multi; + sfs_oper.getattr = sfs_getattr; + sfs_oper.setattr = sfs_setattr; + sfs_oper.readlink = sfs_readlink; + sfs_oper.opendir = sfs_opendir; + sfs_oper.readdir = sfs_readdir; + sfs_oper.readdirplus = sfs_readdirplus; + sfs_oper.releasedir = sfs_releasedir; + sfs_oper.fsyncdir = sfs_fsyncdir; + sfs_oper.create = sfs_create; + sfs_oper.tmpfile = sfs_tmpfile; + sfs_oper.open = sfs_open; + sfs_oper.release = sfs_release; + sfs_oper.flush = sfs_flush; + sfs_oper.fsync = sfs_fsync; + sfs_oper.read = sfs_read; + sfs_oper.write_buf = sfs_write_buf; + sfs_oper.statfs = sfs_statfs; #ifdef HAVE_POSIX_FALLOCATE - sfs_oper.fallocate = sfs_fallocate; + sfs_oper.fallocate = sfs_fallocate; #endif - sfs_oper.flock = sfs_flock; + sfs_oper.flock = sfs_flock; #ifdef HAVE_SETXATTR - sfs_oper.setxattr = sfs_setxattr; - sfs_oper.getxattr = sfs_getxattr; - sfs_oper.listxattr = sfs_listxattr; - sfs_oper.removexattr = sfs_removexattr; + sfs_oper.setxattr = sfs_setxattr; + sfs_oper.getxattr = sfs_getxattr; + sfs_oper.listxattr = sfs_listxattr; + sfs_oper.removexattr = sfs_removexattr; #endif } -static void print_usage(char *prog_name) { - cout << "Usage: " << prog_name << " --help\n" - << " " << prog_name << " [options] <source> <mountpoint>\n"; +static void print_usage(char *prog_name) +{ + cout << "Usage: " << prog_name << " --help\n" + << " " << prog_name << " [options] <source> <mountpoint>\n"; } -static cxxopts::ParseResult parse_wrapper(cxxopts::Options& parser, int& argc, char**& argv) { - try { - return parser.parse(argc, argv); - } catch (cxxopts::option_not_exists_exception& exc) { - std::cout << argv[0] << ": " << exc.what() << std::endl; - print_usage(argv[0]); - exit(2); - } +static cxxopts::ParseResult parse_wrapper(cxxopts::Options &parser, int &argc, + char **&argv) +{ + try { + return parser.parse(argc, argv); + } catch (cxxopts::option_not_exists_exception &exc) { + std::cout << argv[0] << ": " << exc.what() << std::endl; + print_usage(argv[0]); + exit(2); + } } +static void string_split(std::string s, std::vector<std::string> &out, + std::string delimiter) +{ + size_t pos_start = 0, pos_end, delim_len = delimiter.length(); + std::string token; -static void string_split(std::string s, std::vector<std::string>& out, std::string delimiter) { - size_t pos_start = 0, pos_end, delim_len = delimiter.length(); - std::string token; - - while ((pos_end = s.find(delimiter, pos_start)) != std::string::npos) { - token = s.substr(pos_start, pos_end - pos_start); - pos_start = pos_end + delim_len; - out.push_back(token); - } + while ((pos_end = s.find(delimiter, pos_start)) != std::string::npos) { + token = s.substr(pos_start, pos_end - pos_start); + pos_start = pos_end + delim_len; + out.push_back(token); + } - out.push_back(s.substr(pos_start)); + out.push_back(s.substr(pos_start)); } - -static std::string string_join(const std::vector<std::string>& elems, char delim) +static std::string string_join(const std::vector<std::string> &elems, + char delim) { - std::ostringstream out; - for (auto ii = elems.begin(); ii != elems.end(); ++ii) { - out << (*ii); - if (ii + 1 != elems.end()) { - out << delim; - } - } - return out.str(); + std::ostringstream out; + for (auto ii = elems.begin(); ii != elems.end(); ++ii) { + out << (*ii); + if (ii + 1 != elems.end()) { + out << delim; + } + } + return out.str(); } - -static cxxopts::ParseResult parse_options(int argc, char **argv) { - cxxopts::Options opt_parser(argv[0]); - std::vector<std::string> mount_options; - opt_parser.add_options() - ("debug", "Enable filesystem debug messages") - ("debug-fuse", "Enable libfuse debug messages") - ("foreground", "Run in foreground") - ("help", "Print help") - ("nocache", "Disable attribute all caching") - ("nosplice", "Do not use splice(2) to transfer data") - ("nopassthrough", "Do not use pass-through mode for read/write") - ("single", "Run single-threaded") - ("o", "Mount options (see mount.fuse(5) - only use if you know what " - "you are doing)", cxxopts::value(mount_options)) - ("num-threads", "Number of libfuse worker threads", - cxxopts::value<int>()->default_value(SFS_DEFAULT_THREADS)) - ("clone-fd", "use separate fuse device fd for each thread") - ("direct-io", "enable fuse kernel internal direct-io"); - - // FIXME: Find a better way to limit the try clause to just - // opt_parser.parse() (cf. https://github.com/jarro2783/cxxopts/issues/146) - auto options = parse_wrapper(opt_parser, argc, argv); - - if (options.count("help")) { - print_usage(argv[0]); - // Strip everything before the option list from the - // default help string. - auto help = opt_parser.help(); - std::cout << std::endl << "options:" - << help.substr(help.find("\n\n") + 1, string::npos); - exit(0); - - } else if (argc != 3) { - std::cout << argv[0] << ": invalid number of arguments\n"; - print_usage(argv[0]); - exit(2); - } - - fs.debug = options.count("debug") != 0; - fs.debug_fuse = options.count("debug-fuse") != 0; - - fs.foreground = options.count("foreground") != 0; - if (fs.debug || fs.debug_fuse) - fs.foreground = true; - - fs.nosplice = options.count("nosplice") != 0; - fs.passthrough = options.count("nopassthrough") == 0; - fs.num_threads = options["num-threads"].as<int>(); - fs.clone_fd = options.count("clone-fd"); - fs.direct_io = options.count("direct-io"); - - char* resolved_path = realpath(argv[1], NULL); - if (resolved_path == NULL) - warn("WARNING: realpath() failed with"); - fs.source = std::string {resolved_path}; - free(resolved_path); - - std::vector<std::string> flattened_mount_opts; - for (auto opt : mount_options) { - string_split(opt, flattened_mount_opts, ","); - } - - bool found_fsname = false; - for (auto opt : flattened_mount_opts) { - if (opt.find("fsname=") == 0) { - found_fsname = true; - continue; - } - - /* Filter out some obviously incorrect options. */ - if (opt == "fd") { - std::cout << argv[0] << ": Unsupported mount option: " << opt << "\n"; - print_usage(argv[0]); - exit(2); - } - } - if (!found_fsname) { - flattened_mount_opts.push_back("fsname=" + fs.source); - } - flattened_mount_opts.push_back("default_permissions"); - fs.fuse_mount_options = string_join(flattened_mount_opts, ','); - return options; +static cxxopts::ParseResult parse_options(int argc, char **argv) +{ + cxxopts::Options opt_parser(argv[0]); + std::vector<std::string> mount_options; + opt_parser.add_options()("debug", "Enable filesystem debug messages")( + "debug-fuse", "Enable libfuse debug messages")( + "foreground", "Run in foreground")("help", "Print help")( + "nocache", "Disable attribute all caching")( + "nosplice", "Do not use splice(2) to transfer data")( + "nopassthrough", "Do not use pass-through mode for read/write")( + "single", "Run single-threaded")( + "o", + "Mount options (see mount.fuse(5) - only use if you know what " + "you are doing)", + cxxopts::value(mount_options))( + "num-threads", "Number of libfuse worker threads", + cxxopts::value<int>()->default_value(SFS_DEFAULT_THREADS))( + "clone-fd", "use separate fuse device fd for each thread")( + "direct-io", "enable fuse kernel internal direct-io"); + + // FIXME: Find a better way to limit the try clause to just + // opt_parser.parse() (cf. https://github.com/jarro2783/cxxopts/issues/146) + auto options = parse_wrapper(opt_parser, argc, argv); + + if (options.count("help")) { + print_usage(argv[0]); + // Strip everything before the option list from the + // default help string. + auto help = opt_parser.help(); + std::cout << std::endl + << "options:" + << help.substr(help.find("\n\n") + 1, string::npos); + exit(0); + + } else if (argc != 3) { + std::cout << argv[0] << ": invalid number of arguments\n"; + print_usage(argv[0]); + exit(2); + } + + fs.debug = options.count("debug") != 0; + fs.debug_fuse = options.count("debug-fuse") != 0; + + fs.foreground = options.count("foreground") != 0; + if (fs.debug || fs.debug_fuse) + fs.foreground = true; + + fs.nosplice = options.count("nosplice") != 0; + fs.passthrough = options.count("nopassthrough") == 0; + fs.num_threads = options["num-threads"].as<int>(); + fs.clone_fd = options.count("clone-fd"); + fs.direct_io = options.count("direct-io"); + + char *resolved_path = realpath(argv[1], NULL); + if (resolved_path == NULL) + warn("WARNING: realpath() failed with"); + fs.source = std::string{ resolved_path }; + free(resolved_path); + + std::vector<std::string> flattened_mount_opts; + for (auto opt : mount_options) { + string_split(opt, flattened_mount_opts, ","); + } + + bool found_fsname = false; + for (auto opt : flattened_mount_opts) { + if (opt.find("fsname=") == 0) { + found_fsname = true; + continue; + } + + /* Filter out some obviously incorrect options. */ + if (opt == "fd") { + std::cout << argv[0] + << ": Unsupported mount option: " << opt + << "\n"; + print_usage(argv[0]); + exit(2); + } + } + if (!found_fsname) { + flattened_mount_opts.push_back("fsname=" + fs.source); + } + flattened_mount_opts.push_back("default_permissions"); + fs.fuse_mount_options = string_join(flattened_mount_opts, ','); + return options; } - -static void maximize_fd_limit() { - struct rlimit lim {}; - auto res = getrlimit(RLIMIT_NOFILE, &lim); - if (res != 0) { - warn("WARNING: getrlimit() failed with"); - return; - } - lim.rlim_cur = lim.rlim_max; - res = setrlimit(RLIMIT_NOFILE, &lim); - if (res != 0) - warn("WARNING: setrlimit() failed with"); +static void maximize_fd_limit() +{ + struct rlimit lim {}; + auto res = getrlimit(RLIMIT_NOFILE, &lim); + if (res != 0) { + warn("WARNING: getrlimit() failed with"); + return; + } + lim.rlim_cur = lim.rlim_max; + res = setrlimit(RLIMIT_NOFILE, &lim); + if (res != 0) + warn("WARNING: setrlimit() failed with"); } +int main(int argc, char *argv[]) +{ + struct fuse_loop_config *loop_config = NULL; -int main(int argc, char *argv[]) { - - struct fuse_loop_config *loop_config = NULL; + // Parse command line options + auto options{ parse_options(argc, argv) }; - // Parse command line options - auto options {parse_options(argc, argv)}; + // We need an fd for every dentry in our the filesystem that the + // kernel knows about. This is way more than most processes need, + // so try to get rid of any resource softlimit. + maximize_fd_limit(); - // We need an fd for every dentry in our the filesystem that the - // kernel knows about. This is way more than most processes need, - // so try to get rid of any resource softlimit. - maximize_fd_limit(); + // Initialize filesystem root + fs.root.fd = -1; + fs.root.nlookup = 9999; + fs.timeout = options.count("nocache") ? 0 : 86400.0; - // Initialize filesystem root - fs.root.fd = -1; - fs.root.nlookup = 9999; - fs.timeout = options.count("nocache") ? 0 : 86400.0; + struct stat stat; + auto ret = lstat(fs.source.c_str(), &stat); + if (ret == -1) + err(1, "ERROR: failed to stat source (\"%s\")", + fs.source.c_str()); + if (!S_ISDIR(stat.st_mode)) + errx(1, "ERROR: source is not a directory"); + fs.src_dev = stat.st_dev; - struct stat stat; - auto ret = lstat(fs.source.c_str(), &stat); - if (ret == -1) - err(1, "ERROR: failed to stat source (\"%s\")", fs.source.c_str()); - if (!S_ISDIR(stat.st_mode)) - errx(1, "ERROR: source is not a directory"); - fs.src_dev = stat.st_dev; + fs.root.fd = open(fs.source.c_str(), O_PATH); + if (fs.root.fd == -1) + err(1, "ERROR: open(\"%s\", O_PATH)", fs.source.c_str()); - fs.root.fd = open(fs.source.c_str(), O_PATH); - if (fs.root.fd == -1) - err(1, "ERROR: open(\"%s\", O_PATH)", fs.source.c_str()); + // Initialize fuse + fuse_args args = FUSE_ARGS_INIT(0, nullptr); + if (fuse_opt_add_arg(&args, argv[0]) || fuse_opt_add_arg(&args, "-o") || + fuse_opt_add_arg(&args, fs.fuse_mount_options.c_str()) || + (fs.debug_fuse && fuse_opt_add_arg(&args, "-odebug"))) + errx(3, "ERROR: Out of memory adding arguments"); - // Initialize fuse - fuse_args args = FUSE_ARGS_INIT(0, nullptr); - if (fuse_opt_add_arg(&args, argv[0]) || - fuse_opt_add_arg(&args, "-o") || - fuse_opt_add_arg(&args, fs.fuse_mount_options.c_str()) || - (fs.debug_fuse && fuse_opt_add_arg(&args, "-odebug"))) - errx(3, "ERROR: Out of memory adding arguments"); + ret = -1; + fuse_lowlevel_ops sfs_oper{}; + assign_operations(sfs_oper); + auto se = fuse_session_new(&args, &sfs_oper, sizeof(sfs_oper), &fs); + if (se == nullptr) + goto err_out1; - ret = -1; - fuse_lowlevel_ops sfs_oper {}; - assign_operations(sfs_oper); - auto se = fuse_session_new(&args, &sfs_oper, sizeof(sfs_oper), &fs); - if (se == nullptr) - goto err_out1; + if (fuse_set_signal_handlers(se) != 0) + goto err_out2; - if (fuse_set_signal_handlers(se) != 0) - goto err_out2; + if (fuse_set_fail_signal_handlers(se) != 0) + goto err_out2; - if (fuse_set_fail_signal_handlers(se) != 0) - goto err_out2; + // Don't apply umask, use modes exactly as specified + umask(0); - // Don't apply umask, use modes exactly as specified - umask(0); + // Mount and run main loop + loop_config = fuse_loop_cfg_create(); - // Mount and run main loop - loop_config = fuse_loop_cfg_create(); + if (fs.num_threads != -1) + fuse_loop_cfg_set_max_threads(loop_config, fs.num_threads); - if (fs.num_threads != -1) - fuse_loop_cfg_set_max_threads(loop_config, fs.num_threads); + fuse_loop_cfg_set_clone_fd(loop_config, fs.clone_fd); - fuse_loop_cfg_set_clone_fd(loop_config, fs.clone_fd); - - if (fuse_session_mount(se, argv[2]) != 0) - goto err_out3; + if (fuse_session_mount(se, argv[2]) != 0) + goto err_out3; - fuse_daemonize(fs.foreground); + fuse_daemonize(fs.foreground); - if (!fs.foreground) - fuse_log_enable_syslog("passthrough-hp", LOG_PID | LOG_CONS, LOG_DAEMON); + if (!fs.foreground) + fuse_log_enable_syslog("passthrough-hp", LOG_PID | LOG_CONS, + LOG_DAEMON); - if (options.count("single")) - ret = fuse_session_loop(se); - else - ret = fuse_session_loop_mt(se, loop_config); + if (options.count("single")) + ret = fuse_session_loop(se); + else + ret = fuse_session_loop_mt(se, loop_config); - fuse_session_unmount(se); + fuse_session_unmount(se); err_out3: - fuse_remove_signal_handlers(se); + fuse_remove_signal_handlers(se); err_out2: - fuse_session_destroy(se); + fuse_session_destroy(se); err_out1: - fuse_loop_cfg_destroy(loop_config); - fuse_opt_free_args(&args); + fuse_loop_cfg_destroy(loop_config); + fuse_opt_free_args(&args); - if (!fs.foreground) - fuse_log_close_syslog(); + if (!fs.foreground) + fuse_log_close_syslog(); - return ret ? 1 : 0; + return ret ? 1 : 0; } - From 410890482c8b134525bb9f006089848399eea62a Mon Sep 17 00:00:00 2001 From: Bernd Schubert <bschubert@ddn.com> Date: Mon, 16 Jun 2025 12:17:26 +0200 Subject: [PATCH 136/241] example/passthrough_hp: Show fuse lowlevel help options Signed-off-by: Bernd Schubert <bschubert@ddn.com> --- example/passthrough_hp.cc | 2 ++ 1 file changed, 2 insertions(+) diff --git a/example/passthrough_hp.cc b/example/passthrough_hp.cc index dd3a45ae9..4f34e2be7 100644 --- a/example/passthrough_hp.cc +++ b/example/passthrough_hp.cc @@ -1455,6 +1455,8 @@ static cxxopts::ParseResult parse_options(int argc, char **argv) std::cout << std::endl << "options:" << help.substr(help.find("\n\n") + 1, string::npos); + std::cout << "\nFuse lowlevel options:\n"; + fuse_lowlevel_help(); exit(0); } else if (argc != 3) { From f4c5d20772145c02f8e78176ae9d6cb6f60c6cfa Mon Sep 17 00:00:00 2001 From: Bernd Schubert <bschubert@ddn.com> Date: Mon, 16 Jun 2025 15:28:26 +0200 Subject: [PATCH 137/241] Fix io-uring teardown We need to write an uint64_t to eventfd. Signed-off-by: Bernd Schubert <bschubert@ddn.com> --- lib/fuse_uring.c | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/lib/fuse_uring.c b/lib/fuse_uring.c index fb5cd8f3f..33f8f5652 100644 --- a/lib/fuse_uring.c +++ b/lib/fuse_uring.c @@ -345,9 +345,15 @@ static void fuse_session_destruct_uring(struct fuse_ring_pool *fuse_ring) fuse_uring_get_queue(fuse_ring, qid); if (queue->tid != 0) { - int value = 1; - - write(queue->eventfd, &value, sizeof(value)); + uint64_t value = 1ULL; + int rc; + + rc = write(queue->eventfd, &value, sizeof(value)); + if (rc != sizeof(value)) + fprintf(stderr, + "Wrote to eventfd=%d err=%s: rc=%d\n", + queue->eventfd, strerror(errno), rc); + pthread_cancel(queue->tid); pthread_join(queue->tid, NULL); queue->tid = 0; } @@ -416,7 +422,7 @@ static int fuse_uring_prepare_fetch_sqes(struct fuse_ring_queue *queue) return -EINVAL; } - // Add the poll SQE for the eventfd to wake up on teardown + /* Poll SQE for the eventfd to wake up on teardown */ sqe = io_uring_get_sqe(&queue->ring); if (sqe == NULL) { fuse_log(FUSE_LOG_ERR, "Failed to get eventfd SQE"); From 3793b1748ad151c8043dee1db198fffa3dbb5a67 Mon Sep 17 00:00:00 2001 From: Georgi Valkov <gvalkov@gmail.com> Date: Sun, 15 Jun 2025 15:34:57 +0300 Subject: [PATCH 138/241] 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> --- 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 5ceb0b05cb48e6cfa2c81160df9f1212623e86eb Mon Sep 17 00:00:00 2001 From: Luis Henriques <luis@igalia.com> Date: Thu, 22 May 2025 15:15:15 +0100 Subject: [PATCH 139/241] fuse: add support to FUSE_NOTIFY_INC_EPOCH This patch adds support for the FUSE INC_EPOCH notify. This new operation simply increments the FUSE connection epoch value, allowing to invalidate all the dentries next time they are revalidated. Signed-off-by: Luis Henriques <luis@igalia.com> --- include/fuse_kernel.h | 6 +++++- include/fuse_lowlevel.h | 14 ++++++++++++++ lib/fuse_lowlevel.c | 13 +++++++++++++ lib/fuse_versionscript | 1 + 4 files changed, 33 insertions(+), 1 deletion(-) diff --git a/include/fuse_kernel.h b/include/fuse_kernel.h index 42db04c0c..122d6586e 100644 --- a/include/fuse_kernel.h +++ b/include/fuse_kernel.h @@ -232,6 +232,9 @@ * * 7.43 * - add FUSE_REQUEST_TIMEOUT + * + * 7.44 + * - add FUSE_NOTIFY_INC_EPOCH */ #ifndef _LINUX_FUSE_H @@ -267,7 +270,7 @@ #define FUSE_KERNEL_VERSION 7 /** Minor version number of this interface */ -#define FUSE_KERNEL_MINOR_VERSION 42 +#define FUSE_KERNEL_MINOR_VERSION 44 /** The node ID of the root inode */ #define FUSE_ROOT_ID 1 @@ -671,6 +674,7 @@ enum fuse_notify_code { FUSE_NOTIFY_RETRIEVE = 5, FUSE_NOTIFY_DELETE = 6, FUSE_NOTIFY_RESEND = 7, + FUSE_NOTIFY_INC_EPOCH = 8, FUSE_NOTIFY_CODE_MAX, }; diff --git a/include/fuse_lowlevel.h b/include/fuse_lowlevel.h index 138a78436..82f4a8de4 100644 --- a/include/fuse_lowlevel.h +++ b/include/fuse_lowlevel.h @@ -1744,6 +1744,20 @@ int fuse_lowlevel_notify_poll(struct fuse_pollhandle *ph); int fuse_lowlevel_notify_inval_inode(struct fuse_session *se, fuse_ino_t ino, off_t off, off_t len); +/** + * Notify to increment the epoch for the current + * + * Each fuse connection has an 'epoch', which is initialized during INIT. + * Caching will then be validated against the epoch value: if the current epoch + * is higher than an object being revalidated, the object is invalid. + * + * This function simply increment the current epoch value. + * + * @param se the session object + * @return zero for success, -errno for failure + */ +int fuse_lowlevel_notify_increment_epoch(struct fuse_session *se); + /** * Notify to invalidate parent attributes and the dentry matching parent/name * diff --git a/lib/fuse_lowlevel.c b/lib/fuse_lowlevel.c index 556878630..13c05c623 100644 --- a/lib/fuse_lowlevel.c +++ b/lib/fuse_lowlevel.c @@ -2973,6 +2973,19 @@ int fuse_lowlevel_notify_inval_inode(struct fuse_session *se, fuse_ino_t ino, return send_notify_iov(se, FUSE_NOTIFY_INVAL_INODE, iov, 2); } +int fuse_lowlevel_notify_increment_epoch(struct fuse_session *se) +{ + struct iovec iov[1]; + + if (!se) + return -EINVAL; + + if (se->conn.proto_minor < 44) + return -ENOSYS; + + return send_notify_iov(se, FUSE_NOTIFY_INC_EPOCH, iov, 1); +} + /** * Notify parent attributes and the dentry matching parent/name * diff --git a/lib/fuse_versionscript b/lib/fuse_versionscript index ab57d7c08..2d8884d7e 100644 --- a/lib/fuse_versionscript +++ b/lib/fuse_versionscript @@ -208,6 +208,7 @@ FUSE_3.18 { fuse_set_feature_flag; fuse_unset_feature_flag; fuse_get_feature_flag; + fuse_lowlevel_notify_increment_epoch; # Not part of public API, for internal test use only fuse_convert_to_conn_want_ext; From 1caf8162d5b08bd32cc311c001c277067bd572c0 Mon Sep 17 00:00:00 2001 From: Luis Henriques <luis@igalia.com> Date: Wed, 18 Jun 2025 10:42:32 +0100 Subject: [PATCH 140/241] tests: example: add new test for increment epoch This patch modifies the notify_inval_entry.c example so that it includes an extra option to use fuse_lowlevel_notify_increment_epoch(). The test test_notify_inval_entry() was also modified to test this extra option. Signed-off-by: Luis Henriques <luis@igalia.com> --- example/notify_inval_entry.c | 47 ++++++++++++++++++++++++++++-------- test/test_examples.py | 7 +++++- 2 files changed, 43 insertions(+), 11 deletions(-) diff --git a/example/notify_inval_entry.c b/example/notify_inval_entry.c index 1060d505e..728a9dccf 100644 --- a/example/notify_inval_entry.c +++ b/example/notify_inval_entry.c @@ -67,6 +67,12 @@ * To use the function fuse_lowlevel_notify_expire_entry() instead of * fuse_lowlevel_notify_inval_entry(), use the command line option --only-expire * + * Another possible command-line option is --inc-epoch, which will use the FUSE + * low-level function fuse_lowlevel_notify_increment_epoch() instead. This will + * function will force the invalidation of all dentries next time they are + * revalidated. Note that --inc-epoch and --only-expire options are mutually + * exclusive. + * * ## Compilation ## * * gcc -Wall notify_inval_entry.c `pkg-config fuse3 --cflags --libs` -o notify_inval_entry @@ -103,12 +109,14 @@ struct options { float timeout; int update_interval; int only_expire; + int inc_epoch; }; static struct options options = { .timeout = 5, .no_notify = 0, .update_interval = 1, .only_expire = 0, + .inc_epoch = 0, }; #define OPTION(t, p) \ @@ -118,6 +126,7 @@ static const struct fuse_opt option_spec[] = { OPTION("--update-interval=%d", update_interval), OPTION("--timeout=%f", timeout), OPTION("--only-expire", only_expire), + OPTION("--inc-epoch", inc_epoch), FUSE_OPT_END }; @@ -263,7 +272,7 @@ static void update_fs(void) { static void* update_fs_loop(void *data) { struct fuse_session *se = (struct fuse_session*) data; char *old_name; - + int ret = 0; while(!fuse_session_exited(se)) { old_name = strdup(file_name); @@ -271,24 +280,27 @@ static void* update_fs_loop(void *data) { if (!options.no_notify && lookup_cnt) { if(options.only_expire) { // expire entry - int ret = fuse_lowlevel_notify_expire_entry - (se, FUSE_ROOT_ID, old_name, strlen(old_name)); + ret = fuse_lowlevel_notify_expire_entry + (se, FUSE_ROOT_ID, old_name, strlen(old_name)); // no kernel support if (ret == -ENOSYS) { printf("fuse_lowlevel_notify_expire_entry not supported by kernel\n"); - printf("Exiting...\n"); - - fuse_session_exit(se); - // Make sure to exit now, rather than on next request from userspace - pthread_kill(main_thread, SIGPIPE); - break; } + // 1) ret == 0: successful expire of an existing entry // 2) ret == -ENOENT: kernel has already expired the entry / // entry does not exist anymore in the kernel assert(ret == 0 || ret == -ENOENT); + } else if (options.inc_epoch) { // increment epoch + ret = fuse_lowlevel_notify_increment_epoch(se); + + if (ret == -ENOSYS) { + printf("fuse_lowlevel_notify_increment_epoch not supported by kernel\n"); + break; + } + assert(ret == 0); } else { // invalidate entry assert(fuse_lowlevel_notify_inval_entry (se, FUSE_ROOT_ID, old_name, strlen(old_name)) == 0); @@ -297,6 +309,15 @@ static void* update_fs_loop(void *data) { free(old_name); sleep(options.update_interval); } + + if (ret == -ENOSYS) { + printf("Exiting...\n"); + + fuse_session_exit(se); + // Make sure to exit now, rather than on next request from userspace + pthread_kill(main_thread, SIGPIPE); + } + return NULL; } @@ -307,7 +328,8 @@ static void show_help(const char *progname) " --timeout=<secs> Timeout for kernel caches\n" " --update-interval=<secs> Update-rate of file system contents\n" " --no-notify Disable kernel notifications\n" - " --only-expire Expire entries instead of invalidating them\n" + " --only-expire Expire entries instead of invalidating them\n" + " --inc-epoch Increment epoch, invalidating all dentries\n" "\n"); } @@ -336,6 +358,11 @@ int main(int argc, char *argv[]) { ret = 0; goto err_out1; } + if (options.only_expire && options.inc_epoch) { + printf("'only-expire' and 'inc-epoch' options are exclusive\n"); + ret = 0; + goto err_out1; + } /* Initial contents */ update_fs(); diff --git a/test/test_examples.py b/test/test_examples.py index 2e23697fd..2658081fc 100755 --- a/test/test_examples.py +++ b/test/test_examples.py @@ -376,7 +376,8 @@ def test_fn(name): @pytest.mark.skipif(fuse_proto < (7,12), reason='not supported by running kernel') -@pytest.mark.parametrize("only_expire", ("invalidate_entries", "expire_entries")) +@pytest.mark.parametrize("only_expire", ("invalidate_entries", + "expire_entries", "inc_epoch")) @pytest.mark.parametrize("notify", (True, False)) def test_notify_inval_entry(tmpdir, only_expire, notify, output_checker): mnt_dir = str(tmpdir) @@ -390,6 +391,10 @@ def test_notify_inval_entry(tmpdir, only_expire, notify, output_checker): cmdline.append('--only-expire') if "FUSE_CAP_EXPIRE_ONLY" not in fuse_caps: pytest.skip('only-expire not supported by running kernel') + elif only_expire == "inc_epoch": + cmdline.append('--inc-epoch') + if fuse_proto < (7,44): + pytest.skip('inc-epoch not supported by running kernel') mount_process = subprocess.Popen(cmdline, stdout=output_checker.fd, stderr=output_checker.fd) try: From 43e36ffed84f73528b67ca1835760bf5895c9d60 Mon Sep 17 00:00:00 2001 From: izxl007 <zeng.zheng@zte.com.cn> Date: Thu, 19 Jun 2025 01:04:33 +0800 Subject: [PATCH 141/241] example/ioctl_client: Delete the redundant comment Signed-off-by: izxl007 <zeng.zheng@zte.com.cn> --- example/ioctl_client.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/example/ioctl_client.c b/example/ioctl_client.c index 019d03031..2d7ac0e1a 100644 --- a/example/ioctl_client.c +++ b/example/ioctl_client.c @@ -3,8 +3,6 @@ Copyright (C) 2008 SUSE Linux Products GmbH Copyright (C) 2008 Tejun Heo <teheo@suse.de> - This program tests the ioctl.c example file systsem. - This program can be distributed under the terms of the GNU GPLv2. See the file COPYING. */ From abd0a98485756caad5e7297669f8788c167b6b2b Mon Sep 17 00:00:00 2001 From: izxl007 <zeng.zheng@zte.com.cn> Date: Sun, 22 Jun 2025 20:00:39 +0800 Subject: [PATCH 142/241] Clarify offset field is signed despite uint64_t type Add comment noting that the uint64_t offset in fuse_lseek_in is derived from kernel loff_t and should be treated as signed for negative offsets. Signed-off-by: izxl007 <zeng.zheng@zte.com.cn> --- lib/fuse_lowlevel.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/lib/fuse_lowlevel.c b/lib/fuse_lowlevel.c index 13c05c623..8ddc288d3 100644 --- a/lib/fuse_lowlevel.c +++ b/lib/fuse_lowlevel.c @@ -2402,6 +2402,10 @@ static void do_copy_file_range(fuse_req_t req, const fuse_ino_t nodeid_in, _do_copy_file_range(req, nodeid_in, inarg, NULL); } +/* + * Note that the uint64_t offset in struct fuse_lseek_in is derived from + * linux kernel loff_t and is therefore signed. + */ static void _do_lseek(fuse_req_t req, const fuse_ino_t nodeid, const void *op_in, const void *in_payload) { From 1016d1665ab0d8914171943fadfe85ed2f8e12ba Mon Sep 17 00:00:00 2001 From: Gleb Popov <6yearold@gmail.com> Date: Tue, 24 Jun 2025 14:46:32 +0300 Subject: [PATCH 143/241] Catch up with fuse_send_data_iov() signature changes in the fallback case Signed-off-by: Gleb Popov <6yearold@gmail.com> --- lib/fuse_lowlevel.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/lib/fuse_lowlevel.c b/lib/fuse_lowlevel.c index 8ddc288d3..722c11d19 100644 --- a/lib/fuse_lowlevel.c +++ b/lib/fuse_lowlevel.c @@ -979,12 +979,13 @@ static int fuse_send_data_iov(struct fuse_session *se, struct fuse_chan *ch, #else static int fuse_send_data_iov(struct fuse_session *se, struct fuse_chan *ch, struct iovec *iov, int iov_count, - struct fuse_bufvec *req_data, unsigned int flags) + struct fuse_bufvec *req_data, unsigned int flags, + fuse_req_t req) { size_t len = fuse_buf_size(req_data); (void) flags; - return fuse_send_data_iov_fallback(se, ch, iov, iov_count, req_data, len); + return fuse_send_data_iov_fallback(se, ch, iov, iov_count, req_data, len, req); } #endif From 596732dd06f7737e4a4c050773f88c8d36fd9b61 Mon Sep 17 00:00:00 2001 From: Gleb Popov <6yearold@gmail.com> Date: Tue, 24 Jun 2025 09:12:54 +0300 Subject: [PATCH 144/241] meson: Check for some Linux-specific headers Signed-off-by: Gleb Popov <6yearold@gmail.com> --- meson.build | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/meson.build b/meson.build index b6631098d..910a07565 100644 --- a/meson.build +++ b/meson.build @@ -112,6 +112,13 @@ foreach name, code : special_funcs name: name + ' check')) endforeach +# Headers checks +test_headers = [ 'sys/xattr.h', 'linux/limits.h' ] +foreach header : test_headers + private_cfg.set('HAVE_' + header.underscorify().to_upper(), + cc.has_header(header)) +endforeach + # Regular function checks private_cfg.set('HAVE_SETXATTR', cc.has_function('setxattr', prefix: '#include <sys/xattr.h>')) From e9a140b8172c312981b2bb834011ef782a386504 Mon Sep 17 00:00:00 2001 From: Gleb Popov <6yearold@gmail.com> Date: Tue, 24 Jun 2025 09:17:42 +0300 Subject: [PATCH 145/241] examples: Guard inclusions of Linux headers with appropriate ifdefs Signed-off-by: Gleb Popov <6yearold@gmail.com> --- example/memfs_ll.cc | 4 +++- example/passthrough_hp.cc | 2 ++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/example/memfs_ll.cc b/example/memfs_ll.cc index 603885017..68f8e9629 100644 --- a/example/memfs_ll.cc +++ b/example/memfs_ll.cc @@ -6,7 +6,6 @@ See the file COPYING. */ -#include <linux/limits.h> #define FUSE_USE_VERSION 317 #include <algorithm> @@ -28,6 +27,9 @@ #include <string_view> #include <cstdint> #include <fuse_lowlevel.h> +#ifdef HAVE_LINUX_LIMITS_H +#include <linux/limits.h> +#endif #define MEMFS_ATTR_TIMEOUT 0.0 #define MEMFS_ENTRY_TIMEOUT 0.0 diff --git a/example/passthrough_hp.cc b/example/passthrough_hp.cc index 4f34e2be7..65377b95e 100644 --- a/example/passthrough_hp.cc +++ b/example/passthrough_hp.cc @@ -59,7 +59,9 @@ #include <string.h> #include <sys/file.h> #include <sys/resource.h> +#ifdef HAVE_SYS_XATTR_H #include <sys/xattr.h> +#endif #include <time.h> #include <unistd.h> #include <pthread.h> From 9168e3be310b9805c269bf85580e46bf30cd63b5 Mon Sep 17 00:00:00 2001 From: Gleb Popov <6yearold@gmail.com> Date: Tue, 24 Jun 2025 09:20:09 +0300 Subject: [PATCH 146/241] memfs_ll: Fix compiling on non-Linux OSes Signed-off-by: Gleb Popov <6yearold@gmail.com> --- example/memfs_ll.cc | 2 ++ 1 file changed, 2 insertions(+) diff --git a/example/memfs_ll.cc b/example/memfs_ll.cc index 68f8e9629..ad0b1871c 100644 --- a/example/memfs_ll.cc +++ b/example/memfs_ll.cc @@ -905,10 +905,12 @@ static void memfs_rename(fuse_req_t req, fuse_ino_t parent, const char *name, Dentry *child_dentry_copy = nullptr; Dentry *existing_dentry = nullptr; +#if defined(RENAME_EXCHANGE) && defined(RENAME_NOREPLACE) if (flags & (RENAME_EXCHANGE | RENAME_NOREPLACE)) { fuse_reply_err(req, EINVAL); return; } +#endif Inodes.lock(); From f8fe398ee14864e2c3c7c524ca851d7cc08bed28 Mon Sep 17 00:00:00 2001 From: Gleb Popov <6yearold@gmail.com> Date: Tue, 24 Jun 2025 14:36:40 +0300 Subject: [PATCH 147/241] passthrough_hp: Abstract out the fd -> path transition Signed-off-by: Gleb Popov <6yearold@gmail.com> --- example/passthrough_hp.cc | 43 ++++++++++++++++++++++++++++----------- 1 file changed, 31 insertions(+), 12 deletions(-) diff --git a/example/passthrough_hp.cc b/example/passthrough_hp.cc index 65377b95e..040bc7548 100644 --- a/example/passthrough_hp.cc +++ b/example/passthrough_hp.cc @@ -66,6 +66,10 @@ #include <unistd.h> #include <pthread.h> #include <limits.h> +#ifdef __FreeBSD__ +#include <fcntl.h> +#include <sys/user.h> +#endif // C++ includes #include <cstddef> @@ -248,6 +252,21 @@ static void sfs_getattr(fuse_req_t req, fuse_ino_t ino, fuse_file_info *fi) fuse_reply_attr(req, &attr, fs.timeout); } +static int with_fd_path(int fd, const std::function<int(const char*)>& f) +{ +#ifdef __FreeBSD__ + struct kinfo_file kf; + kf.kf_structsize = sizeof(kf); + int ret = fcntl(fd, F_KINFO, &kf); + if (ret == -1) + return ret; + return f (kf.kf_path); +#else // Linux + char procname[64]; + sprintf(procname, "/proc/self/fd/%i", fd); + return f(procname); +#endif +} static void do_setattr(fuse_req_t req, fuse_ino_t ino, struct stat *attr, int valid, struct fuse_file_info *fi) { @@ -259,9 +278,9 @@ static void do_setattr(fuse_req_t req, fuse_ino_t ino, struct stat *attr, if (fi) { res = fchmod(fi->fh, attr->st_mode); } else { - char procname[64]; - sprintf(procname, "/proc/self/fd/%i", ifd); - res = chmod(procname, attr->st_mode); + res = with_fd_path(ifd, [attr](const char* procname) { + return chmod(procname, attr->st_mode); + }); } if (res == -1) goto out_err; @@ -283,9 +302,9 @@ static void do_setattr(fuse_req_t req, fuse_ino_t ino, struct stat *attr, if (fi) { res = ftruncate(fi->fh, attr->st_size); } else { - char procname[64]; - sprintf(procname, "/proc/self/fd/%i", ifd); - res = truncate(procname, attr->st_size); + res = with_fd_path(ifd, [attr](const char* procname) { + return truncate(procname, attr->st_size); + }); } if (res == -1) goto out_err; @@ -312,9 +331,9 @@ static void do_setattr(fuse_req_t req, fuse_ino_t ino, struct stat *attr, res = futimens(fi->fh, tv); else { #ifdef HAVE_UTIMENSAT - char procname[64]; - sprintf(procname, "/proc/self/fd/%i", ifd); - res = utimensat(AT_FDCWD, procname, tv, 0); + res = with_fd_path(ifd, [&tv](const char* procname) { + return utimensat(AT_FDCWD, procname, tv, 0); + }); #else res = -1; errno = EOPNOTSUPP; @@ -1067,9 +1086,9 @@ static void sfs_open(fuse_req_t req, fuse_ino_t ino, fuse_file_info *fi) /* Unfortunately we cannot use inode.fd, because this was opened with O_PATH (so it doesn't allow read/write access). */ - char buf[64]; - sprintf(buf, "/proc/self/fd/%i", inode.fd); - auto fd = open(buf, fi->flags & ~O_NOFOLLOW); + auto fd = with_fd_path(inode.fd, [fi](const char* buf) { + return open(buf, fi->flags & ~O_NOFOLLOW); + }); if (fd == -1) { auto err = errno; if (err == ENFILE || err == EMFILE) From 8e649e8f5dbb1d619619643296b75db8ee63aa70 Mon Sep 17 00:00:00 2001 From: Gleb Popov <6yearold@gmail.com> Date: Tue, 24 Jun 2025 14:37:07 +0300 Subject: [PATCH 148/241] passthrough_hp: Fix compilation on systems without O_TMPFILE support Signed-off-by: Gleb Popov <6yearold@gmail.com> --- example/passthrough_hp.cc | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/example/passthrough_hp.cc b/example/passthrough_hp.cc index 040bc7548..e5edc1b86 100644 --- a/example/passthrough_hp.cc +++ b/example/passthrough_hp.cc @@ -1012,6 +1012,7 @@ static Inode *create_new_inode(int fd, fuse_entry_param *e) return p_inode; } +#ifdef O_TMPFILE static void sfs_tmpfile(fuse_req_t req, fuse_ino_t parent, mode_t mode, fuse_file_info *fi) { @@ -1049,7 +1050,7 @@ static void sfs_tmpfile(fuse_req_t req, fuse_ino_t parent, mode_t mode, fuse_reply_create(req, &e, fi); } - +#endif static void sfs_fsyncdir(fuse_req_t req, fuse_ino_t ino, int datasync, fuse_file_info *fi) { @@ -1378,7 +1379,9 @@ static void assign_operations(fuse_lowlevel_ops &sfs_oper) sfs_oper.releasedir = sfs_releasedir; sfs_oper.fsyncdir = sfs_fsyncdir; sfs_oper.create = sfs_create; +#ifdef O_TMPFILE sfs_oper.tmpfile = sfs_tmpfile; +#endif sfs_oper.open = sfs_open; sfs_oper.release = sfs_release; sfs_oper.flush = sfs_flush; From f0ec646e87d726b6c10c951e8bf5b7f5e16cc38f Mon Sep 17 00:00:00 2001 From: Gleb Popov <6yearold@gmail.com> Date: Tue, 24 Jun 2025 14:37:34 +0300 Subject: [PATCH 149/241] meson: Enable building C++ examples on FreeBSD Signed-off-by: Gleb Popov <6yearold@gmail.com> --- example/meson.build | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/example/meson.build b/example/meson.build index b2e896cc4..701afa768 100644 --- a/example/meson.build +++ b/example/meson.build @@ -31,7 +31,7 @@ foreach ex : threaded_examples install: false) endforeach -if not platform.endswith('bsd') and platform != 'dragonfly' and add_languages('cpp', required : false) +if platform != 'dragonfly' and add_languages('cpp', required : false) executable('passthrough_hp', 'passthrough_hp.cc', dependencies: [ thread_dep, libfuse_dep ], install: false) From 57227e4fb6c1d3ba67d0df60d03ba6f485562978 Mon Sep 17 00:00:00 2001 From: Vassili Tchersky <vt+git@vbc.su> Date: Thu, 20 Feb 2025 20:44:47 +0100 Subject: [PATCH 150/241] FreeBSD: test runner Draft version of a yet simple runner that just build libfuse under FreeBSD and show the compile log. --- .github/workflows/bsd.yaml | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100644 .github/workflows/bsd.yaml diff --git a/.github/workflows/bsd.yaml b/.github/workflows/bsd.yaml new file mode 100644 index 000000000..e07d543dd --- /dev/null +++ b/.github/workflows/bsd.yaml @@ -0,0 +1,33 @@ +--- +# TODO: integrate into matrix.os in pr-ci.yml +# TODO: add NetBSD and DragonFlyBSD +name: 'Build (FreeBSD)' +on: + push: + branches: + - master + - 'fuse-[0-9]+.[0-9]+*' + pull_request: + branches: + - master + - 'fuse-[0-9]+.[0-9]+*' + +jobs: + build_bsd: + runs-on: ubuntu-latest + name: Build under FreeBSD + steps: + - name: Checkout + uses: actions/checkout@v4 + - name: Build + uses: vmactions/freebsd-vm@v1 + with: + usesh: true + prepare: | + pkg install -y meson ninja + run: | + mkdir build + cd build + meson setup .. + ninja -v +... \ No newline at end of file From 1bb2f2a08e6808a5d48ac57a6ebb422620d587c4 Mon Sep 17 00:00:00 2001 From: Gleb Popov <6yearold@gmail.com> Date: Wed, 25 Jun 2025 14:37:01 +0300 Subject: [PATCH 151/241] memfs_ll: Fix unused variable warning Signed-off-by: Gleb Popov <6yearold@gmail.com> --- example/memfs_ll.cc | 2 ++ 1 file changed, 2 insertions(+) diff --git a/example/memfs_ll.cc b/example/memfs_ll.cc index ad0b1871c..a1bd0543f 100644 --- a/example/memfs_ll.cc +++ b/example/memfs_ll.cc @@ -910,6 +910,8 @@ static void memfs_rename(fuse_req_t req, fuse_ino_t parent, const char *name, fuse_reply_err(req, EINVAL); return; } +#else + (void)flags; #endif Inodes.lock(); From 4de3db413cb419829203d614fda1cea8fe7e0c57 Mon Sep 17 00:00:00 2001 From: Gleb Popov <6yearold@gmail.com> Date: Wed, 25 Jun 2025 14:39:02 +0300 Subject: [PATCH 152/241] passthrough_hp: Fix unused function warning Signed-off-by: Gleb Popov <6yearold@gmail.com> --- example/passthrough_hp.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/example/passthrough_hp.cc b/example/passthrough_hp.cc index e5edc1b86..ecd862521 100644 --- a/example/passthrough_hp.cc +++ b/example/passthrough_hp.cc @@ -967,6 +967,7 @@ static void sfs_create(fuse_req_t req, fuse_ino_t parent, const char *name, fuse_reply_create(req, &e, fi); } +#ifdef O_TMPFILE static Inode *create_new_inode(int fd, fuse_entry_param *e) { memset(e, 0, sizeof(*e)); @@ -1012,7 +1013,6 @@ static Inode *create_new_inode(int fd, fuse_entry_param *e) return p_inode; } -#ifdef O_TMPFILE static void sfs_tmpfile(fuse_req_t req, fuse_ino_t parent, mode_t mode, fuse_file_info *fi) { From fc1c8da0cf8a18d222cb1feed0057ba44ea4d18f Mon Sep 17 00:00:00 2001 From: izxl007 <zeng.zheng@zte.com.cn> Date: Thu, 19 Jun 2025 22:37:48 +0800 Subject: [PATCH 153/241] license: s/COPYING/GPL2.txt, s/COPYING.LIB/LGPL2.txt Signed-off-by: izxl007 <zeng.zheng@zte.com.cn> --- example/cuse.c | 2 +- example/cuse_client.c | 2 +- example/hello.c | 2 +- example/hello_ll.c | 2 +- example/hello_ll_uds.c | 2 +- example/invalidate_path.c | 2 +- example/ioctl.c | 2 +- example/ioctl.h | 2 +- example/ioctl_client.c | 2 +- example/memfs_ll.cc | 2 +- example/notify_inval_entry.c | 2 +- example/notify_inval_inode.c | 2 +- example/notify_store_retrieve.c | 2 +- example/null.c | 2 +- example/passthrough.c | 2 +- example/passthrough_fh.c | 2 +- example/passthrough_hp.cc | 2 +- example/passthrough_ll.c | 2 +- example/poll.c | 2 +- example/poll_client.c | 2 +- example/printcap.c | 2 +- include/cuse_lowlevel.h | 2 +- include/fuse.h | 2 +- include/fuse_common.h | 2 +- include/fuse_log.h | 2 +- include/fuse_lowlevel.h | 2 +- include/fuse_opt.h | 2 +- lib/buffer.c | 2 +- lib/compat.c | 2 +- lib/cuse_lowlevel.c | 2 +- lib/fuse.c | 2 +- lib/fuse_i.h | 2 +- lib/fuse_log.c | 2 +- lib/fuse_loop.c | 2 +- lib/fuse_loop_mt.c | 2 +- lib/fuse_lowlevel.c | 2 +- lib/fuse_misc.h | 2 +- lib/fuse_opt.c | 2 +- lib/fuse_signals.c | 2 +- lib/fuse_uring.c | 2 +- lib/fuse_uring_i.h | 2 +- lib/helper.c | 2 +- lib/modules/iconv.c | 2 +- lib/modules/subdir.c | 2 +- lib/mount.c | 2 +- lib/mount_bsd.c | 2 +- lib/mount_util.c | 2 +- lib/mount_util.h | 2 +- test/hello.c | 2 +- test/release_unlink_race.c | 2 +- test/test_setattr.c | 2 +- test/test_signals.c | 2 +- test/test_write_cache.c | 2 +- util/fusermount.c | 2 +- util/mount.fuse.c | 2 +- 55 files changed, 55 insertions(+), 55 deletions(-) diff --git a/example/cuse.c b/example/cuse.c index 6b333029f..92b6b09a5 100644 --- a/example/cuse.c +++ b/example/cuse.c @@ -4,7 +4,7 @@ Copyright (C) 2008-2009 Tejun Heo <tj@kernel.org> This program can be distributed under the terms of the GNU GPLv2. - See the file COPYING. + See the file GPL2.txt. */ diff --git a/example/cuse_client.c b/example/cuse_client.c index 903ffc63f..de8244473 100644 --- a/example/cuse_client.c +++ b/example/cuse_client.c @@ -4,7 +4,7 @@ Copyright (C) 2008 Tejun Heo <teheo@suse.de> This program can be distributed under the terms of the GNU GPLv2. - See the file COPYING. + See the file GPL2.txt. */ /** @file diff --git a/example/hello.c b/example/hello.c index d9f01b9f0..6288162d8 100644 --- a/example/hello.c +++ b/example/hello.c @@ -3,7 +3,7 @@ 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. + See the file GPL2.txt. */ /** @file diff --git a/example/hello_ll.c b/example/hello_ll.c index b1ef69275..bd5fecdcd 100644 --- a/example/hello_ll.c +++ b/example/hello_ll.c @@ -3,7 +3,7 @@ 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. + See the file GPL2.txt. */ /** @file diff --git a/example/hello_ll_uds.c b/example/hello_ll_uds.c index e9cd17315..b0a6e9cd4 100644 --- a/example/hello_ll_uds.c +++ b/example/hello_ll_uds.c @@ -4,7 +4,7 @@ Copyright (C) 2022 Tofik Sonono <tofik.sonono@intel.com> This program can be distributed under the terms of the GNU GPLv2. - See the file COPYING. + See the file GPL2.txt. */ /** @file diff --git a/example/invalidate_path.c b/example/invalidate_path.c index 0e8d77f86..fad43ea77 100644 --- a/example/invalidate_path.c +++ b/example/invalidate_path.c @@ -4,7 +4,7 @@ (C) 2017 EditShare LLC <slawek.rudnicki@editshare.com> This program can be distributed under the terms of the GNU GPLv2. - See the file COPYING. + See the file GPL2.txt. */ /** @file diff --git a/example/ioctl.c b/example/ioctl.c index 9fe5c5bc5..2548fd3f0 100644 --- a/example/ioctl.c +++ b/example/ioctl.c @@ -4,7 +4,7 @@ Copyright (C) 2008 Tejun Heo <teheo@suse.de> This program can be distributed under the terms of the GNU GPLv2. - See the file COPYING. + See the file GPL2.txt. */ /** @file diff --git a/example/ioctl.h b/example/ioctl.h index a4f054cc3..ca7e97541 100644 --- a/example/ioctl.h +++ b/example/ioctl.h @@ -4,7 +4,7 @@ Copyright (C) 2008 Tejun Heo <teheo@suse.de> This program can be distributed under the terms of the GNU GPLv2. - See the file COPYING. + See the file GPL2.txt. */ /** @file diff --git a/example/ioctl_client.c b/example/ioctl_client.c index 2d7ac0e1a..99e27505e 100644 --- a/example/ioctl_client.c +++ b/example/ioctl_client.c @@ -4,7 +4,7 @@ Copyright (C) 2008 Tejun Heo <teheo@suse.de> This program can be distributed under the terms of the GNU GPLv2. - See the file COPYING. + See the file GPL2.txt. */ /** @file diff --git a/example/memfs_ll.cc b/example/memfs_ll.cc index a1bd0543f..b4f0b63cb 100644 --- a/example/memfs_ll.cc +++ b/example/memfs_ll.cc @@ -3,7 +3,7 @@ Copyright (C) 2024 DataDirect Networks. This program can be distributed under the terms of the GNU GPLv2. - See the file COPYING. + See the file GPL2.txt. */ #define FUSE_USE_VERSION 317 diff --git a/example/notify_inval_entry.c b/example/notify_inval_entry.c index 728a9dccf..5ab3c32d1 100644 --- a/example/notify_inval_entry.c +++ b/example/notify_inval_entry.c @@ -3,7 +3,7 @@ Copyright (C) 2016 Nikolaus Rath <Nikolaus@rath.org> This program can be distributed under the terms of the GNU GPLv2. - See the file COPYING. + See the file GPL2.txt. */ /** @file diff --git a/example/notify_inval_inode.c b/example/notify_inval_inode.c index 99631cc3d..1dcecee14 100644 --- a/example/notify_inval_inode.c +++ b/example/notify_inval_inode.c @@ -3,7 +3,7 @@ Copyright (C) 2016 Nikolaus Rath <Nikolaus@rath.org> This program can be distributed under the terms of the GNU GPLv2. - See the file COPYING. + See the file GPL2.txt. */ /** @file diff --git a/example/notify_store_retrieve.c b/example/notify_store_retrieve.c index 42e6eb05e..23fdecc28 100644 --- a/example/notify_store_retrieve.c +++ b/example/notify_store_retrieve.c @@ -3,7 +3,7 @@ Copyright (C) 2016 Nikolaus Rath <Nikolaus@rath.org> This program can be distributed under the terms of the GNU GPLv2. - See the file COPYING. + See the file GPL2.txt. */ /** @file diff --git a/example/null.c b/example/null.c index 2dd5e8bdd..ec41def40 100644 --- a/example/null.c +++ b/example/null.c @@ -3,7 +3,7 @@ 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. + See the file GPL2.txt. */ /** @file diff --git a/example/passthrough.c b/example/passthrough.c index cc93f53ee..a5ac4b356 100644 --- a/example/passthrough.c +++ b/example/passthrough.c @@ -4,7 +4,7 @@ Copyright (C) 2011 Sebastian Pipping <sebastian@pipping.org> This program can be distributed under the terms of the GNU GPLv2. - See the file COPYING. + See the file GPL2.txt. */ /** @file diff --git a/example/passthrough_fh.c b/example/passthrough_fh.c index 3602c969f..7bbdb700d 100644 --- a/example/passthrough_fh.c +++ b/example/passthrough_fh.c @@ -4,7 +4,7 @@ Copyright (C) 2011 Sebastian Pipping <sebastian@pipping.org> This program can be distributed under the terms of the GNU GPLv2. - See the file COPYING. + See the file GPL2.txt. */ /** @file diff --git a/example/passthrough_hp.cc b/example/passthrough_hp.cc index ecd862521..cc6b7e8d3 100644 --- a/example/passthrough_hp.cc +++ b/example/passthrough_hp.cc @@ -5,7 +5,7 @@ Copyright (C) 2018 Valve, Inc This program can be distributed under the terms of the GNU GPLv2. - See the file COPYING. + See the file GPL2.txt. */ /** @file diff --git a/example/passthrough_ll.c b/example/passthrough_ll.c index fa8abb897..59f43c53c 100644 --- a/example/passthrough_ll.c +++ b/example/passthrough_ll.c @@ -3,7 +3,7 @@ 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. + See the file GPL2.txt. */ /** @file diff --git a/example/poll.c b/example/poll.c index ffcb4f1fa..cdcdd87de 100644 --- a/example/poll.c +++ b/example/poll.c @@ -4,7 +4,7 @@ Copyright (C) 2008 Tejun Heo <teheo@suse.de> This program can be distributed under the terms of the GNU GPLv2. - See the file COPYING. + See the file GPL2.txt. */ /** @file diff --git a/example/poll_client.c b/example/poll_client.c index 83c58239c..96f14190f 100644 --- a/example/poll_client.c +++ b/example/poll_client.c @@ -4,7 +4,7 @@ Copyright (C) 2008 Tejun Heo <teheo@suse.de> This program can be distributed under the terms of the GNU GPLv2. - See the file COPYING. + See the file GPL2.txt. */ /** @file diff --git a/example/printcap.c b/example/printcap.c index 82a759884..4c59582f9 100644 --- a/example/printcap.c +++ b/example/printcap.c @@ -3,7 +3,7 @@ Copyright (C) 2017 Nikolaus Rath <Nikolaus@rath.org> This program can be distributed under the terms of the GNU GPLv2. - See the file COPYING. + See the file GPL2.txt. */ /** @file diff --git a/include/cuse_lowlevel.h b/include/cuse_lowlevel.h index 80476c20b..fe0b6f2da 100644 --- a/include/cuse_lowlevel.h +++ b/include/cuse_lowlevel.h @@ -4,7 +4,7 @@ Copyright (C) 2008-2009 Tejun Heo <tj@kernel.org> This program can be distributed under the terms of the GNU LGPLv2. - See the file COPYING.LIB. + See the file LGPL2.txt. Read example/cusexmp.c for usages. */ diff --git a/include/fuse.h b/include/fuse.h index 4582cc7ac..b99004334 100644 --- a/include/fuse.h +++ b/include/fuse.h @@ -3,7 +3,7 @@ Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu> This program can be distributed under the terms of the GNU LGPLv2. - See the file COPYING.LIB. + See the file LGPL2.txt. */ #ifndef FUSE_H_ diff --git a/include/fuse_common.h b/include/fuse_common.h index 054c61874..b82f2c41d 100644 --- a/include/fuse_common.h +++ b/include/fuse_common.h @@ -2,7 +2,7 @@ Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu> This program can be distributed under the terms of the GNU LGPLv2. - See the file COPYING.LIB. + See the file LGPL2.txt. */ /** @file */ diff --git a/include/fuse_log.h b/include/fuse_log.h index c8559572a..b901db252 100644 --- a/include/fuse_log.h +++ b/include/fuse_log.h @@ -3,7 +3,7 @@ Copyright (C) 2019 Red Hat, Inc. This program can be distributed under the terms of the GNU LGPLv2. - See the file COPYING.LIB. + See the file LGPL2.txt. */ #ifndef FUSE_LOG_H_ diff --git a/include/fuse_lowlevel.h b/include/fuse_lowlevel.h index 82f4a8de4..75e084d09 100644 --- a/include/fuse_lowlevel.h +++ b/include/fuse_lowlevel.h @@ -3,7 +3,7 @@ Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu> This program can be distributed under the terms of the GNU LGPLv2. - See the file COPYING.LIB. + See the file LGPL2.txt. */ #ifndef FUSE_LOWLEVEL_H_ diff --git a/include/fuse_opt.h b/include/fuse_opt.h index d8573e74f..a9a41fe80 100644 --- a/include/fuse_opt.h +++ b/include/fuse_opt.h @@ -3,7 +3,7 @@ Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu> This program can be distributed under the terms of the GNU LGPLv2. - See the file COPYING.LIB. + See the file LGPL2.txt. */ #ifndef FUSE_OPT_H_ diff --git a/lib/buffer.c b/lib/buffer.c index 6375433ee..2b72d95a0 100644 --- a/lib/buffer.c +++ b/lib/buffer.c @@ -6,7 +6,7 @@ fuse_bufvec`. This program can be distributed under the terms of the GNU LGPLv2. - See the file COPYING.LIB + See the file LGPL2.txt */ #define _GNU_SOURCE diff --git a/lib/compat.c b/lib/compat.c index b98ca4b04..5fac4155c 100644 --- a/lib/compat.c +++ b/lib/compat.c @@ -7,7 +7,7 @@ file system by implementing nothing but the request handlers. This program can be distributed under the terms of the GNU LGPLv2. - See the file COPYING.LIB. + See the file LGPL2.txt. */ /* Description: diff --git a/lib/cuse_lowlevel.c b/lib/cuse_lowlevel.c index a25711bfc..0bc2e66be 100644 --- a/lib/cuse_lowlevel.c +++ b/lib/cuse_lowlevel.c @@ -4,7 +4,7 @@ Copyright (C) 2008 Tejun Heo <teheo@suse.de> This program can be distributed under the terms of the GNU LGPLv2. - See the file COPYING.LIB. + See the file LGPL2.txt. */ #include "fuse_config.h" diff --git a/lib/fuse.c b/lib/fuse.c index 1f01351c5..68b61ce69 100644 --- a/lib/fuse.c +++ b/lib/fuse.c @@ -6,7 +6,7 @@ API. This program can be distributed under the terms of the GNU LGPLv2. - See the file COPYING.LIB + See the file LGPL2.txt */ #define _GNU_SOURCE diff --git a/lib/fuse_i.h b/lib/fuse_i.h index 80ec80315..2221cf2af 100644 --- a/lib/fuse_i.h +++ b/lib/fuse_i.h @@ -3,7 +3,7 @@ Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu> This program can be distributed under the terms of the GNU LGPLv2. - See the file COPYING.LIB + See the file LGPL2.txt */ #ifndef LIB_FUSE_I_H_ diff --git a/lib/fuse_log.c b/lib/fuse_log.c index 5039b0454..66dcf8a06 100644 --- a/lib/fuse_log.c +++ b/lib/fuse_log.c @@ -5,7 +5,7 @@ Logging API. This program can be distributed under the terms of the GNU LGPLv2. - See the file COPYING.LIB + See the file LGPL2.txt */ #include "fuse_log.h" diff --git a/lib/fuse_loop.c b/lib/fuse_loop.c index a27111ca8..1ff075cbf 100644 --- a/lib/fuse_loop.c +++ b/lib/fuse_loop.c @@ -5,7 +5,7 @@ Implementation of the single-threaded FUSE session loop. This program can be distributed under the terms of the GNU LGPLv2. - See the file COPYING.LIB + See the file LGPL2.txt */ #include "fuse_config.h" diff --git a/lib/fuse_loop_mt.c b/lib/fuse_loop_mt.c index e75580cf1..8ee7d49ae 100644 --- a/lib/fuse_loop_mt.c +++ b/lib/fuse_loop_mt.c @@ -5,7 +5,7 @@ Implementation of the multi-threaded FUSE session loop. This program can be distributed under the terms of the GNU LGPLv2. - See the file COPYING.LIB. + See the file LGPL2.txt. */ #define _GNU_SOURCE diff --git a/lib/fuse_lowlevel.c b/lib/fuse_lowlevel.c index 722c11d19..91f42440f 100644 --- a/lib/fuse_lowlevel.c +++ b/lib/fuse_lowlevel.c @@ -6,7 +6,7 @@ functions are implemented in separate files. This program can be distributed under the terms of the GNU LGPLv2. - See the file COPYING.LIB + See the file LGPL2.txt */ #define _GNU_SOURCE diff --git a/lib/fuse_misc.h b/lib/fuse_misc.h index 855edc326..1452593c3 100644 --- a/lib/fuse_misc.h +++ b/lib/fuse_misc.h @@ -3,7 +3,7 @@ Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu> This program can be distributed under the terms of the GNU LGPLv2. - See the file COPYING.LIB + See the file LGPL2.txt */ #include <pthread.h> diff --git a/lib/fuse_opt.c b/lib/fuse_opt.c index 1d3b6a1a5..046e57718 100644 --- a/lib/fuse_opt.c +++ b/lib/fuse_opt.c @@ -6,7 +6,7 @@ fuse_args`). This program can be distributed under the terms of the GNU LGPLv2. - See the file COPYING.LIB + See the file LGPL2.txt */ #include "fuse_config.h" diff --git a/lib/fuse_signals.c b/lib/fuse_signals.c index 53129f0ca..03b0d8f11 100644 --- a/lib/fuse_signals.c +++ b/lib/fuse_signals.c @@ -5,7 +5,7 @@ Utility functions for setting signal handlers. This program can be distributed under the terms of the GNU LGPLv2. - See the file COPYING.LIB + See the file LGPL2.txt */ #include "fuse_config.h" diff --git a/lib/fuse_uring.c b/lib/fuse_uring.c index 33f8f5652..a54973eed 100644 --- a/lib/fuse_uring.c +++ b/lib/fuse_uring.c @@ -5,7 +5,7 @@ * Implementation of (most of) FUSE-over-io-uring. * * This program can be distributed under the terms of the GNU LGPLv2. - * See the file COPYING.LIB + * See the file LGPL2.txt */ #define _GNU_SOURCE diff --git a/lib/fuse_uring_i.h b/lib/fuse_uring_i.h index a65241798..fc236917b 100644 --- a/lib/fuse_uring_i.h +++ b/lib/fuse_uring_i.h @@ -2,7 +2,7 @@ * FUSE: Filesystem in Userspace * Copyright (C) 2025 Bernd Schubert <bschubert@ddn.com> * This program can be distributed under the terms of the GNU LGPLv2. - * See the file COPYING.LIB + * See the file LGPL2.txt */ #ifndef FUSE_URING_I_H_ diff --git a/lib/helper.c b/lib/helper.c index aceff9fd5..5c13b93a4 100644 --- a/lib/helper.c +++ b/lib/helper.c @@ -7,7 +7,7 @@ file system by implementing nothing but the request handlers. This program can be distributed under the terms of the GNU LGPLv2. - See the file COPYING.LIB. + See the file LGPL2.txt. */ #include "fuse_config.h" diff --git a/lib/modules/iconv.c b/lib/modules/iconv.c index a0bf72be5..599b8df4d 100644 --- a/lib/modules/iconv.c +++ b/lib/modules/iconv.c @@ -3,7 +3,7 @@ Copyright (C) 2007 Miklos Szeredi <miklos@szeredi.hu> This program can be distributed under the terms of the GNU LGPLv2. - See the file COPYING.LIB + See the file LGPL2.txt */ #include <fuse_config.h> diff --git a/lib/modules/subdir.c b/lib/modules/subdir.c index e92eb6284..dd2d49d80 100644 --- a/lib/modules/subdir.c +++ b/lib/modules/subdir.c @@ -3,7 +3,7 @@ Copyright (C) 2007 Miklos Szeredi <miklos@szeredi.hu> This program can be distributed under the terms of the GNU LGPLv2. - See the file COPYING.LIB + See the file LGPL2.txt */ #include <fuse_config.h> diff --git a/lib/mount.c b/lib/mount.c index 34e5dd454..2eb967399 100644 --- a/lib/mount.c +++ b/lib/mount.c @@ -5,7 +5,7 @@ Architecture specific file system mounting (Linux). This program can be distributed under the terms of the GNU LGPLv2. - See the file COPYING.LIB. + See the file LGPL2.txt. */ /* For environ */ diff --git a/lib/mount_bsd.c b/lib/mount_bsd.c index 28a9ae569..5dba5937d 100644 --- a/lib/mount_bsd.c +++ b/lib/mount_bsd.c @@ -5,7 +5,7 @@ Architecture specific file system mounting (FreeBSD). This program can be distributed under the terms of the GNU LGPLv2. - See the file COPYING.LIB. + See the file LGPL2.txt. */ #include "fuse_config.h" diff --git a/lib/mount_util.c b/lib/mount_util.c index f19dfb4c8..8c0cdf72d 100644 --- a/lib/mount_util.c +++ b/lib/mount_util.c @@ -5,7 +5,7 @@ Architecture-independent mounting code. This program can be distributed under the terms of the GNU LGPLv2. - See the file COPYING.LIB. + See the file LGPL2.txt. */ #include "fuse_config.h" diff --git a/lib/mount_util.h b/lib/mount_util.h index 0ef0fbe81..9cb9077dd 100644 --- a/lib/mount_util.h +++ b/lib/mount_util.h @@ -3,7 +3,7 @@ Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu> This program can be distributed under the terms of the GNU LGPLv2. - See the file COPYING.LIB. + See the file LGPL2.txt. */ #include <sys/types.h> diff --git a/test/hello.c b/test/hello.c index a07df0e48..cf576689b 100644 --- a/test/hello.c +++ b/test/hello.c @@ -3,7 +3,7 @@ * 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. + * See the file GPL2.txt. */ /** @file diff --git a/test/release_unlink_race.c b/test/release_unlink_race.c index 2edb20044..f7b7b81be 100644 --- a/test/release_unlink_race.c +++ b/test/release_unlink_race.c @@ -1,6 +1,6 @@ /* This program can be distributed under the terms of the GNU GPLv2. - See the file COPYING. + See the file GPL2.txt. */ #define FUSE_USE_VERSION 31 diff --git a/test/test_setattr.c b/test/test_setattr.c index ac552644c..ed0e93dc5 100644 --- a/test/test_setattr.c +++ b/test/test_setattr.c @@ -3,7 +3,7 @@ Copyright (C) 2016 Nikolaus Rath <Nikolaus@rath.org> This program can be distributed under the terms of the GNU GPLv2. - See the file COPYING. + See the file GPL2.txt. */ diff --git a/test/test_signals.c b/test/test_signals.c index 558202698..3ce7fbd32 100644 --- a/test/test_signals.c +++ b/test/test_signals.c @@ -5,7 +5,7 @@ * Test for signal handling in libfuse. * * This program can be distributed under the terms of the GNU LGPLv2. - * See the file COPYING.LIB + * See the file GPL2.txt */ #define FUSE_USE_VERSION FUSE_MAKE_VERSION(3, 17) diff --git a/test/test_write_cache.c b/test/test_write_cache.c index 2b5b4f383..00db5a65b 100644 --- a/test/test_write_cache.c +++ b/test/test_write_cache.c @@ -3,7 +3,7 @@ Copyright (C) 2016 Nikolaus Rath <Nikolaus@rath.org> This program can be distributed under the terms of the GNU GPLv2. - See the file COPYING. + See the file GPL2.txt. */ #define FUSE_USE_VERSION 30 diff --git a/util/fusermount.c b/util/fusermount.c index 68fa31e55..48f7fe7fa 100644 --- a/util/fusermount.c +++ b/util/fusermount.c @@ -3,7 +3,7 @@ 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. + See the file GPL2.txt. */ /* This program does the mounting and unmounting of FUSE filesystems */ diff --git a/util/mount.fuse.c b/util/mount.fuse.c index b98fb2a65..f1a90fe8a 100644 --- a/util/mount.fuse.c +++ b/util/mount.fuse.c @@ -3,7 +3,7 @@ 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. + See the file GPL2.txt. */ #include "fuse_config.h" From 4166f2eb97da4e25a516abee3d6fe13b9ed77bc6 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 1 Jul 2025 01:36:06 +0000 Subject: [PATCH 154/241] build(deps): bump github/codeql-action from 3.29.0 to 3.29.2 Bumps [github/codeql-action](https://github.com/github/codeql-action) from 3.29.0 to 3.29.2. - [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/ce28f5bb42b7a9f2c824e633a3f6ee835bab6858...181d5eefc20863364f96762470ba6f862bdef56b) --- updated-dependencies: - dependency-name: github/codeql-action dependency-version: 3.29.2 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 3c37133b0..8c954d476 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@ce28f5bb42b7a9f2c824e633a3f6ee835bab6858 # v3.29.0 + uses: github/codeql-action/init@181d5eefc20863364f96762470ba6f862bdef56b # v3.29.2 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@ce28f5bb42b7a9f2c824e633a3f6ee835bab6858 # v3.29.0 + uses: github/codeql-action/analyze@181d5eefc20863364f96762470ba6f862bdef56b # v3.29.2 with: category: "/language:${{matrix.language}}" From db5a1b6ae9d39614154590e5b8c62702a5230ca4 Mon Sep 17 00:00:00 2001 From: CismonX <admin@cismon.net> Date: Wed, 9 Jul 2025 23:01:05 +0800 Subject: [PATCH 155/241] example/passthrough: refactor fallocate Move fallocate implementation to passthrough_helpers.h, so that it could be reused by multiple passthrough examples. Signed-off-by: CismonX <admin@cismon.net> --- example/passthrough.c | 9 +-------- example/passthrough_fh.c | 11 +++-------- example/passthrough_helpers.h | 17 +++++++++++++++++ example/passthrough_hp.cc | 13 ++++--------- example/passthrough_ll.c | 16 ++-------------- 5 files changed, 27 insertions(+), 39 deletions(-) diff --git a/example/passthrough.c b/example/passthrough.c index a5ac4b356..7e5c9aaed 100644 --- a/example/passthrough.c +++ b/example/passthrough.c @@ -399,7 +399,6 @@ static int xmp_fsync(const char *path, int isdatasync, return 0; } -#ifdef HAVE_POSIX_FALLOCATE static int xmp_fallocate(const char *path, int mode, off_t offset, off_t length, struct fuse_file_info *fi) { @@ -408,9 +407,6 @@ static int xmp_fallocate(const char *path, int mode, (void) fi; - if (mode) - return -EOPNOTSUPP; - if(fi == NULL) fd = open(path, O_WRONLY); else @@ -419,13 +415,12 @@ static int xmp_fallocate(const char *path, int mode, if (fd == -1) return -errno; - res = -posix_fallocate(fd, offset, length); + res = do_fallocate(fd, mode, offset, length); if(fi == NULL) close(fd); return res; } -#endif #ifdef HAVE_SETXATTR /* xattr operations are optional and can safely be left unimplemented */ @@ -554,9 +549,7 @@ static const struct fuse_operations xmp_oper = { .statfs = xmp_statfs, .release = xmp_release, .fsync = xmp_fsync, -#ifdef HAVE_POSIX_FALLOCATE .fallocate = xmp_fallocate, -#endif #ifdef HAVE_SETXATTR .setxattr = xmp_setxattr, .getxattr = xmp_getxattr, diff --git a/example/passthrough_fh.c b/example/passthrough_fh.c index 7bbdb700d..67645542c 100644 --- a/example/passthrough_fh.c +++ b/example/passthrough_fh.c @@ -47,6 +47,8 @@ #endif #include <sys/file.h> /* flock(2) */ +#include "passthrough_helpers.h" + static void *xmp_init(struct fuse_conn_info *conn, struct fuse_config *cfg) { @@ -514,18 +516,13 @@ static int xmp_fsync(const char *path, int isdatasync, return 0; } -#ifdef HAVE_POSIX_FALLOCATE static int xmp_fallocate(const char *path, int mode, off_t offset, off_t length, struct fuse_file_info *fi) { (void) path; - if (mode) - return -EOPNOTSUPP; - - return -posix_fallocate(fi->fh, offset, length); + return do_fallocate(fi->fh, mode, offset, length); } -#endif #ifdef HAVE_SETXATTR /* xattr operations are optional and can safely be left unimplemented */ @@ -650,9 +647,7 @@ static const struct fuse_operations xmp_oper = { .flush = xmp_flush, .release = xmp_release, .fsync = xmp_fsync, -#ifdef HAVE_POSIX_FALLOCATE .fallocate = xmp_fallocate, -#endif #ifdef HAVE_SETXATTR .setxattr = xmp_setxattr, .getxattr = xmp_getxattr, diff --git a/example/passthrough_helpers.h b/example/passthrough_helpers.h index 6b77c3360..aca796a57 100644 --- a/example/passthrough_helpers.h +++ b/example/passthrough_helpers.h @@ -23,6 +23,23 @@ * SUCH DAMAGE */ +static inline int do_fallocate(int fd, int mode, off_t offset, off_t length) +{ +#ifdef HAVE_FALLOCATE + if (fallocate(fd, mode, offset, length) == -1) + return -errno; + return 0; +#else // HAVE_FALLOCATE + +#ifdef HAVE_POSIX_FALLOCATE + if (mode == 0) + return -posix_fallocate(fd, offset, length); +#endif + + return -EOPNOTSUPP; +#endif // HAVE_FALLOCATE +} + /* * Creates files on the underlying file system in response to a FUSE_MKNOD * operation diff --git a/example/passthrough_hp.cc b/example/passthrough_hp.cc index cc6b7e8d3..a0bf0d5c8 100644 --- a/example/passthrough_hp.cc +++ b/example/passthrough_hp.cc @@ -79,6 +79,8 @@ #include <mutex> #include <syslog.h> +#include "passthrough_helpers.h" + using namespace std; #define SFS_DEFAULT_THREADS "-1" // take libfuse value as default @@ -1216,20 +1218,15 @@ static void sfs_statfs(fuse_req_t req, fuse_ino_t ino) fuse_reply_statfs(req, &stbuf); } -#ifdef HAVE_POSIX_FALLOCATE static void sfs_fallocate(fuse_req_t req, fuse_ino_t ino, int mode, off_t offset, off_t length, fuse_file_info *fi) { (void)ino; - if (mode) { - fuse_reply_err(req, EOPNOTSUPP); - return; - } - auto err = posix_fallocate(fi->fh, offset, length); + auto err = -do_fallocate(fi->fh, mode, offset, length); + fuse_reply_err(req, err); } -#endif static void sfs_flock(fuse_req_t req, fuse_ino_t ino, fuse_file_info *fi, int op) @@ -1389,9 +1386,7 @@ static void assign_operations(fuse_lowlevel_ops &sfs_oper) sfs_oper.read = sfs_read; sfs_oper.write_buf = sfs_write_buf; sfs_oper.statfs = sfs_statfs; -#ifdef HAVE_POSIX_FALLOCATE sfs_oper.fallocate = sfs_fallocate; -#endif sfs_oper.flock = sfs_flock; #ifdef HAVE_SETXATTR sfs_oper.setxattr = sfs_setxattr; diff --git a/example/passthrough_ll.c b/example/passthrough_ll.c index 59f43c53c..9e027b43b 100644 --- a/example/passthrough_ll.c +++ b/example/passthrough_ll.c @@ -1006,22 +1006,10 @@ static void lo_statfs(fuse_req_t req, fuse_ino_t ino) static void lo_fallocate(fuse_req_t req, fuse_ino_t ino, int mode, off_t offset, off_t length, struct fuse_file_info *fi) { - int err = EOPNOTSUPP; + int err; (void) ino; -#ifdef HAVE_FALLOCATE - err = fallocate(fi->fh, mode, offset, length); - if (err < 0) - err = errno; - -#elif defined(HAVE_POSIX_FALLOCATE) - if (mode) { - fuse_reply_err(req, EOPNOTSUPP); - return; - } - - err = posix_fallocate(fi->fh, offset, length); -#endif + err = -do_fallocate(fi->fh, mode, offset, length); fuse_reply_err(req, err); } From 21a27e371fbb44e16eec6ef5e18768b476db2e27 Mon Sep 17 00:00:00 2001 From: CismonX <admin@cismon.net> Date: Wed, 9 Jul 2025 23:30:17 +0800 Subject: [PATCH 156/241] example/passthrough: tidy up passthrough_helpers.h Add header guards, include system headers as needed, and declare helper functions as inline. This ensures that the helper header could be properly included. Signed-off-by: CismonX <admin@cismon.net> --- example/passthrough.c | 4 ---- example/passthrough_helpers.h | 18 +++++++++++++++++- 2 files changed, 17 insertions(+), 5 deletions(-) diff --git a/example/passthrough.c b/example/passthrough.c index 7e5c9aaed..f0e686dc9 100644 --- a/example/passthrough.c +++ b/example/passthrough.c @@ -40,10 +40,6 @@ #include <sys/stat.h> #include <dirent.h> #include <errno.h> -#ifdef __FreeBSD__ -#include <sys/socket.h> -#include <sys/un.h> -#endif #include <sys/time.h> #ifdef HAVE_SETXATTR #include <sys/xattr.h> diff --git a/example/passthrough_helpers.h b/example/passthrough_helpers.h index aca796a57..52958c666 100644 --- a/example/passthrough_helpers.h +++ b/example/passthrough_helpers.h @@ -23,6 +23,20 @@ * SUCH DAMAGE */ +#ifndef FUSE_EXAMPLE_PASSTHROUGH_HELPERS_H_ +#define FUSE_EXAMPLE_PASSTHROUGH_HELPERS_H_ + +#include <errno.h> +#include <fcntl.h> +#include <string.h> +#include <sys/stat.h> +#include <unistd.h> + +#ifdef __FreeBSD__ +#include <sys/socket.h> +#include <sys/un.h> +#endif + static inline int do_fallocate(int fd, int mode, off_t offset, off_t length) { #ifdef HAVE_FALLOCATE @@ -44,7 +58,7 @@ static inline int do_fallocate(int fd, int mode, off_t offset, off_t length) * Creates files on the underlying file system in response to a FUSE_MKNOD * operation */ -static int mknod_wrapper(int dirfd, const char *path, const char *link, +static inline int mknod_wrapper(int dirfd, const char *path, const char *link, int mode, dev_t rdev) { int res; @@ -91,3 +105,5 @@ static int mknod_wrapper(int dirfd, const char *path, const char *link, return res; } + +#endif // FUSE_PASSTHROUGH_HELPERS_H_ From fc9888e0b281dc0d0fcebcfe0c8c392263b1b308 Mon Sep 17 00:00:00 2001 From: CismonX <admin@cismon.net> Date: Wed, 9 Jul 2025 23:10:03 +0800 Subject: [PATCH 157/241] example/passthrough: support fspacectl() FreeBSD 14 introduced a new system call, fspacectl(). Currently, it supports one operation mode, SPACECTL_DEALLOC, which is functionally equivalent to Linux fallocate() with FALLOC_FL_KEEP_SIZE|FALLOC_FL_PUNCH_HOLE flags. fspacectl() calls with SPACECTL_DEALLOC is supported on FUSE filesystems via FUSE_FALLOCATE with the aforementioned flags. Signed-off-by: CismonX <admin@cismon.net> --- example/passthrough_helpers.h | 13 +++++++++++++ meson.build | 2 +- 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/example/passthrough_helpers.h b/example/passthrough_helpers.h index 52958c666..326a5c7d6 100644 --- a/example/passthrough_helpers.h +++ b/example/passthrough_helpers.h @@ -50,6 +50,19 @@ static inline int do_fallocate(int fd, int mode, off_t offset, off_t length) return -posix_fallocate(fd, offset, length); #endif +#ifdef HAVE_FSPACECTL + // 0x3 == FALLOC_FL_KEEP_SIZE | FALLOC_FL_PUNCH_HOLE + if (mode == 0x3) { + struct spacectl_range sr; + + sr.r_offset = offset; + sr.r_len = length; + if (fspacectl(fd, SPACECTL_DEALLOC, &sr, 0, NULL) == -1) + return -errno; + return 0; + } +#endif + return -EOPNOTSUPP; #endif // HAVE_FALLOCATE } diff --git a/meson.build b/meson.build index 910a07565..16b804e74 100644 --- a/meson.build +++ b/meson.build @@ -74,7 +74,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', 'fspacectl' ] foreach func : test_funcs private_cfg.set('HAVE_' + func.to_upper(), cc.has_function(func, prefix: include_default, args: args_default)) From 3be844147764b96496bcae6d92fa4b0e43ebff42 Mon Sep 17 00:00:00 2001 From: Bernd Schubert <bernd@bsbernd.com> Date: Tue, 15 Jul 2025 18:46:44 +0200 Subject: [PATCH 158/241] Switch to c++20 This only effects example/{passthrough_hp.cc,memfs_ll.cc} and is mainly to avoid these warnings ../example/memfs_ll.cc:1100:1: warning: missing field 'statx' initializer [-Wmissing-designated-field-initializers] Signed-off-by: Bernd Schubert <bernd@bsbernd.com> --- example/meson.build | 2 +- meson.build | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/example/meson.build b/example/meson.build index 701afa768..21e8317a1 100644 --- a/example/meson.build +++ b/example/meson.build @@ -37,7 +37,7 @@ if platform != 'dragonfly' and add_languages('cpp', required : false) install: false) executable('memfs_ll', 'memfs_ll.cc', dependencies: [ thread_dep, libfuse_dep ], - cpp_args : '-std=c++17', + cpp_args : '-std=c++20', install: false) endif diff --git a/meson.build b/meson.build index 16b804e74..7091d5f68 100644 --- a/meson.build +++ b/meson.build @@ -4,7 +4,7 @@ project('libfuse3', ['c'], default_options: [ 'buildtype=debugoptimized', 'c_std=gnu11', - 'cpp_std=c++17', + 'cpp_std=c++20', 'warning_level=2', ]) From b507cbc2b1aaec1931642497edcb6723a0d24dc4 Mon Sep 17 00:00:00 2001 From: Joanne Koong <joannelkoong@gmail.com> Date: Fri, 23 Aug 2024 13:11:09 -0700 Subject: [PATCH 159/241] Add statx support This commit adds libfuse support for FUSE_STATX requests on linux distributions. Currently, statx is only supported on linux. To make the interface a ergonomic as possible (eg using native 'struct statx' vs 'struct fuse_statx'), this implementation gates the 'struct statx' changes by #ifdef linux. Signed-off-by: Joanne Koong <joannelkoong@gmail.com> --- ChangeLog.rst | 2 + example/memfs_ll.cc | 3 ++ example/passthrough.c | 21 ++++++++ example/passthrough_fh.c | 21 ++++++++ example/passthrough_ll.c | 25 +++++++++ include/fuse.h | 18 +++++++ include/fuse_lowlevel.h | 35 ++++++++++++- lib/fuse.c | 108 +++++++++++++++++++++++++++++++++++++++ lib/fuse_lowlevel.c | 66 +++++++++++++++++++++++- lib/fuse_versionscript | 3 ++ lib/modules/iconv.c | 19 +++++++ lib/modules/subdir.c | 19 +++++++ meson.build | 4 ++ test/test_syscalls.c | 49 ++++++++++++++++++ 14 files changed, 391 insertions(+), 2 deletions(-) diff --git a/ChangeLog.rst b/ChangeLog.rst index 658f898aa..505d9dba8 100644 --- a/ChangeLog.rst +++ b/ChangeLog.rst @@ -1,3 +1,5 @@ +libfuse 3.18 + libfuse 3.17.1-rc0 (2024-02.10) =============================== diff --git a/example/memfs_ll.cc b/example/memfs_ll.cc index b4f0b63cb..0da7c2544 100644 --- a/example/memfs_ll.cc +++ b/example/memfs_ll.cc @@ -1094,6 +1094,9 @@ static const struct fuse_lowlevel_ops memfs_oper = { .copy_file_range = nullptr, .lseek = nullptr, .tmpfile = nullptr, +#ifdef HAVE_STATX + .statx = nullptr, +#endif }; int main(int argc, char *argv[]) diff --git a/example/passthrough.c b/example/passthrough.c index f0e686dc9..fdaa19e33 100644 --- a/example/passthrough.c +++ b/example/passthrough.c @@ -519,6 +519,24 @@ static off_t xmp_lseek(const char *path, off_t off, int whence, struct fuse_file return res; } +#ifdef HAVE_STATX +static int xmp_statx(const char *path, int flags, int mask, struct statx *stxbuf, + struct fuse_file_info *fi) +{ + int fd = -1; + int res; + + if (fi) + fd = fi->fh; + + res = statx(fd, path, flags | AT_SYMLINK_NOFOLLOW, mask, stxbuf); + if (res == -1) + return -errno; + + return 0; +} +#endif + static const struct fuse_operations xmp_oper = { .init = xmp_init, .getattr = xmp_getattr, @@ -556,6 +574,9 @@ static const struct fuse_operations xmp_oper = { .copy_file_range = xmp_copy_file_range, #endif .lseek = xmp_lseek, +#ifdef HAVE_STATX + .statx = xmp_statx, +#endif }; int main(int argc, char *argv[]) diff --git a/example/passthrough_fh.c b/example/passthrough_fh.c index 67645542c..0d4fb5bd4 100644 --- a/example/passthrough_fh.c +++ b/example/passthrough_fh.c @@ -616,6 +616,24 @@ static off_t xmp_lseek(const char *path, off_t off, int whence, struct fuse_file return res; } +#ifdef HAVE_STATX +static int xmp_statx(const char *path, int flags, int mask, struct statx *stxbuf, + struct fuse_file_info *fi) +{ + int fd = -1; + int res; + + if (fi) + fd = fi->fh; + + res = statx(fd, path, flags | AT_SYMLINK_NOFOLLOW, mask, stxbuf); + if (res == -1) + return -errno; + + return 0; +} +#endif + static const struct fuse_operations xmp_oper = { .init = xmp_init, .getattr = xmp_getattr, @@ -662,6 +680,9 @@ static const struct fuse_operations xmp_oper = { .copy_file_range = xmp_copy_file_range, #endif .lseek = xmp_lseek, +#ifdef HAVE_STATX + .statx = xmp_statx, +#endif }; int main(int argc, char *argv[]) diff --git a/example/passthrough_ll.c b/example/passthrough_ll.c index 9e027b43b..5ca6efa23 100644 --- a/example/passthrough_ll.c +++ b/example/passthrough_ll.c @@ -1215,6 +1215,28 @@ static void lo_lseek(fuse_req_t req, fuse_ino_t ino, off_t off, int whence, fuse_reply_err(req, errno); } +#ifdef HAVE_STATX +static void lo_statx(fuse_req_t req, fuse_ino_t ino, int flags, int mask, + struct fuse_file_info *fi) +{ + struct lo_data *lo = lo_data(req); + struct statx buf; + int res; + int fd; + + if (fi) + fd = fi->fh; + else + fd = lo_fd(req, ino); + + res = statx(fd, "", flags | AT_EMPTY_PATH | AT_SYMLINK_NOFOLLOW, mask, &buf); + if (res == -1) + fuse_reply_err(req, errno); + else + fuse_reply_statx(req, 0, &buf, lo->timeout); +} +#endif + static const struct fuse_lowlevel_ops lo_oper = { .init = lo_init, .destroy = lo_destroy, @@ -1255,6 +1277,9 @@ static const struct fuse_lowlevel_ops lo_oper = { .copy_file_range = lo_copy_file_range, #endif .lseek = lo_lseek, +#ifdef HAVE_STATX + .statx = lo_statx, +#endif }; int main(int argc, char *argv[]) diff --git a/include/fuse.h b/include/fuse.h index b99004334..06feacb07 100644 --- a/include/fuse.h +++ b/include/fuse.h @@ -33,6 +33,9 @@ extern "C" { * Basic FUSE API * * ----------------------------------------------------------- */ +/* Forward declaration */ +struct statx; + /** Handle for a FUSE filesystem */ struct fuse; @@ -850,6 +853,19 @@ struct fuse_operations { * Find next data or hole after the specified offset */ off_t (*lseek) (const char *, off_t off, int whence, struct fuse_file_info *); + +#ifdef HAVE_STATX + /** + * Get extended file attributes. + * + * fi may be NULL. + * + * If path is NULL, then the AT_EMPTY_PATH bit in flags will be + * already set. + */ + int (*statx)(const char *path, int flags, int mask, struct statx *stxbuf, + struct fuse_file_info *fi); +#endif }; /** Extra context that may be needed by some filesystems @@ -1344,6 +1360,8 @@ ssize_t fuse_fs_copy_file_range(struct fuse_fs *fs, const char *path_in, size_t len, int flags); off_t fuse_fs_lseek(struct fuse_fs *fs, const char *path, off_t off, int whence, struct fuse_file_info *fi); +int fuse_fs_statx(struct fuse_fs *fs, const char *path, int flags, int mask, + struct statx *stxbuf, struct fuse_file_info *fi); void fuse_fs_init(struct fuse_fs *fs, struct fuse_conn_info *conn, struct fuse_config *cfg); void fuse_fs_destroy(struct fuse_fs *fs); diff --git a/include/fuse_lowlevel.h b/include/fuse_lowlevel.h index 75e084d09..844ee7102 100644 --- a/include/fuse_lowlevel.h +++ b/include/fuse_lowlevel.h @@ -49,6 +49,9 @@ typedef uint64_t fuse_ino_t; /** Request pointer type */ typedef struct fuse_req *fuse_req_t; +/* Forward declaration */ +struct statx; + /** * Session * @@ -1303,7 +1306,6 @@ struct fuse_lowlevel_ops { void (*lseek) (fuse_req_t req, fuse_ino_t ino, off_t off, int whence, struct fuse_file_info *fi); - /** * Create a tempfile * @@ -1325,6 +1327,23 @@ struct fuse_lowlevel_ops { void (*tmpfile) (fuse_req_t req, fuse_ino_t parent, mode_t mode, struct fuse_file_info *fi); +#ifdef HAVE_STATX + /** + * Get extended file attributes. + * + * Valid replies: + * fuse_reply_statx + * fuse_reply_err + * + * @param req request handle + * @param ino the inode number + * @param flags bitmask of requested flags + * @param mask bitmask of requested fields + * @param fi file information (may be NULL) + */ + void (*statx)(fuse_req_t req, fuse_ino_t ino, int flags, int mask, + struct fuse_file_info *fi); +#endif }; /** @@ -1705,6 +1724,20 @@ int fuse_reply_poll(fuse_req_t req, unsigned revents); */ int fuse_reply_lseek(fuse_req_t req, off_t off); +/** + * Reply with extended file attributes. + * + * Possible requests: + * statx + * + * @param req request handle + * @param flags statx flags + * @param statx the attributes + * @param attr_timeout validity timeout (in seconds) for the attributes + * @return zero for success, -errno for failure to send reply + */ +int fuse_reply_statx(fuse_req_t req, int flags, struct statx *statx, double attr_timeout); + /* ----------------------------------------------------------- * * Notification * * ----------------------------------------------------------- */ diff --git a/lib/fuse.c b/lib/fuse.c index 68b61ce69..333fdc7dc 100644 --- a/lib/fuse.c +++ b/lib/fuse.c @@ -1507,6 +1507,29 @@ static void set_stat(struct fuse *f, fuse_ino_t nodeid, struct stat *stbuf) stbuf->st_gid = f->conf.gid; } +#ifdef HAVE_STATX +static void set_statx(struct fuse *f, fuse_ino_t nodeid, struct statx *stxbuf) +{ + if (!f->conf.use_ino) + stxbuf->stx_ino = nodeid; + if (f->conf.set_mode) { + if (f->conf.dmask && S_ISDIR(stxbuf->stx_mode)) + stxbuf->stx_mode = (stxbuf->stx_mode & S_IFMT) | + (0777 & ~f->conf.dmask); + else if (f->conf.fmask) + stxbuf->stx_mode = (stxbuf->stx_mode & S_IFMT) | + (0777 & ~f->conf.fmask); + else + stxbuf->stx_mode = (stxbuf->stx_mode & S_IFMT) | + (0777 & ~f->conf.umask); + } + if (f->conf.set_uid) + stxbuf->stx_uid = f->conf.uid; + if (f->conf.set_gid) + stxbuf->stx_gid = f->conf.gid; +} +#endif + static struct fuse *req_fuse(fuse_req_t req) { return (struct fuse *) fuse_req_userdata(req); @@ -2312,6 +2335,39 @@ off_t fuse_fs_lseek(struct fuse_fs *fs, const char *path, off_t off, int whence, return fs->op.lseek(path, off, whence, fi); } +#ifdef HAVE_STATX +int fuse_fs_statx(struct fuse_fs *fs, const char *path, int flags, int mask, + struct statx *stxbuf, struct fuse_file_info *fi) +{ + fuse_get_context()->private_data = fs->user_data; + if (fs->op.statx) { + if (fs->debug) { + char buf[10]; + + fuse_log(FUSE_LOG_DEBUG, "statx[%s] %s %d %d\n", + file_info_string(fi, buf, sizeof(buf)), path, + flags, mask); + } + return fs->op.statx(path, flags, mask, stxbuf, fi); + } + + return -ENOSYS; +} +#else +int fuse_fs_statx(struct fuse_fs *fs, const char *path, int flags, int mask, + struct statx *stxbuf, struct fuse_file_info *fi) +{ + (void)fs; + (void)path; + (void)flags; + (void)mask; + (void)stxbuf; + (void)fi; + + return -ENOSYS; +} +#endif + static int is_open(struct fuse *f, fuse_ino_t dir, const char *name) { struct node *node; @@ -4361,6 +4417,55 @@ static void fuse_lib_lseek(fuse_req_t req, fuse_ino_t ino, off_t off, int whence reply_err(req, res); } +#ifdef HAVE_STATX +static void fuse_lib_statx(fuse_req_t req, fuse_ino_t ino, int flags, int mask, + struct fuse_file_info *fi) +{ + struct fuse *f = req_fuse_prepare(req); + struct statx stxbuf; + char *path; + int err; + + memset(&stxbuf, 0, sizeof(stxbuf)); + + if (fi != NULL) + err = get_path_nullok(f, ino, &path); + else + err = get_path(f, ino, &path); + + if (!err) { + struct fuse_intr_data d; + + if (!path) + flags |= AT_EMPTY_PATH; + fuse_prepare_interrupt(f, req, &d); + err = fuse_fs_statx(f->fs, path, flags, mask, &stxbuf, fi); + fuse_finish_interrupt(f, req, &d); + free_path(f, ino, path); + } + if (!err) { + struct node *node; + + pthread_mutex_lock(&f->lock); + node = get_node(f, ino); + if (node->is_hidden && stxbuf.stx_nlink > 0) + stxbuf.stx_nlink--; + if (f->conf.auto_cache) { + struct stat stbuf; + + stbuf.st_mtime = stxbuf.stx_mtime.tv_nsec; + ST_MTIM_NSEC(&stbuf) = stxbuf.stx_mtime.tv_nsec; + stbuf.st_size = stxbuf.stx_size; + update_stat(node, &stbuf); + } + pthread_mutex_unlock(&f->lock); + set_statx(f, ino, &stxbuf); + fuse_reply_statx(req, 0, &stxbuf, f->conf.attr_timeout); + } else + reply_err(req, err); +} +#endif + static int clean_delay(struct fuse *f) { /* @@ -4459,6 +4564,9 @@ static struct fuse_lowlevel_ops fuse_path_ops = { .fallocate = fuse_lib_fallocate, .copy_file_range = fuse_lib_copy_file_range, .lseek = fuse_lib_lseek, +#ifdef HAVE_STATX + .statx = fuse_lib_statx, +#endif }; int fuse_notify_poll(struct fuse_pollhandle *ph) diff --git a/lib/fuse_lowlevel.c b/lib/fuse_lowlevel.c index 91f42440f..1ae3473be 100644 --- a/lib/fuse_lowlevel.c +++ b/lib/fuse_lowlevel.c @@ -1216,6 +1216,33 @@ int fuse_reply_lseek(fuse_req_t req, off_t off) return send_reply_ok(req, &arg, sizeof(arg)); } +#ifdef HAVE_STATX +int fuse_reply_statx(fuse_req_t req, int flags, struct statx *statx, + double attr_timeout) +{ + struct fuse_statx_out arg; + + memset(&arg, 0, sizeof(arg)); + arg.flags = flags; + arg.attr_valid = calc_timeout_sec(attr_timeout); + arg.attr_valid_nsec = calc_timeout_nsec(attr_timeout); + memcpy(&arg.stat, statx, sizeof(arg.stat)); + + return send_reply_ok(req, &arg, sizeof(arg)); +} +#else +int fuse_reply_statx(fuse_req_t req, int flags, struct statx *statx, + double attr_timeout) +{ + (void)req; + (void)flags; + (void)statx; + (void)attr_timeout; + + return -ENOSYS; +} +#endif + static void _do_lookup(fuse_req_t req, const fuse_ino_t nodeid, const void *op_in, const void *in_payload) { @@ -2428,6 +2455,42 @@ static void do_lseek(fuse_req_t req, const fuse_ino_t nodeid, const void *inarg) _do_lseek(req, nodeid, inarg, NULL); } +#ifdef HAVE_STATX +static void _do_statx(fuse_req_t req, const fuse_ino_t nodeid, + const void *op_in, const void *in_payload) +{ + (void)in_payload; + const struct fuse_statx_in *arg = op_in; + struct fuse_file_info *fip = NULL; + struct fuse_file_info fi; + + if (arg->getattr_flags & FUSE_GETATTR_FH) { + memset(&fi, 0, sizeof(fi)); + fi.fh = arg->fh; + fip = &fi; + } + + if (req->se->op.statx) + req->se->op.statx(req, nodeid, arg->sx_flags, arg->sx_mask, fip); + else + fuse_reply_err(req, ENOSYS); +} +#else +static void _do_statx(fuse_req_t req, const fuse_ino_t nodeid, + const void *op_in, const void *in_payload) +{ + (void)in_payload; + (void)req; + (void)nodeid; + (void)op_in; +} +#endif + +static void do_statx(fuse_req_t req, fuse_ino_t nodeid, const void *inarg) +{ + _do_statx(req, nodeid, inarg, NULL); +} + static bool want_flags_valid(uint64_t capable, uint64_t want) { uint64_t unknown_flags = want & (~capable); @@ -2510,7 +2573,6 @@ bool fuse_get_feature_flag(struct fuse_conn_info *conn, 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"))) void @@ -3297,6 +3359,7 @@ static struct { [FUSE_RENAME2] = { do_rename2, "RENAME2" }, [FUSE_COPY_FILE_RANGE] = { do_copy_file_range, "COPY_FILE_RANGE" }, [FUSE_LSEEK] = { do_lseek, "LSEEK" }, + [FUSE_STATX] = { do_statx, "STATX" }, [CUSE_INIT] = { cuse_lowlevel_init, "CUSE_INIT" }, }; @@ -3351,6 +3414,7 @@ static struct { [FUSE_RENAME2] = { _do_rename2, "RENAME2" }, [FUSE_COPY_FILE_RANGE] = { _do_copy_file_range, "COPY_FILE_RANGE" }, [FUSE_LSEEK] = { _do_lseek, "LSEEK" }, + [FUSE_STATX] = { _do_statx, "STATX" }, [CUSE_INIT] = { _cuse_lowlevel_init, "CUSE_INIT" }, }; diff --git a/lib/fuse_versionscript b/lib/fuse_versionscript index 2d8884d7e..0e581f171 100644 --- a/lib/fuse_versionscript +++ b/lib/fuse_versionscript @@ -212,6 +212,9 @@ FUSE_3.18 { # Not part of public API, for internal test use only fuse_convert_to_conn_want_ext; + + fuse_reply_statx; + fuse_fs_statx; } FUSE_3.17; # Local Variables: diff --git a/lib/modules/iconv.c b/lib/modules/iconv.c index 599b8df4d..417c9043d 100644 --- a/lib/modules/iconv.c +++ b/lib/modules/iconv.c @@ -568,6 +568,22 @@ static off_t iconv_lseek(const char *path, off_t off, int whence, return res; } +#ifdef HAVE_STATX +static int iconv_statx(const char *path, int flags, int mask, struct statx *stxbuf, + struct fuse_file_info *fi) +{ + struct iconv *ic = iconv_get(); + char *newpath; + int res = iconv_convpath(ic, path, &newpath, 0); + + if (!res) { + res = fuse_fs_statx(ic->next, newpath, flags, mask, stxbuf, fi); + free(newpath); + } + return res; +} +#endif + static void *iconv_init(struct fuse_conn_info *conn, struct fuse_config *cfg) { @@ -627,6 +643,9 @@ static const struct fuse_operations iconv_oper = { .flock = iconv_flock, .bmap = iconv_bmap, .lseek = iconv_lseek, +#ifdef HAVE_STATX + .statx = iconv_statx, +#endif }; static const struct fuse_opt iconv_opts[] = { diff --git a/lib/modules/subdir.c b/lib/modules/subdir.c index dd2d49d80..67c469799 100644 --- a/lib/modules/subdir.c +++ b/lib/modules/subdir.c @@ -553,6 +553,22 @@ static off_t subdir_lseek(const char *path, off_t off, int whence, return res; } +#ifdef HAVE_STATX +static int subdir_statx(const char *path, int flags, int mask, struct statx *stxbuf, + struct fuse_file_info *fi) +{ + struct subdir *ic = subdir_get(); + char *newpath; + int res = subdir_addpath(ic, path, &newpath); + + if (!res) { + res = fuse_fs_statx(ic->next, newpath, flags, mask, stxbuf, fi); + free(newpath); + } + return res; +} +#endif + static void *subdir_init(struct fuse_conn_info *conn, struct fuse_config *cfg) { @@ -608,6 +624,9 @@ static const struct fuse_operations subdir_oper = { .flock = subdir_flock, .bmap = subdir_bmap, .lseek = subdir_lseek, +#ifdef HAVE_STATX + .statx = subdir_statx, +#endif }; static const struct fuse_opt subdir_opts[] = { diff --git a/meson.build b/meson.build index 7091d5f68..64ff334f7 100644 --- a/meson.build +++ b/meson.build @@ -126,6 +126,10 @@ 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>')) +public_cfg.set('HAVE_STATX', + cc.has_function('statx', prefix : '#define _GNU_SOURCE\n#include <sys/stat.h>')) +private_cfg.set('HAVE_STATX', + cc.has_function('statx', prefix : '#define _GNU_SOURCE\n#include <sys/stat.h>')) # Struct member checks private_cfg.set('HAVE_STRUCT_STAT_ST_ATIM', diff --git a/test/test_syscalls.c b/test/test_syscalls.c index 4bbe97340..61ee953b9 100644 --- a/test/test_syscalls.c +++ b/test/test_syscalls.c @@ -11,6 +11,7 @@ #include <utime.h> #include <errno.h> #include <assert.h> +#include <time.h> #include <sys/socket.h> #include <sys/types.h> #include <sys/stat.h> @@ -921,6 +922,53 @@ static int test_copy_file_range(void) } #endif +#ifdef HAVE_STATX +static int test_statx(void) +{ + struct statx sb; + char msg[] = "hi"; + size_t msg_size = sizeof(msg); + struct timespec tp; + int res; + + memset(&sb, 0, sizeof(sb)); + unlink(testfile); + + start_test("statx"); + + res = create_testfile(testfile, msg, msg_size); + if (res == -1) + return -1; + + res = statx(-1, testfile, AT_EMPTY_PATH, + STATX_BASIC_STATS | STATX_BTIME, &sb); + if (res == -1) + return -1; + + if (sb.stx_size != msg_size) + return -1; + + clock_gettime(CLOCK_REALTIME, &tp); + + if (sb.stx_btime.tv_sec > tp.tv_sec) + return -1; + + if (sb.stx_btime.tv_sec == tp.tv_sec && + sb.stx_btime.tv_nsec >= tp.tv_nsec) + return -1; + + unlink(testfile); + + success(); + return 0; +} +#else +static int test_statx(void) +{ + return 0; +} +#endif + static int test_utime(void) { struct utimbuf utm; @@ -2179,6 +2227,7 @@ 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(); + err += test_statx(); #ifndef __FreeBSD__ err += test_create_tmpfile(); err += test_create_and_link_tmpfile(); From 194023c5999651386a3ddbf91fdd710d661d083b Mon Sep 17 00:00:00 2001 From: Bernd Schubert <bernd@bsbernd.com> Date: Tue, 15 Jul 2025 23:54:27 +0200 Subject: [PATCH 160/241] 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> --- util/fusermount.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/util/fusermount.c b/util/fusermount.c index 48f7fe7fa..1b94e8e5e 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 @@ -1451,7 +1451,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 5f6d3be57f73c8b79e9b616b0f30464475116084 Mon Sep 17 00:00:00 2001 From: Bernd Schubert <bernd@bsbernd.com> Date: Wed, 16 Jul 2025 00:09:31 +0200 Subject: [PATCH 161/241] fusermount: close_range seems to be available on bsd According to https://man.freebsd.org/cgi/man.cgi?close_range(2) we just need to remove the linux include. Signed-off-by: Bernd Schubert <bernd@bsbernd.com> --- meson.build | 2 ++ util/fusermount.c | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/meson.build b/meson.build index 64ff334f7..05ca1e74a 100644 --- a/meson.build +++ b/meson.build @@ -98,7 +98,9 @@ special_funcs = { 'close_range': ''' #include <unistd.h> #include <fcntl.h> + #ifdef linux #include <linux/close_range.h> + #endif int main(void) { unsigned int flags = CLOSE_RANGE_UNSHARE; return close_range(3, ~0U, flags); diff --git a/util/fusermount.c b/util/fusermount.c index 1b94e8e5e..9cc08b4bf 100644 --- a/util/fusermount.c +++ b/util/fusermount.c @@ -36,7 +36,7 @@ #include <stdbool.h> #include <sys/vfs.h> -#ifdef HAVE_CLOSE_RANGE +#if defined HAVE_CLOSE_RANGE && defined linux #include <linux/close_range.h> #endif From d8253770ac2cf4b8769e8cf41eb3c629f30ee80f Mon Sep 17 00:00:00 2001 From: Bernd Schubert <bernd@bsbernd.com> Date: Tue, 15 Jul 2025 20:09:17 +0200 Subject: [PATCH 162/241] 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> --- 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 2221cf2af..0d0e637ae 100644 --- a/lib/fuse_i.h +++ b/lib/fuse_i.h @@ -18,6 +18,7 @@ #include <stdint.h> #include <stdbool.h> #include <errno.h> +#include <stdatomic.h> #define MIN(a, b) \ ({ \ @@ -67,7 +68,7 @@ struct fuse_session_uring { }; struct fuse_session { - char *mountpoint; + _Atomic(char *)mountpoint; int fd; struct fuse_custom_io *io; struct mount_opts *mo; diff --git a/lib/fuse_lowlevel.c b/lib/fuse_lowlevel.c index 1ae3473be..9731b44ea 100644 --- a/lib/fuse_lowlevel.c +++ b/lib/fuse_lowlevel.c @@ -2921,11 +2921,15 @@ static void _do_destroy(fuse_req_t req, const fuse_ino_t nodeid, const void *op_in, const void *in_payload) { struct fuse_session *se = req->se; + char *mountpoint; (void) nodeid; (void)op_in; (void)in_payload; + mountpoint = atomic_exchange(&se->mountpoint, NULL); + free(mountpoint); + se->got_destroy = 1; se->got_init = 0; if (se->op.destroy) @@ -4206,15 +4210,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. @@ -4237,7 +4249,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; @@ -4246,18 +4258,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; } @@ -4269,10 +4279,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 b96c738b10e9a308725c62b9392ba7d553eec254 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 163/241] 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> --- example/memfs_ll.cc | 1 - 1 file changed, 1 deletion(-) diff --git a/example/memfs_ll.cc b/example/memfs_ll.cc index 0da7c2544..17d7d034b 100644 --- a/example/memfs_ll.cc +++ b/example/memfs_ll.cc @@ -199,7 +199,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 0d15126f720e5515d9b2d2b661cff80c9a05cc5c Mon Sep 17 00:00:00 2001 From: Bernd Schubert <bschubert@ddn.com> Date: Sat, 19 Jul 2025 15:33:34 +0200 Subject: [PATCH 164/241] example/memfs_ll: Avoid nullptr field initializations by pragma These nullptr initializations don't make sense - methods that are not explicitly set are not implemented. I thought that C++20 would eventually avoid the nullptr, but looks like compilers still give warnings - avoid them with a pragma. Signed-off-by: Bernd Schubert <bschubert@ddn.com> --- example/memfs_ll.cc | 32 +++----------------------------- 1 file changed, 3 insertions(+), 29 deletions(-) diff --git a/example/memfs_ll.cc b/example/memfs_ll.cc index 17d7d034b..edda34b4e 100644 --- a/example/memfs_ll.cc +++ b/example/memfs_ll.cc @@ -1047,56 +1047,30 @@ static void memfs_statfs(fuse_req_t req, [[maybe_unused]] fuse_ino_t ino) fuse_reply_statfs(req, &stbuf); } +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wmissing-field-initializers" static const struct fuse_lowlevel_ops memfs_oper = { - .init = nullptr, - .destroy = nullptr, .lookup = memfs_lookup, .forget = memfs_forget, .getattr = memfs_getattr, .setattr = memfs_setattr, - .readlink = nullptr, - .mknod = nullptr, .mkdir = memfs_mkdir, .unlink = memfs_unlink, .rmdir = memfs_rmdir, - .symlink = nullptr, .rename = memfs_rename, .link = memfs_link, .open = memfs_open, .read = memfs_read, .write = memfs_write, - .flush = nullptr, .release = memfs_release, - .fsync = nullptr, .opendir = memfs_opendir, .readdir = memfs_readdir, .releasedir = memfs_releasedir, - .fsyncdir = nullptr, .statfs = memfs_statfs, - .setxattr = nullptr, - .getxattr = nullptr, - .listxattr = nullptr, - .removexattr = nullptr, - .access = nullptr, .create = memfs_create, - .getlk = nullptr, - .setlk = nullptr, - .bmap = nullptr, - .ioctl = nullptr, - .poll = nullptr, - .write_buf = nullptr, - .retrieve_reply = nullptr, .forget_multi = memfs_forget_multi, - .flock = nullptr, - .fallocate = nullptr, - .readdirplus = nullptr, - .copy_file_range = nullptr, - .lseek = nullptr, - .tmpfile = nullptr, -#ifdef HAVE_STATX - .statx = nullptr, -#endif }; +#pragma GCC diagnostic pop int main(int argc, char *argv[]) { From 6d02f5d763b1f1da54c3108707fb2ce6677fb1d0 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 21 Jul 2025 23:45:34 +0000 Subject: [PATCH 165/241] build(deps): bump github/codeql-action from 3.29.2 to 3.29.3 Bumps [github/codeql-action](https://github.com/github/codeql-action) from 3.29.2 to 3.29.3. - [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/181d5eefc20863364f96762470ba6f862bdef56b...d6bbdef45e766d081b84a2def353b0055f728d3e) --- updated-dependencies: - dependency-name: github/codeql-action dependency-version: 3.29.3 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 8c954d476..c7307acfa 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@181d5eefc20863364f96762470ba6f862bdef56b # v3.29.2 + uses: github/codeql-action/init@d6bbdef45e766d081b84a2def353b0055f728d3e # v3.29.3 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@181d5eefc20863364f96762470ba6f862bdef56b # v3.29.2 + uses: github/codeql-action/analyze@d6bbdef45e766d081b84a2def353b0055f728d3e # v3.29.3 with: category: "/language:${{matrix.language}}" From e1e7b117ede7d78abde3ca13545a2057b4160b50 Mon Sep 17 00:00:00 2001 From: Bernd Schubert <bschubert@ddn.com> Date: Sat, 19 Jul 2025 15:42:46 +0200 Subject: [PATCH 166/241] fuse-over-io-uring: Remove handling of -EOPNOTSUPP -EOPNOTSUPP was in early RFC kernel patches, but merged version does not have this handler anymore. Closes: https://github.com/libfuse/libfuse/issues/1283 Signed-off-by: Bernd Schubert <bschubert@ddn.com> --- lib/fuse_uring.c | 14 +++----------- 1 file changed, 3 insertions(+), 11 deletions(-) diff --git a/lib/fuse_uring.c b/lib/fuse_uring.c index a54973eed..8dea7d06d 100644 --- a/lib/fuse_uring.c +++ b/lib/fuse_uring.c @@ -553,8 +553,8 @@ static int fuse_uring_queue_handle_cqes(struct fuse_ring_queue *queue) //fuse_log(FUSE_LOG_ERR, "cqe res: %d\n", cqe->res); /* -ENOTCONN is ok on umount */ - if (err != -EINTR && err != -EOPNOTSUPP && - err != -EAGAIN && err != -ENOTCONN) { + if (err != -EINTR && err != -EAGAIN && + err != -ENOTCONN) { se->error = cqe->res; /* return first error */ @@ -701,22 +701,14 @@ static void *fuse_uring_thread(void *arg) io_uring_submit_and_wait(&queue->ring, 1); err = fuse_uring_queue_handle_cqes(queue); - if (err < 0) { - /* - * fuse-over-io-uring is not supported, operation can - * continue over /dev/fuse - */ - if (err == -EOPNOTSUPP) - goto ret; + if (err < 0) goto err; - } } return NULL; err: fuse_session_exit(se); -ret: return NULL; } From 062465063500e042723a24704a6e350907cb8f8c Mon Sep 17 00:00:00 2001 From: Bernd Schubert <bschubert@ddn.com> Date: Sun, 20 Jul 2025 14:31:46 +0200 Subject: [PATCH 167/241] fuse-io-uring: Add sanity check for the number of queue entries fuse-io-uring would create a blocking mount with 0 queue entries, we need a sanity check for it. Signed-off-by: Bernd Schubert <bschubert@ddn.com> --- lib/fuse_uring.c | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/lib/fuse_uring.c b/lib/fuse_uring.c index 8dea7d06d..10d3286e9 100644 --- a/lib/fuse_uring.c +++ b/lib/fuse_uring.c @@ -727,8 +727,13 @@ static int fuse_uring_start_ring_threads(struct fuse_ring_pool *ring) return rc; } -static int fuse_uring_sanity_check(void) +static int fuse_uring_sanity_check(struct fuse_session *se) { + if (se->uring.q_depth == 0) { + fuse_log(FUSE_LOG_ERR, "io-uring queue depth must be > 0\n"); + return -EINVAL; + } + _Static_assert(sizeof(struct fuse_uring_cmd_req) <= FUSE_URING_MAX_SQE128_CMD_DATA, "SQE128_CMD_DATA has 80B cmd data"); @@ -741,7 +746,7 @@ int fuse_uring_start(struct fuse_session *se) int err = 0; struct fuse_ring_pool *fuse_ring; - fuse_uring_sanity_check(); + fuse_uring_sanity_check(se); fuse_ring = fuse_create_ring(se); if (fuse_ring == NULL) { From 2f092ef1084fe72c6cc26a8cde61ee94329c2f34 Mon Sep 17 00:00:00 2001 From: Bernd Schubert <bschubert@ddn.com> Date: Mon, 21 Jul 2025 13:11:22 +0200 Subject: [PATCH 168/241] Simplify meson condition for liburing Signed-off-by: Bernd Schubert <bschubert@ddn.com> --- meson.build | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/meson.build b/meson.build index 05ca1e74a..f98ef8a6d 100644 --- a/meson.build +++ b/meson.build @@ -158,12 +158,10 @@ int main(void) { return ret; }''' -liburing = get_option('enable-io-uring') ? \ - dependency('liburing', required: false) :\ - dependency('', required: false) +liburing = dependency('liburing', required: false) libnuma = dependency('numa', required: false) -if liburing.found() and libnuma.found() +if get_option('enable-io-uring') and liburing.found() and libnuma.found() if cc.links(code, name: 'liburing linking and SQE128 support', dependencies: [liburing]) From e915a28ec44ba0f5345eed9985e862ebe13104cb Mon Sep 17 00:00:00 2001 From: Bernd Schubert <bschubert@ddn.com> Date: Sun, 20 Jul 2025 15:51:55 +0200 Subject: [PATCH 169/241] Split fuse-io-uring startup Start the ring threads before sending fuse_reply_ok() so that io-uring startup issues can be non-fatal. Signed-off-by: Bernd Schubert <bschubert@ddn.com> --- lib/fuse_lowlevel.c | 14 +++++++---- lib/fuse_uring.c | 60 ++++++++++++++++++++++++++++++++++++++++++--- lib/fuse_uring_i.h | 6 +++++ 3 files changed, 71 insertions(+), 9 deletions(-) diff --git a/lib/fuse_lowlevel.c b/lib/fuse_lowlevel.c index 9731b44ea..6afcecd3b 100644 --- a/lib/fuse_lowlevel.c +++ b/lib/fuse_lowlevel.c @@ -2897,18 +2897,22 @@ _do_init(fuse_req_t req, const fuse_ino_t nodeid, const void *op_in, else if (arg->minor < 23) outargsize = FUSE_COMPAT_22_INIT_OUT_SIZE; - send_reply_ok(req, &outarg, outargsize); - - /* XXX: Split the start, and send SQEs only after send_reply_ok() */ + /* XXX: Add an option to make non-available io-uring fatal */ if (enable_io_uring) { int ring_rc = fuse_uring_start(se); if (ring_rc != 0) { - fuse_log(FUSE_LOG_ERR, "fuse: failed to start io-uring: %s\n", + fuse_log(FUSE_LOG_INFO, + "fuse: failed to start io-uring: %s\n", strerror(ring_rc)); - fuse_session_exit(se); + outargflags &= ~FUSE_OVER_IO_URING; + enable_io_uring = false; } } + + send_reply_ok(req, &outarg, outargsize); + if (enable_io_uring) + fuse_uring_wake_ring_threads(se); } static __attribute__((no_sanitize("thread"))) void diff --git a/lib/fuse_uring.c b/lib/fuse_uring.c index 10d3286e9..985c81700 100644 --- a/lib/fuse_uring.c +++ b/lib/fuse_uring.c @@ -78,6 +78,15 @@ struct fuse_ring_pool { /* size of a single queue */ size_t queue_mem_size; + unsigned int started_threads; + unsigned int failed_threads; + + /* Avoid sending queue entries before FUSE_INIT reply*/ + sem_t init_sem; + + pthread_cond_t thread_start_cond; + pthread_mutex_t thread_start_mutex; + /* pointer to the first queue */ struct fuse_ring_queue *queues; }; @@ -432,7 +441,7 @@ static int fuse_uring_prepare_fetch_sqes(struct fuse_ring_queue *queue) io_uring_prep_poll_add(sqe, queue->eventfd, POLLIN); io_uring_sqe_set_data(sqe, (void *)(uintptr_t)queue->eventfd); - io_uring_submit(&queue->ring); + /* Only preparation until here, no submission yet */ return 0; } @@ -690,12 +699,24 @@ static void *fuse_uring_thread(void *arg) fuse_uring_set_thread_core(queue->qid); err = fuse_uring_init_queue(queue); + + if (err < 0) + ring_pool->failed_threads++; + pthread_mutex_lock(&ring_pool->thread_start_mutex); + if (err < 0) + ring_pool->failed_threads++; + ring_pool->started_threads++; + pthread_cond_broadcast(&ring_pool->thread_start_cond); + pthread_mutex_unlock(&ring_pool->thread_start_mutex); + if (err < 0) { fuse_log(FUSE_LOG_ERR, "qid=%d queue setup failed\n", queue->qid); - goto err; + goto err_non_fatal; } + sem_wait(&ring_pool->init_sem); + /* Not using fuse_session_exited(se), as that cannot be inlined */ while (!atomic_load_explicit(&se->mt_exited, memory_order_relaxed)) { io_uring_submit_and_wait(&queue->ring, 1); @@ -709,6 +730,7 @@ static void *fuse_uring_thread(void *arg) err: fuse_session_exit(se); +err_non_fatal: return NULL; } @@ -751,13 +773,34 @@ int fuse_uring_start(struct fuse_session *se) fuse_ring = fuse_create_ring(se); if (fuse_ring == NULL) { err = -EADDRNOTAVAIL; - goto out; + goto err; } se->uring.pool = fuse_ring; + /* Hold off threads from send fuse ring entries (SQEs) */ + sem_init(&fuse_ring->init_sem, 0, 0); + pthread_cond_init(&fuse_ring->thread_start_cond, NULL); + pthread_mutex_init(&fuse_ring->thread_start_mutex, NULL); + err = fuse_uring_start_ring_threads(fuse_ring); -out: + if (err) + goto err; + + while (fuse_ring->started_threads < fuse_ring->nr_queues) { + /* Wait for all threads to start */ + if (fuse_ring->failed_threads != 0) { + err = -EADDRNOTAVAIL; + goto err; + } + } + + if (fuse_ring->failed_threads != 0) { + err = -EADDRNOTAVAIL; + goto err; + } + +err: return err; } @@ -772,3 +815,12 @@ int fuse_uring_stop(struct fuse_session *se) return 0; } + +void fuse_uring_wake_ring_threads(struct fuse_session *se) +{ + struct fuse_ring_pool *ring = se->uring.pool; + + /* Wake up the threads to let them send SQEs */ + for (size_t qid = 0; qid < ring->nr_queues; qid++) + sem_post(&ring->init_sem); +} diff --git a/lib/fuse_uring_i.h b/lib/fuse_uring_i.h index fc236917b..14418ef16 100644 --- a/lib/fuse_uring_i.h +++ b/lib/fuse_uring_i.h @@ -30,6 +30,7 @@ void fuse_session_process_uring_cqe(struct fuse_session *se, struct fuse_in_header; int fuse_uring_start(struct fuse_session *se); +void fuse_uring_wake_ring_threads(struct fuse_session *se); int fuse_uring_stop(struct fuse_session *se); int send_reply_uring(fuse_req_t req, int error, const void *arg, size_t argsize); @@ -45,6 +46,11 @@ static inline int fuse_uring_start(struct fuse_session *se FUSE_VAR_UNUSED) return -ENOTSUP; } +static inline void +fuse_uring_wake_ring_threads(struct fuse_session *se FUSE_VAR_UNUSED) +{ +} + static inline int fuse_uring_stop(struct fuse_session *se FUSE_VAR_UNUSED) { return -ENOTSUP; From 898def7594b3b63a44119f5d77c797644108e74e Mon Sep 17 00:00:00 2001 From: Bernd Schubert <bschubert@ddn.com> Date: Mon, 21 Jul 2025 13:24:55 +0200 Subject: [PATCH 170/241] fuse_uring_i.h: Include errno.h Needed for ENOTSUP Signed-off-by: Bernd Schubert <bschubert@ddn.com> --- lib/fuse_uring_i.h | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/fuse_uring_i.h b/lib/fuse_uring_i.h index 14418ef16..c1da73ad7 100644 --- a/lib/fuse_uring_i.h +++ b/lib/fuse_uring_i.h @@ -16,6 +16,8 @@ #include "util.h" #endif +#include <errno.h> // IWYU pragma: keep + /* io-uring defaults */ #define SESSION_DEF_URING_ENABLE (0) #define SESSION_DEF_URING_Q_DEPTH (8) From 7e2dc4abbe1333eeefe80f6efbb81df24fe297a0 Mon Sep 17 00:00:00 2001 From: Bernd Schubert <bschubert@ddn.com> Date: Mon, 21 Jul 2025 18:56:53 +0200 Subject: [PATCH 171/241] high level: Remove the nullpath_ok log message This seems to be a left over - the file system has to set this variable in its ->init method, i.e. logging it in startup is not giving any helpful output. Closes: https://github.com/libfuse/libfuse/issues/1272 Signed-off-by: Bernd Schubert <bschubert@ddn.com> --- lib/fuse.c | 4 ---- 1 file changed, 4 deletions(-) diff --git a/lib/fuse.c b/lib/fuse.c index 333fdc7dc..e997531f4 100644 --- a/lib/fuse.c +++ b/lib/fuse.c @@ -5073,10 +5073,6 @@ struct fuse *_fuse_new_31(struct fuse_args *args, if (f->se == NULL) goto out_free_fs; - if (f->conf.debug) { - fuse_log(FUSE_LOG_DEBUG, "nullpath_ok: %i\n", f->conf.nullpath_ok); - } - /* Trace topmost layer by default */ f->fs->debug = f->conf.debug; f->ctr = 0; From db74ec7441dcdd444ce2599ae196025bc5383bc3 Mon Sep 17 00:00:00 2001 From: Gleb Popov <6yearold@gmail.com> Date: Thu, 24 Jul 2025 10:43:40 +0300 Subject: [PATCH 172/241] mount_bsd.c: Actually report mount failures back to caller Signed-off-by: Gleb Popov <6yearold@gmail.com> --- lib/mount_bsd.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/mount_bsd.c b/lib/mount_bsd.c index 5dba5937d..c12ab322e 100644 --- a/lib/mount_bsd.c +++ b/lib/mount_bsd.c @@ -215,7 +215,9 @@ static int fuse_mount_core(const char *mountpoint, const char *opts) } waitpid(pid, &status, 0); - _exit(EXIT_SUCCESS); + if (!WIFEXITED(status)) + _exit(EXIT_FAILURE); + _exit(WEXITSTATUS(status)); } if (waitpid(cpid, &status, 0) == -1 || WEXITSTATUS(status) != 0) { From 95b9d3b3e09cf9488ac331ea2b30c73c29f16e9d Mon Sep 17 00:00:00 2001 From: Bernd Schubert <bernd@bsbernd.com> Date: Sat, 2 Aug 2025 13:35:29 +0200 Subject: [PATCH 173/241] Update issue templates Signed-off-by: Bernd Schubert <bernd@bsbernd.com> --- .github/ISSUE_TEMPLATE/bug_report.md | 27 ++++++++++++++++++++++++++ .github/ISSUE_TEMPLATE/issue-report.md | 16 --------------- 2 files changed, 27 insertions(+), 16 deletions(-) create mode 100644 .github/ISSUE_TEMPLATE/bug_report.md delete mode 100644 .github/ISSUE_TEMPLATE/issue-report.md diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 000000000..4677c34d2 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,27 @@ +--- +name: Bug report +about: Create a report to help us improve +title: '' +labels: '' +assignees: '' + +--- + +**Describe the bug** +A clear and concise description of what the bug is. + +**To Reproduce** +Steps to reproduce the behavior: + +**Expected behavior** +A clear and concise description of what you expected to happen. + +**Screenshots** +If applicable, add screenshots to help explain your problem. + +**Desktop (please complete the following information):** + - OS: [e.g. Debian] + - Version [e.g. 12] + +**Additional context** +Add any other context about the problem here. diff --git a/.github/ISSUE_TEMPLATE/issue-report.md b/.github/ISSUE_TEMPLATE/issue-report.md deleted file mode 100644 index 492ce0a1c..000000000 --- a/.github/ISSUE_TEMPLATE/issue-report.md +++ /dev/null @@ -1,16 +0,0 @@ ---- -name: Issue report -about: Create a report to help us improve -title: '' -labels: '' -assignees: '' - ---- - -PLEASE READ BEFORE REPORTING AN ISSUE - -libfuse does not have any active, regular contributors or developers. The current maintainer continues to apply pull requests and tries to make regular releases, but unfortunately has no capacity to do any development beyond addressing high-impact issues. When reporting bugs, please understand that unless you are including a pull request or are reporting a critical issue, you will probably not get a response. - -To prevent the issue tracker from being flooded with issues that no-one is intending to work on, and to give more visibility to critical issues that users should be aware of and that most urgently need attention, I will also close most bug reports once they've been inactive for a while. - -Please note that this isn't meant to imply that you haven't found a bug - you most likely have and I'm grateful that you took the time to report it. Unfortunately, libfuse is a purely volunteer driven project, and at the moment there simply aren't any volunteers. From ba8a3a420e0421561eb6ff6caf17588e748ab164 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 5 Aug 2025 04:08:01 +0000 Subject: [PATCH 174/241] build(deps): bump github/codeql-action from 3.29.3 to 3.29.5 Bumps [github/codeql-action](https://github.com/github/codeql-action) from 3.29.3 to 3.29.5. - [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/d6bbdef45e766d081b84a2def353b0055f728d3e...51f77329afa6477de8c49fc9c7046c15b9a4e79d) --- updated-dependencies: - dependency-name: github/codeql-action dependency-version: 3.29.5 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 c7307acfa..c31114487 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@d6bbdef45e766d081b84a2def353b0055f728d3e # v3.29.3 + uses: github/codeql-action/init@51f77329afa6477de8c49fc9c7046c15b9a4e79d # v3.29.5 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@d6bbdef45e766d081b84a2def353b0055f728d3e # v3.29.3 + uses: github/codeql-action/analyze@51f77329afa6477de8c49fc9c7046c15b9a4e79d # v3.29.5 with: category: "/language:${{matrix.language}}" From 33ec2372f36ae96c4828f00720b58a097c578165 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 12 Aug 2025 07:04:06 +0000 Subject: [PATCH 175/241] build(deps): bump actions/checkout from 4 to 5 Bumps [actions/checkout](https://github.com/actions/checkout) from 4 to 5. - [Release notes](https://github.com/actions/checkout/releases) - [Commits](https://github.com/actions/checkout/compare/v4...v5) --- updated-dependencies: - dependency-name: actions/checkout dependency-version: '5' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] <support@github.com> --- .github/workflows/abicheck.yml | 4 ++-- .github/workflows/bsd.yaml | 2 +- .github/workflows/checkpatch.yml | 2 +- .github/workflows/codeql.yml | 2 +- .github/workflows/codespell.yml | 2 +- .github/workflows/iwyi-check.yml | 2 +- .github/workflows/pr-ci.yml | 2 +- 7 files changed, 8 insertions(+), 8 deletions(-) diff --git a/.github/workflows/abicheck.yml b/.github/workflows/abicheck.yml index b79ccc6ed..8ab13da8a 100644 --- a/.github/workflows/abicheck.yml +++ b/.github/workflows/abicheck.yml @@ -28,11 +28,11 @@ jobs: sudo apt-get update sudo apt-get -y install abigail-tools clang gcc liburing-dev libnuma-dev - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 with: path: current - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 with: path: previous ref: ${{ github.event.pull_request.base.ref }} diff --git a/.github/workflows/bsd.yaml b/.github/workflows/bsd.yaml index e07d543dd..13b30299b 100644 --- a/.github/workflows/bsd.yaml +++ b/.github/workflows/bsd.yaml @@ -18,7 +18,7 @@ jobs: name: Build under FreeBSD steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v5 - name: Build uses: vmactions/freebsd-vm@v1 with: diff --git a/.github/workflows/checkpatch.yml b/.github/workflows/checkpatch.yml index 0da6c5410..86b16aae1 100644 --- a/.github/workflows/checkpatch.yml +++ b/.github/workflows/checkpatch.yml @@ -10,7 +10,7 @@ jobs: checkpatch: runs-on: ubuntu-latest steps: - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 with: fetch-depth: 0 - name: Install dependencies diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index c31114487..d9ae46883 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -49,7 +49,7 @@ jobs: build-mode: manual steps: - name: Checkout repository - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL diff --git a/.github/workflows/codespell.yml b/.github/workflows/codespell.yml index 184fbcfe1..4c2288b7a 100644 --- a/.github/workflows/codespell.yml +++ b/.github/workflows/codespell.yml @@ -21,7 +21,7 @@ jobs: steps: - name: Checkout - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 - name: Codespell uses: codespell-project/actions-codespell@406322ec52dd7b488e48c1c4b82e2a8b3a1bf630 # v2.1 with: diff --git a/.github/workflows/iwyi-check.yml b/.github/workflows/iwyi-check.yml index 3751f35de..0718c3155 100644 --- a/.github/workflows/iwyi-check.yml +++ b/.github/workflows/iwyi-check.yml @@ -18,7 +18,7 @@ jobs: name: Include What You Use Check runs-on: ubuntu-latest steps: - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 with: fetch-depth: 0 diff --git a/.github/workflows/pr-ci.yml b/.github/workflows/pr-ci.yml index 78b5e02a9..b0134cf6b 100644 --- a/.github/workflows/pr-ci.yml +++ b/.github/workflows/pr-ci.yml @@ -33,7 +33,7 @@ jobs: gcc-multilib g++-multilib libc6-dev-i386 \ libpcap0.8-dev:i386 libudev-dev:i386 pkg-config:i386 \ liburing-dev libnuma-dev - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 - uses: actions/setup-python@v5 with: python-version: '3.12' From 2454ff17512dd0ff8d44ec4a0c35f5fe3aa09d2f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 12 Aug 2025 06:07:12 +0000 Subject: [PATCH 176/241] build(deps): bump github/codeql-action from 3.29.7 to 3.29.8 Bumps [github/codeql-action](https://github.com/github/codeql-action) from 3.29.7 to 3.29.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/51f77329afa6477de8c49fc9c7046c15b9a4e79d...76621b61decf072c1cee8dd1ce2d2a82d33c17ed) --- updated-dependencies: - dependency-name: github/codeql-action dependency-version: 3.29.8 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 d9ae46883..62dac1451 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@51f77329afa6477de8c49fc9c7046c15b9a4e79d # v3.29.5 + uses: github/codeql-action/init@76621b61decf072c1cee8dd1ce2d2a82d33c17ed # v3.29.5 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@51f77329afa6477de8c49fc9c7046c15b9a4e79d # v3.29.5 + uses: github/codeql-action/analyze@76621b61decf072c1cee8dd1ce2d2a82d33c17ed # v3.29.5 with: category: "/language:${{matrix.language}}" From 42b9b3bbb71d38be6a5670bbd0948cf406c1573c Mon Sep 17 00:00:00 2001 From: CismonX <admin@cismon.net> Date: Tue, 12 Aug 2025 08:55:42 +0800 Subject: [PATCH 177/241] 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> --- 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 8ee7d49ae..77712b6a2 100644 --- a/lib/fuse_loop_mt.c +++ b/lib/fuse_loop_mt.c @@ -271,9 +271,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 a63fc71a55038f49671eb56d0ade99a48ad8d7ab Mon Sep 17 00:00:00 2001 From: CismonX <admin@cismon.net> Date: Tue, 19 Aug 2025 08:05:45 +0800 Subject: [PATCH 178/241] 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> --- 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 b82f2c41d..041188ec7 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 @@ -719,8 +711,6 @@ struct fuse_conn_info { */ uint16_t reserved[31]; }; -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 f98ef8a6d..07c729581 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 332921657..9f6f409cc 100644 --- a/test/meson.build +++ b/test/meson.build @@ -22,6 +22,9 @@ td += executable('test_want_conversion', 'test_want_conversion.c', td += executable('test_signals', 'test_signals.c', dependencies: [ libfuse_dep, thread_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 ae5cc8f15..b3863e058 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 8b8ce5a8242e78db4f1d22437d8e1f015d71765f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 19 Aug 2025 07:59:16 +0000 Subject: [PATCH 179/241] build(deps): bump github/codeql-action from 3.29.8 to 3.29.10 Bumps [github/codeql-action](https://github.com/github/codeql-action) from 3.29.8 to 3.29.10. - [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/76621b61decf072c1cee8dd1ce2d2a82d33c17ed...96f518a34f7a870018057716cc4d7a5c014bd61c) --- updated-dependencies: - dependency-name: github/codeql-action dependency-version: 3.29.10 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 62dac1451..bcc631d0f 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@76621b61decf072c1cee8dd1ce2d2a82d33c17ed # v3.29.5 + uses: github/codeql-action/init@96f518a34f7a870018057716cc4d7a5c014bd61c # v3.29.5 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@76621b61decf072c1cee8dd1ce2d2a82d33c17ed # v3.29.5 + uses: github/codeql-action/analyze@96f518a34f7a870018057716cc4d7a5c014bd61c # v3.29.5 with: category: "/language:${{matrix.language}}" From bd8d8cdda9a5f520b7decfe98fafe540992ecb87 Mon Sep 17 00:00:00 2001 From: Bernd Schubert <bernd@bsbernd.com> Date: Mon, 18 Aug 2025 22:37:11 +0200 Subject: [PATCH 180/241] io-uring startup: Fix spinning and variable read Reported-by: lixianming <lixianming.19951001@bytedance.com> Closes: https://github.com/libfuse/libfuse/pull/1317 Signed-off-by: Bernd Schubert <bernd@bsbernd.com> --- lib/fuse_uring.c | 25 +++++++++++++++---------- 1 file changed, 15 insertions(+), 10 deletions(-) diff --git a/lib/fuse_uring.c b/lib/fuse_uring.c index 985c81700..a835ea1da 100644 --- a/lib/fuse_uring.c +++ b/lib/fuse_uring.c @@ -384,6 +384,8 @@ static void fuse_session_destruct_uring(struct fuse_ring_pool *fuse_ring) } free(fuse_ring->queues); + pthread_cond_destroy(&fuse_ring->thread_start_cond); + pthread_mutex_destroy(&fuse_ring->thread_start_mutex); free(fuse_ring); } @@ -491,6 +493,10 @@ static struct fuse_ring_pool *fuse_create_ring(struct fuse_session *se) queue->ring_pool = fuse_ring; } + pthread_cond_init(&fuse_ring->thread_start_cond, NULL); + pthread_mutex_init(&fuse_ring->thread_start_mutex, NULL); + sem_init(&fuse_ring->init_sem, 0, 0); + return fuse_ring; err: @@ -787,18 +793,17 @@ int fuse_uring_start(struct fuse_session *se) if (err) goto err; - while (fuse_ring->started_threads < fuse_ring->nr_queues) { - /* Wait for all threads to start */ - if (fuse_ring->failed_threads != 0) { - err = -EADDRNOTAVAIL; - goto err; - } - } + /* + * Wait for all threads to start or to fail + */ + pthread_mutex_lock(&fuse_ring->thread_start_mutex); + while (fuse_ring->started_threads < fuse_ring->nr_queues) + pthread_cond_wait(&fuse_ring->thread_start_cond, + &fuse_ring->thread_start_mutex); - if (fuse_ring->failed_threads != 0) { + if (fuse_ring->failed_threads != 0) err = -EADDRNOTAVAIL; - goto err; - } + pthread_mutex_unlock(&fuse_ring->thread_start_mutex); err: return err; From 210d92f66b128b229c5803d6f430f346d690feb0 Mon Sep 17 00:00:00 2001 From: Bernd Schubert <bschubert@ddn.com> Date: Mon, 18 Aug 2025 23:32:22 +0200 Subject: [PATCH 181/241] fuse-io-uring: Fix double count of ring_pool->failed_threads This is already done a few lines below. And actually no reason to hold the lock at all given the variables are atomics now. Signed-off-by: Bernd Schubert <bschubert@ddn.com> --- lib/fuse_uring.c | 3 --- 1 file changed, 3 deletions(-) diff --git a/lib/fuse_uring.c b/lib/fuse_uring.c index a835ea1da..53ef8dbb4 100644 --- a/lib/fuse_uring.c +++ b/lib/fuse_uring.c @@ -705,9 +705,6 @@ static void *fuse_uring_thread(void *arg) fuse_uring_set_thread_core(queue->qid); err = fuse_uring_init_queue(queue); - - if (err < 0) - ring_pool->failed_threads++; pthread_mutex_lock(&ring_pool->thread_start_mutex); if (err < 0) ring_pool->failed_threads++; From 253ea916fd5122e1c182cb3ffbff1ac2eeea3991 Mon Sep 17 00:00:00 2001 From: Bernd Schubert <bschubert@ddn.com> Date: Mon, 18 Aug 2025 23:22:02 +0200 Subject: [PATCH 182/241] fuse-io-uring: Release io-uring resources on io-uring startup failure Operation might continue without io-uring, so just free these resources. Signed-off-by: Bernd Schubert <bschubert@ddn.com> --- lib/fuse_uring.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/lib/fuse_uring.c b/lib/fuse_uring.c index 53ef8dbb4..104be14f0 100644 --- a/lib/fuse_uring.c +++ b/lib/fuse_uring.c @@ -803,6 +803,11 @@ int fuse_uring_start(struct fuse_session *se) pthread_mutex_unlock(&fuse_ring->thread_start_mutex); err: + if (err) { + /* Note all threads need to have been started */ + fuse_session_destruct_uring(fuse_ring); + se->uring.pool = fuse_ring; + } return err; } From d52ae4f9d44d7e08375bfacac0bc7cc02ad1b54b Mon Sep 17 00:00:00 2001 From: Bernd Schubert <bschubert@ddn.com> Date: Wed, 28 May 2025 15:13:47 +0200 Subject: [PATCH 183/241] Allow applications to retrieve the req payload (io-uring only) With io-uring the req owns the payload buffer, the application can directly access it and copy data into it. fuse_buf_copy_one() already has a check for dstmem == srcmem and skips data copies. fuse_reply_data fuse_reply_data_uring fuse_buf_copy fuse_buf_copy_one Signed-off-by: Bernd Schubert <bschubert@ddn.com> --- example/passthrough_hp.cc | 36 ++++++++++++++++++++++++++++++++---- include/fuse_lowlevel.h | 18 ++++++++++++++++++ lib/fuse_lowlevel.c | 12 ++++++++++++ lib/fuse_uring.c | 24 ++++++++++++++++++++++++ lib/fuse_versionscript | 1 + 5 files changed, 87 insertions(+), 4 deletions(-) diff --git a/example/passthrough_hp.cc b/example/passthrough_hp.cc index a0bf0d5c8..2bdfc0add 100644 --- a/example/passthrough_hp.cc +++ b/example/passthrough_hp.cc @@ -1158,10 +1158,38 @@ static void sfs_fsync(fuse_req_t req, fuse_ino_t ino, int datasync, static void do_read(fuse_req_t req, size_t size, off_t off, fuse_file_info *fi) { fuse_bufvec buf = FUSE_BUFVEC_INIT(size); - buf.buf[0].flags = - static_cast<fuse_buf_flags>(FUSE_BUF_IS_FD | FUSE_BUF_FD_SEEK); - buf.buf[0].fd = fi->fh; - buf.buf[0].pos = off; + char *payload = NULL; + size_t payload_size = 0; + int res = fuse_req_get_payload(req, &payload, &payload_size, NULL); + + /* + * This is a demonstration how to use io-uring payload. For FUSE_BUF_IS_FD + * it shouldn't make much of a difference because fuse_reply_data() -> + * fuse_reply_data_uring() also has access to the payload and will + * read directly from the FD into the payload. + * It is more useful for file systems that need a buffer for decryption, + * decompression, etc. + */ + if (res == 0) { + /* This is an io-uring request - write directly to the payload */ + assert(payload_size >= size); + + buf.buf[0].mem = payload; + buf.buf[0].size = payload_size; + + res = pread(fi->fh, payload, size, off); + if (res < 0) { + fuse_reply_err(req, errno); + return; + } + + buf.buf[0].size = res; + } else { + buf.buf[0].flags = static_cast<fuse_buf_flags>( + FUSE_BUF_IS_FD | FUSE_BUF_FD_SEEK); + buf.buf[0].fd = fi->fh; + buf.buf[0].pos = off; + } fuse_reply_data(req, &buf, FUSE_BUF_COPY_FLAGS); } diff --git a/include/fuse_lowlevel.h b/include/fuse_lowlevel.h index 844ee7102..dc4ec07e6 100644 --- a/include/fuse_lowlevel.h +++ b/include/fuse_lowlevel.h @@ -2367,6 +2367,24 @@ int fuse_session_receive_buf(struct fuse_session *se, struct fuse_buf *buf); */ bool fuse_req_is_uring(fuse_req_t req); +/** + * Get the payload of a request + * (for requests submitted through fuse-io-uring only) + * + * This is useful for a file system that wants to write data directly + * to the request buffer. With io-uring the req is the buffer owner + * and the file system can write directly to the buffer and avoid + * extra copying. For example useful for network file systems. + * + * @param req the request + * @param payload pointer to the payload + * @param payload_sz size of the payload + * @param mr memory registration handle, currently unused + * @return 0 on success, -errno on failure + */ +int fuse_req_get_payload(fuse_req_t req, char **payload, size_t *payload_sz, + void **mr); + #ifdef __cplusplus } #endif diff --git a/lib/fuse_lowlevel.c b/lib/fuse_lowlevel.c index 6afcecd3b..e4544dfc3 100644 --- a/lib/fuse_lowlevel.c +++ b/lib/fuse_lowlevel.c @@ -3317,6 +3317,18 @@ bool fuse_req_is_uring(fuse_req_t req) return req->is_uring; } +#ifndef HAVE_URING +int fuse_req_get_payload(fuse_req_t req, char **payload, size_t *payload_sz, + void **mr) +{ + (void)req; + (void)payload; + (void)payload_sz; + (void)mr; + return -ENOTSUP; +} +#endif + static struct { void (*func)(fuse_req_t req, const fuse_ino_t node, const void *arg); const char *name; diff --git a/lib/fuse_uring.c b/lib/fuse_uring.c index 104be14f0..ee68fab57 100644 --- a/lib/fuse_uring.c +++ b/lib/fuse_uring.c @@ -190,6 +190,30 @@ static int fuse_uring_commit_sqe(struct fuse_ring_pool *ring_pool, return 0; } +int fuse_req_get_payload(fuse_req_t req, char **payload, size_t *payload_sz, + void **mr) +{ + struct fuse_ring_ent *ring_ent; + + /* Not possible without io-uring interface */ + if (!req->is_uring) + return -EINVAL; + + ring_ent = container_of(req, struct fuse_ring_ent, req); + + *payload = ring_ent->op_payload; + *payload_sz = ring_ent->req_payload_sz; + + /* + * For now unused, but will be used later when the application can + * allocate the buffers itself and register them for rdma. + */ + if (mr) + *mr = NULL; + + return 0; +} + int send_reply_uring(fuse_req_t req, int error, const void *arg, size_t argsize) { int res; diff --git a/lib/fuse_versionscript b/lib/fuse_versionscript index 0e581f171..2feafcf83 100644 --- a/lib/fuse_versionscript +++ b/lib/fuse_versionscript @@ -205,6 +205,7 @@ FUSE_3.17 { FUSE_3.18 { global: fuse_req_is_uring; + fuse_req_get_payload; fuse_set_feature_flag; fuse_unset_feature_flag; fuse_get_feature_flag; From 7e6cdc754ae82706435e8bdd8e98d0f4f6cf9881 Mon Sep 17 00:00:00 2001 From: Bernd Schubert <bschubert@ddn.com> Date: Wed, 28 May 2025 15:57:27 +0200 Subject: [PATCH 184/241] send_reply_uring: Avoid memcpy if src and dest are identical The application might have just written directly into the payload to no need to copy it and in fact, using memcpy would be undefined behavior. Signed-off-by: Bernd Schubert <bschubert@ddn.com> --- lib/fuse_uring.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/fuse_uring.c b/lib/fuse_uring.c index ee68fab57..4c6f0a483 100644 --- a/lib/fuse_uring.c +++ b/lib/fuse_uring.c @@ -233,7 +233,8 @@ int send_reply_uring(fuse_req_t req, int error, const void *arg, size_t argsize) argsize, max_payload_sz); error = -EINVAL; } else if (argsize) { - memcpy(ring_ent->op_payload, arg, argsize); + if (arg != ring_ent->op_payload) + memcpy(ring_ent->op_payload, arg, argsize); } ent_in_out->payload_sz = argsize; From c7c5dbe7cfa5c956ecb36ed69e13eae62a8f4966 Mon Sep 17 00:00:00 2001 From: Bernd Schubert <bschubert@ddn.com> Date: Fri, 22 Aug 2025 18:12:07 +0200 Subject: [PATCH 185/241] Fix passthrough_hp.cc indentation Commit f8fe398ee14864e2c3c7c524ca851d7cc08bed28 introduced an invalid passthrough_hp.cc indentation and that was not detected by checkpatch, as that only handles C files. Signed-off-by: Bernd Schubert <bschubert@ddn.com> --- example/passthrough_hp.cc | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/example/passthrough_hp.cc b/example/passthrough_hp.cc index 2bdfc0add..1a67704dc 100644 --- a/example/passthrough_hp.cc +++ b/example/passthrough_hp.cc @@ -254,19 +254,19 @@ static void sfs_getattr(fuse_req_t req, fuse_ino_t ino, fuse_file_info *fi) fuse_reply_attr(req, &attr, fs.timeout); } -static int with_fd_path(int fd, const std::function<int(const char*)>& f) +static int with_fd_path(int fd, const std::function<int(const char *)> &f) { #ifdef __FreeBSD__ - struct kinfo_file kf; - kf.kf_structsize = sizeof(kf); - int ret = fcntl(fd, F_KINFO, &kf); - if (ret == -1) - return ret; - return f (kf.kf_path); + struct kinfo_file kf; + kf.kf_structsize = sizeof(kf); + int ret = fcntl(fd, F_KINFO, &kf); + if (ret == -1) + return ret; + return f(kf.kf_path); #else // Linux - char procname[64]; - sprintf(procname, "/proc/self/fd/%i", fd); - return f(procname); + char procname[64]; + sprintf(procname, "/proc/self/fd/%i", fd); + return f(procname); #endif } static void do_setattr(fuse_req_t req, fuse_ino_t ino, struct stat *attr, @@ -280,7 +280,7 @@ static void do_setattr(fuse_req_t req, fuse_ino_t ino, struct stat *attr, if (fi) { res = fchmod(fi->fh, attr->st_mode); } else { - res = with_fd_path(ifd, [attr](const char* procname) { + res = with_fd_path(ifd, [attr](const char *procname) { return chmod(procname, attr->st_mode); }); } @@ -304,7 +304,7 @@ static void do_setattr(fuse_req_t req, fuse_ino_t ino, struct stat *attr, if (fi) { res = ftruncate(fi->fh, attr->st_size); } else { - res = with_fd_path(ifd, [attr](const char* procname) { + res = with_fd_path(ifd, [attr](const char *procname) { return truncate(procname, attr->st_size); }); } @@ -333,7 +333,7 @@ static void do_setattr(fuse_req_t req, fuse_ino_t ino, struct stat *attr, res = futimens(fi->fh, tv); else { #ifdef HAVE_UTIMENSAT - res = with_fd_path(ifd, [&tv](const char* procname) { + res = with_fd_path(ifd, [&tv](const char *procname) { return utimensat(AT_FDCWD, procname, tv, 0); }); #else @@ -1089,7 +1089,7 @@ static void sfs_open(fuse_req_t req, fuse_ino_t ino, fuse_file_info *fi) /* Unfortunately we cannot use inode.fd, because this was opened with O_PATH (so it doesn't allow read/write access). */ - auto fd = with_fd_path(inode.fd, [fi](const char* buf) { + auto fd = with_fd_path(inode.fd, [fi](const char *buf) { return open(buf, fi->flags & ~O_NOFOLLOW); }); if (fd == -1) { From 63579dffce8ab079a1a1eab047e63dc1211185d2 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 26 Aug 2025 16:58:03 +0000 Subject: [PATCH 186/241] build(deps): bump github/codeql-action from 3.29.10 to 3.29.11 Bumps [github/codeql-action](https://github.com/github/codeql-action) from 3.29.10 to 3.29.11. - [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/96f518a34f7a870018057716cc4d7a5c014bd61c...3c3833e0f8c1c83d449a7478aa59c036a9165498) --- updated-dependencies: - dependency-name: github/codeql-action dependency-version: 3.29.11 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 bcc631d0f..ba6422916 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@96f518a34f7a870018057716cc4d7a5c014bd61c # v3.29.5 + uses: github/codeql-action/init@3c3833e0f8c1c83d449a7478aa59c036a9165498 # v3.29.5 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@96f518a34f7a870018057716cc4d7a5c014bd61c # v3.29.5 + uses: github/codeql-action/analyze@3c3833e0f8c1c83d449a7478aa59c036a9165498 # v3.29.5 with: category: "/language:${{matrix.language}}" From 49ad73a46b6237f0f1584b30ade81a3e6744e695 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 8 Sep 2025 22:05:47 +0000 Subject: [PATCH 187/241] build(deps): bump github/codeql-action from 3.29.11 to 3.30.1 Bumps [github/codeql-action](https://github.com/github/codeql-action) from 3.29.11 to 3.30.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/3c3833e0f8c1c83d449a7478aa59c036a9165498...f1f6e5f6af878fb37288ce1c627459e94dbf7d01) --- updated-dependencies: - dependency-name: github/codeql-action dependency-version: 3.30.1 dependency-type: direct:production update-type: version-update:semver-minor ... 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 ba6422916..f919a086a 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@3c3833e0f8c1c83d449a7478aa59c036a9165498 # v3.29.5 + uses: github/codeql-action/init@f1f6e5f6af878fb37288ce1c627459e94dbf7d01 # v3.29.5 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@3c3833e0f8c1c83d449a7478aa59c036a9165498 # v3.29.5 + uses: github/codeql-action/analyze@f1f6e5f6af878fb37288ce1c627459e94dbf7d01 # v3.29.5 with: category: "/language:${{matrix.language}}" From 3ff309ea9efa8fc95274d35a96aa5700f0a0d6c1 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 8 Sep 2025 22:05:38 +0000 Subject: [PATCH 188/241] build(deps): bump actions/setup-python from 5 to 6 Bumps [actions/setup-python](https://github.com/actions/setup-python) from 5 to 6. - [Release notes](https://github.com/actions/setup-python/releases) - [Commits](https://github.com/actions/setup-python/compare/v5...v6) --- updated-dependencies: - dependency-name: actions/setup-python dependency-version: '6' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] <support@github.com> --- .github/workflows/abicheck.yml | 2 +- .github/workflows/pr-ci.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/abicheck.yml b/.github/workflows/abicheck.yml index 8ab13da8a..a00449c3a 100644 --- a/.github/workflows/abicheck.yml +++ b/.github/workflows/abicheck.yml @@ -37,7 +37,7 @@ jobs: path: previous ref: ${{ github.event.pull_request.base.ref }} - - uses: actions/setup-python@f677139bbe7f9c59b41e40162b753c062f5d49a3 # v5.2.0 + - uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6.0.0 with: python-version: '3.12' diff --git a/.github/workflows/pr-ci.yml b/.github/workflows/pr-ci.yml index b0134cf6b..ebd79c427 100644 --- a/.github/workflows/pr-ci.yml +++ b/.github/workflows/pr-ci.yml @@ -34,7 +34,7 @@ jobs: libpcap0.8-dev:i386 libudev-dev:i386 pkg-config:i386 \ liburing-dev libnuma-dev - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 - - uses: actions/setup-python@v5 + - uses: actions/setup-python@v6 with: python-version: '3.12' - run: pip install -r requirements.txt From 2317d8602334093ea0c88efee2e6ac326d42adc7 Mon Sep 17 00:00:00 2001 From: Miklos Szeredi <mszeredi@redhat.com> Date: Tue, 5 Aug 2025 20:31:53 +0200 Subject: [PATCH 189/241] libfuse: fix COPY_FILE_RANGE interface The FUSE protocol uses struct fuse_write_out to convey the return value of copy_file_range, which is restricted to uint32_t. But the COPY_FILE_RANGE interface supports a 64-bit size copies. Currently the number of bytes copied is silently truncated to 32-bit, which is unfortunate at best. Implement the COPY_FILE_RANGE_64 interface which is identical to the old one, except the number of bytes copied is returned in a 64-bit value. The library interface remains the same. If the kernel does not support the new interface or the server is running as a 32-bit process, limit the copy size to size to UINT_MAX - 4096. Edit by Bernd: Keep ioctl_64bit and add use new bit is_copy_file_range_64 to keep flags separated from each other - easier code readability IMO. Reported-by: Florian Weimer <fweimer@redhat.com> Closes: https://lore.kernel.org/all/lhuh5ynl8z5.fsf@oldenburg.str.redhat.com/ Signed-off-by: Miklos Szeredi <mszeredi@redhat.com> Signed-off-by: Bernd Schubert <bschubert@ddn.com> --- include/fuse_kernel.h | 12 +++++++- lib/fuse_i.h | 1 + lib/fuse_lowlevel.c | 66 +++++++++++++++++++++++++++++++++++++++---- 3 files changed, 73 insertions(+), 6 deletions(-) diff --git a/include/fuse_kernel.h b/include/fuse_kernel.h index 122d6586e..94621f68a 100644 --- a/include/fuse_kernel.h +++ b/include/fuse_kernel.h @@ -235,6 +235,10 @@ * * 7.44 * - add FUSE_NOTIFY_INC_EPOCH + * + * 7.45 + * - add FUSE_COPY_FILE_RANGE_64 + * - add struct fuse_copy_file_range_out */ #ifndef _LINUX_FUSE_H @@ -270,7 +274,7 @@ #define FUSE_KERNEL_VERSION 7 /** Minor version number of this interface */ -#define FUSE_KERNEL_MINOR_VERSION 44 +#define FUSE_KERNEL_MINOR_VERSION 45 /** The node ID of the root inode */ #define FUSE_ROOT_ID 1 @@ -657,6 +661,7 @@ enum fuse_opcode { FUSE_SYNCFS = 50, FUSE_TMPFILE = 51, FUSE_STATX = 52, + FUSE_COPY_FILE_RANGE_64 = 53, /* CUSE specific operations */ CUSE_INIT = 4096, @@ -1148,6 +1153,11 @@ struct fuse_copy_file_range_in { uint64_t flags; }; +/* For FUSE_COPY_FILE_RANGE_64 */ +struct fuse_copy_file_range_out { + uint64_t bytes_copied; +}; + #define FUSE_SETUPMAPPING_FLAG_WRITE (1ull << 0) #define FUSE_SETUPMAPPING_FLAG_READ (1ull << 1) struct fuse_setupmapping_in { diff --git a/lib/fuse_i.h b/lib/fuse_i.h index 0d0e637ae..d3d86d417 100644 --- a/lib/fuse_i.h +++ b/lib/fuse_i.h @@ -40,6 +40,7 @@ struct fuse_req { int interrupted; unsigned int ioctl_64bit : 1; unsigned int is_uring : 1; + unsigned int is_copy_file_range_64 : 1; union { struct { uint64_t unique; diff --git a/lib/fuse_lowlevel.c b/lib/fuse_lowlevel.c index e4544dfc3..1e4531576 100644 --- a/lib/fuse_lowlevel.c +++ b/lib/fuse_lowlevel.c @@ -589,7 +589,7 @@ int fuse_reply_open(fuse_req_t req, const struct fuse_file_info *f) return send_reply_ok(req, &arg, sizeof(arg)); } -int fuse_reply_write(fuse_req_t req, size_t count) +static int do_fuse_reply_write(fuse_req_t req, size_t count) { struct fuse_write_out arg; @@ -599,6 +599,28 @@ int fuse_reply_write(fuse_req_t req, size_t count) return send_reply_ok(req, &arg, sizeof(arg)); } +static int do_fuse_reply_copy(fuse_req_t req, size_t count) +{ + struct fuse_copy_file_range_out arg; + + memset(&arg, 0, sizeof(arg)); + arg.bytes_copied = count; + + return send_reply_ok(req, &arg, sizeof(arg)); +} + +int fuse_reply_write(fuse_req_t req, size_t count) +{ + /* + * This function is also used by FUSE_COPY_FILE_RANGE and its 64-bit + * variant. + */ + if (req->is_copy_file_range_64) + return do_fuse_reply_copy(req, count); + else + return do_fuse_reply_write(req, count); +} + int fuse_reply_buf(fuse_req_t req, const char *buf, size_t size) { return send_reply_ok(req, buf, size); @@ -2403,11 +2425,9 @@ static void do_fallocate(fuse_req_t req, const fuse_ino_t nodeid, _do_fallocate(req, nodeid, inarg, NULL); } -static void _do_copy_file_range(fuse_req_t req, const fuse_ino_t nodeid_in, - const void *op_in, const void *in_payload) +static void copy_file_range_common(fuse_req_t req, const fuse_ino_t nodeid_in, + const struct fuse_copy_file_range_in *arg) { - (void)in_payload; - const struct fuse_copy_file_range_in *arg = op_in; struct fuse_file_info fi_in, fi_out; memset(&fi_in, 0, sizeof(fi_in)); @@ -2424,12 +2444,46 @@ static void _do_copy_file_range(fuse_req_t req, const fuse_ino_t nodeid_in, fuse_reply_err(req, ENOSYS); } +static void _do_copy_file_range(fuse_req_t req, const fuse_ino_t nodeid_in, + const void *op_in, const void *in_payload) +{ + const struct fuse_copy_file_range_in *arg = op_in; + struct fuse_copy_file_range_in arg_tmp; + + (void) in_payload; + /* fuse_write_out can only handle 32bit copy size */ + if (arg->len > 0xfffff000) { + arg_tmp = *arg; + arg_tmp.len = 0xfffff000; + arg = &arg_tmp; + } + copy_file_range_common(req, nodeid_in, arg); +} + static void do_copy_file_range(fuse_req_t req, const fuse_ino_t nodeid_in, const void *inarg) { _do_copy_file_range(req, nodeid_in, inarg, NULL); } +static void _do_copy_file_range_64(fuse_req_t req, const fuse_ino_t nodeid_in, + const void *op_in, const void *in_payload) +{ + (void) in_payload; + req->is_copy_file_range_64 = 1; + /* Limit size on 32bit userspace to avoid conversion overflow */ + if (sizeof(size_t) == 4) + _do_copy_file_range(req, nodeid_in, op_in, NULL); + else + copy_file_range_common(req, nodeid_in, op_in); +} + +static void do_copy_file_range_64(fuse_req_t req, const fuse_ino_t nodeid_in, + const void *inarg) +{ + _do_copy_file_range_64(req, nodeid_in, inarg, NULL); +} + /* * Note that the uint64_t offset in struct fuse_lseek_in is derived from * linux kernel loff_t and is therefore signed. @@ -3378,6 +3432,7 @@ static struct { [FUSE_READDIRPLUS] = { do_readdirplus, "READDIRPLUS"}, [FUSE_RENAME2] = { do_rename2, "RENAME2" }, [FUSE_COPY_FILE_RANGE] = { do_copy_file_range, "COPY_FILE_RANGE" }, + [FUSE_COPY_FILE_RANGE_64] = { do_copy_file_range_64, "COPY_FILE_RANGE_64" }, [FUSE_LSEEK] = { do_lseek, "LSEEK" }, [FUSE_STATX] = { do_statx, "STATX" }, [CUSE_INIT] = { cuse_lowlevel_init, "CUSE_INIT" }, @@ -3433,6 +3488,7 @@ static struct { [FUSE_READDIRPLUS] = { _do_readdirplus, "READDIRPLUS" }, [FUSE_RENAME2] = { _do_rename2, "RENAME2" }, [FUSE_COPY_FILE_RANGE] = { _do_copy_file_range, "COPY_FILE_RANGE" }, + [FUSE_COPY_FILE_RANGE_64] = { _do_copy_file_range_64, "COPY_FILE_RANGE_64" }, [FUSE_LSEEK] = { _do_lseek, "LSEEK" }, [FUSE_STATX] = { _do_statx, "STATX" }, [CUSE_INIT] = { _cuse_lowlevel_init, "CUSE_INIT" }, From 939fc0dd3ed525c0be780537cfa41ffcbf98648c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 15 Sep 2025 22:05:46 +0000 Subject: [PATCH 190/241] build(deps): bump github/codeql-action from 3.30.1 to 3.30.3 Bumps [github/codeql-action](https://github.com/github/codeql-action) from 3.30.1 to 3.30.3. - [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/f1f6e5f6af878fb37288ce1c627459e94dbf7d01...192325c86100d080feab897ff886c34abd4c83a3) --- updated-dependencies: - dependency-name: github/codeql-action dependency-version: 3.30.3 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 f919a086a..19a4569a8 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@f1f6e5f6af878fb37288ce1c627459e94dbf7d01 # v3.29.5 + uses: github/codeql-action/init@192325c86100d080feab897ff886c34abd4c83a3 # v3.29.5 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@f1f6e5f6af878fb37288ce1c627459e94dbf7d01 # v3.29.5 + uses: github/codeql-action/analyze@192325c86100d080feab897ff886c34abd4c83a3 # v3.29.5 with: category: "/language:${{matrix.language}}" From 6e4557c57c4ce63de4042f77dcbf58627d13a59b Mon Sep 17 00:00:00 2001 From: "Darrick J. Wong" <djwong@kernel.org> Date: Mon, 15 Sep 2025 17:41:47 -0700 Subject: [PATCH 191/241] libfuse: don't put HAVE_STATX in a public header fuse.h and fuse_lowlevel.h are already forward declaring struct statx, there is no need for HAVE_STATX anymore. HAVE_STATX also bears the risk to conflict with an application define. Alternatively it would have been possible to change to HAVE_FUSE_STATX. Get rid of the conditionals in the public header files and also remove HAVE_STATX definition from the public libfuse_config.h. Edit by Bernd: Commit message and removal of HAVE_STATX from public libfuse_config.h. Signed-off-by: Darrick J. Wong <djwong@kernel.org> Signed-off-by: Bernd Schubert <bernd@bsbernd.com> --- example/memfs_ll.cc | 2 +- example/passthrough.c | 2 +- example/passthrough_fh.c | 2 +- example/passthrough_ll.c | 2 +- include/fuse.h | 2 -- include/fuse_lowlevel.h | 2 -- meson.build | 2 -- 7 files changed, 4 insertions(+), 10 deletions(-) diff --git a/example/memfs_ll.cc b/example/memfs_ll.cc index edda34b4e..7055a434a 100644 --- a/example/memfs_ll.cc +++ b/example/memfs_ll.cc @@ -6,7 +6,7 @@ See the file GPL2.txt. */ -#define FUSE_USE_VERSION 317 +#define FUSE_USE_VERSION FUSE_MAKE_VERSION(3, 18) #include <algorithm> #include <stdio.h> diff --git a/example/passthrough.c b/example/passthrough.c index fdaa19e33..1f09c2dc0 100644 --- a/example/passthrough.c +++ b/example/passthrough.c @@ -23,7 +23,7 @@ */ -#define FUSE_USE_VERSION 31 +#define FUSE_USE_VERSION FUSE_MAKE_VERSION(3, 18) #define _GNU_SOURCE diff --git a/example/passthrough_fh.c b/example/passthrough_fh.c index 0d4fb5bd4..6403fbb74 100644 --- a/example/passthrough_fh.c +++ b/example/passthrough_fh.c @@ -23,7 +23,7 @@ * \include passthrough_fh.c */ -#define FUSE_USE_VERSION 31 +#define FUSE_USE_VERSION FUSE_MAKE_VERSION(3, 18) #define _GNU_SOURCE diff --git a/example/passthrough_ll.c b/example/passthrough_ll.c index 5ca6efa23..8a5ac2e92 100644 --- a/example/passthrough_ll.c +++ b/example/passthrough_ll.c @@ -35,7 +35,7 @@ */ #define _GNU_SOURCE -#define FUSE_USE_VERSION FUSE_MAKE_VERSION(3, 12) +#define FUSE_USE_VERSION FUSE_MAKE_VERSION(3, 18) #include <fuse_lowlevel.h> #include <unistd.h> diff --git a/include/fuse.h b/include/fuse.h index 06feacb07..209102651 100644 --- a/include/fuse.h +++ b/include/fuse.h @@ -854,7 +854,6 @@ struct fuse_operations { */ off_t (*lseek) (const char *, off_t off, int whence, struct fuse_file_info *); -#ifdef HAVE_STATX /** * Get extended file attributes. * @@ -865,7 +864,6 @@ struct fuse_operations { */ int (*statx)(const char *path, int flags, int mask, struct statx *stxbuf, struct fuse_file_info *fi); -#endif }; /** Extra context that may be needed by some filesystems diff --git a/include/fuse_lowlevel.h b/include/fuse_lowlevel.h index dc4ec07e6..a6cce0143 100644 --- a/include/fuse_lowlevel.h +++ b/include/fuse_lowlevel.h @@ -1327,7 +1327,6 @@ struct fuse_lowlevel_ops { void (*tmpfile) (fuse_req_t req, fuse_ino_t parent, mode_t mode, struct fuse_file_info *fi); -#ifdef HAVE_STATX /** * Get extended file attributes. * @@ -1343,7 +1342,6 @@ struct fuse_lowlevel_ops { */ void (*statx)(fuse_req_t req, fuse_ino_t ino, int flags, int mask, struct fuse_file_info *fi); -#endif }; /** diff --git a/meson.build b/meson.build index 07c729581..1dd687c1a 100644 --- a/meson.build +++ b/meson.build @@ -122,8 +122,6 @@ 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>')) -public_cfg.set('HAVE_STATX', - cc.has_function('statx', prefix : '#define _GNU_SOURCE\n#include <sys/stat.h>')) private_cfg.set('HAVE_STATX', cc.has_function('statx', prefix : '#define _GNU_SOURCE\n#include <sys/stat.h>')) From 52a633a5d4435f1965fc8d44cf9a705e6679f793 Mon Sep 17 00:00:00 2001 From: Alik Aslanyan <inline0@pm.me> Date: Tue, 9 Sep 2025 23:35:40 +0400 Subject: [PATCH 192/241] fuse_lowlevel.c: allow passing ops as NULL During testing of LACT using various sanitizers issue with libfuse.so leaking memory and reading uninitialized memory was uncovered. While this was due to ABI misusage on the side of fuser (Rust wrapper for libfuse.so), we now allow creating a no-op session by passing ops as NULL. fuser and other libfuse users is using fuse_session_new() with NULL struct fuse_lowlevel_ops *op and op_size=0. This resulted in a warning, but otherwise succeeded. The resulting fuse_session is just passed to fuse_session_mount() so that libfuse could do the actual mount. Fuse kernel request handling is not done through libfuse but their own layer. Edit by Bernd: - Update the commit message - moved using null_ops to fuse_session_new_30() as that is actually called by the external fuse_session_new(). - forbidding NULL struct fuse_lowlevel_ops *op and 0 op_size in fuse_session_new_versioned() - various other NULL ptr checks in fuse_session_new() that now result in failure. Signed-off-by: Alik Aslanyan <inline0@pm.me> Signed-off-by: Bernd Schubert <bernd@bsbernd.com> --- include/fuse_lowlevel.h | 2 ++ lib/fuse_lowlevel.c | 26 +++++++++++++++++++++++++- 2 files changed, 27 insertions(+), 1 deletion(-) diff --git a/include/fuse_lowlevel.h b/include/fuse_lowlevel.h index a6cce0143..312b33133 100644 --- a/include/fuse_lowlevel.h +++ b/include/fuse_lowlevel.h @@ -2115,6 +2115,8 @@ fuse_session_new_versioned(struct fuse_args *args, * If not all options are known, an error message is written to stderr * and the function returns NULL. * + * To create a no-op session just for mounting pass op as NULL. + * * Option parsing skips argv[0], which is assumed to contain the * program name. To prevent accidentally passing an option in * argv[0], this element must always be present (even if no options diff --git a/lib/fuse_lowlevel.c b/lib/fuse_lowlevel.c index 1e4531576..cb757cd92 100644 --- a/lib/fuse_lowlevel.c +++ b/lib/fuse_lowlevel.c @@ -4108,12 +4108,23 @@ fuse_session_new_versioned(struct fuse_args *args, struct fuse_session *se; struct mount_opts *mo; + if (op == NULL || op_size == 0) { + fuse_log(FUSE_LOG_ERR, + "fuse: warning: empty op list passed to fuse_session_new()\n"); + return NULL; + } + + if (version == NULL) { + fuse_log(FUSE_LOG_ERR, "fuse: warning: version not passed to fuse_session_new()\n"); + return NULL; + } + if (sizeof(struct fuse_lowlevel_ops) < op_size) { fuse_log(FUSE_LOG_ERR, "fuse: warning: library too old, some operations may not work\n"); op_size = sizeof(struct fuse_lowlevel_ops); } - if (args->argc == 0) { + if (args == NULL || args->argc == 0) { fuse_log(FUSE_LOG_ERR, "fuse: empty argv passed to fuse_session_new().\n"); return NULL; } @@ -4225,9 +4236,22 @@ struct fuse_session *fuse_session_new_30(struct fuse_args *args, size_t op_size, void *userdata) { + struct fuse_lowlevel_ops null_ops = { 0 }; + /* unknown version */ struct libfuse_version version = { 0 }; + /* + * This function is the ABI interface function from fuse_session_new in + * compat.c. External libraries like "fuser" might call fuse_session_new() + * with NULL ops and then pass that session to fuse_session_mount(). + * The actual FUSE operations are handled in their own library. + */ + if (op == NULL) { + op = &null_ops; + op_size = sizeof(null_ops); + } + return fuse_session_new_versioned(args, op, op_size, &version, userdata); } From 3e2cd9e46c87a57de374b82fd198328f7745e942 Mon Sep 17 00:00:00 2001 From: Bernd Schubert <bernd@bsbernd.com> Date: Thu, 18 Sep 2025 23:39:11 +0200 Subject: [PATCH 193/241] fuse_log: Add __attribute__((format(printf, ) and fix warnings fuse_log() did not have that attribute and so compilers didn't give warnings for plain printf(). Add the attribute and fix related warnings. Signed-off-by: Bernd Schubert <bernd@bsbernd.com> --- example/passthrough_ll.c | 30 ++++++++++++++++-------------- include/fuse_log.h | 3 ++- lib/fuse.c | 8 ++++---- lib/fuse_lowlevel.c | 23 +++++++++++++++-------- lib/fuse_uring.c | 5 +++-- lib/mount.c | 2 +- 6 files changed, 41 insertions(+), 30 deletions(-) diff --git a/example/passthrough_ll.c b/example/passthrough_ll.c index 8a5ac2e92..dce8355cb 100644 --- a/example/passthrough_ll.c +++ b/example/passthrough_ll.c @@ -315,7 +315,7 @@ static struct lo_inode *create_new_inode(int fd, struct fuse_entry_param *e, str { struct lo_inode *inode = NULL; struct lo_inode *prev, *next; - + inode = calloc(1, sizeof(struct lo_inode)); if (!inode) return NULL; @@ -352,7 +352,7 @@ static int fill_entry_param_new_inode(fuse_req_t req, fuse_ino_t parent, int fd, e->ino = (uintptr_t) create_new_inode(dup(fd), e, lo); if (lo_debug(req)) - fuse_log(FUSE_LOG_DEBUG, " %lli/%lli -> %lli\n", + fuse_log(FUSE_LOG_DEBUG, " %lli/%d -> %lli\n", (unsigned long long) parent, fd, (unsigned long long) e->ino); return 0; @@ -712,7 +712,7 @@ static void lo_do_readdir(fuse_req_t req, fuse_ino_t ino, size_t size, err = errno; goto error; } else { // End of stream - break; + break; } } } @@ -744,11 +744,11 @@ static void lo_do_readdir(fuse_req_t req, fuse_ino_t ino, size_t size, &st, nextoff); } if (entsize > rem) { - if (entry_ino != 0) + if (entry_ino != 0) lo_forget_one(req, entry_ino, 1); break; } - + p += entsize; rem -= entsize; @@ -816,9 +816,9 @@ static void lo_tmpfile(fuse_req_t req, fuse_ino_t parent, /* parallel_direct_writes feature depends on direct_io features. To make parallel_direct_writes valid, need set fi->direct_io in current function. */ - fi->parallel_direct_writes = 1; - - err = fill_entry_param_new_inode(req, parent, fd, &e); + fi->parallel_direct_writes = 1; + + err = fill_entry_param_new_inode(req, parent, fd, &e); if (err) fuse_reply_err(req, err); else @@ -981,8 +981,8 @@ static void lo_write_buf(fuse_req_t req, fuse_ino_t ino, out_buf.buf[0].pos = off; if (lo_debug(req)) - fuse_log(FUSE_LOG_DEBUG, "lo_write(ino=%" PRIu64 ", size=%zd, off=%lu)\n", - ino, out_buf.buf[0].size, (unsigned long) off); + fuse_log(FUSE_LOG_DEBUG, "lo_write(ino=%" PRIu64 ", size=%zd, off=%jd)\n", + ino, out_buf.buf[0].size, (intmax_t) off); res = fuse_buf_copy(&out_buf, in_buf, 0); if(res < 0) @@ -1187,10 +1187,12 @@ static void lo_copy_file_range(fuse_req_t req, fuse_ino_t ino_in, off_t off_in, ssize_t res; if (lo_debug(req)) - fuse_log(FUSE_LOG_DEBUG, "lo_copy_file_range(ino=%" PRIu64 "/fd=%lu, " - "off=%lu, ino=%" PRIu64 "/fd=%lu, " - "off=%lu, size=%zd, flags=0x%x)\n", - ino_in, fi_in->fh, off_in, ino_out, fi_out->fh, off_out, + fuse_log(FUSE_LOG_DEBUG, + "%s(ino=%lld fd=%lld off=%jd ino=%lld fd=%lld off=%jd, size=%zd, flags=0x%x)\n", + __func__, (unsigned long long)ino_in, + (unsigned long long)fi_in->fh, + (intmax_t) off_in, (unsigned long long)ino_out, + (unsigned long long)fi_out->fh, (intmax_t) off_out, len, flags); res = copy_file_range(fi_in->fh, &off_in, fi_out->fh, &off_out, len, diff --git a/include/fuse_log.h b/include/fuse_log.h index b901db252..7948bab14 100644 --- a/include/fuse_log.h +++ b/include/fuse_log.h @@ -73,7 +73,8 @@ void fuse_set_log_func(fuse_log_func_t func); * @param level severity level (FUSE_LOG_ERR, FUSE_LOG_DEBUG, etc) * @param fmt sprintf-style format string including newline */ -void fuse_log(enum fuse_log_level level, const char *fmt, ...); +void fuse_log(enum fuse_log_level level, const char *fmt, ...) + __attribute__((format(printf, 2, 3))); /** * Switch default log handler from stderr to syslog diff --git a/lib/fuse.c b/lib/fuse.c index e997531f4..4cc6f3b1c 100644 --- a/lib/fuse.c +++ b/lib/fuse.c @@ -2122,10 +2122,10 @@ int fuse_fs_utimens(struct fuse_fs *fs, const char *path, if (fs->debug) { char buf[10]; - fuse_log(FUSE_LOG_DEBUG, "utimens[%s] %s %li.%09lu %li.%09lu\n", - file_info_string(fi, buf, sizeof(buf)), - path, tv[0].tv_sec, tv[0].tv_nsec, - tv[1].tv_sec, tv[1].tv_nsec); + fuse_log(FUSE_LOG_DEBUG, "utimens[%s] %s %jd.%09ld %jd.%09ld\n", + file_info_string(fi, buf, sizeof(buf)), + path, (intmax_t)tv[0].tv_sec, tv[0].tv_nsec, + (intmax_t)tv[1].tv_sec, tv[1].tv_nsec); } return fs->op.utimens(path, tv, fi); } diff --git a/lib/fuse_lowlevel.c b/lib/fuse_lowlevel.c index cb757cd92..273d4dc6a 100644 --- a/lib/fuse_lowlevel.c +++ b/lib/fuse_lowlevel.c @@ -23,6 +23,7 @@ #include <pthread.h> #include <stdatomic.h> #include <stdint.h> +#include <inttypes.h> #include <stdbool.h> #include <stdio.h> #include <stdlib.h> @@ -753,11 +754,15 @@ static int read_back(int fd, char *buf, size_t len) res = read(fd, buf, len); if (res == -1) { - fuse_log(FUSE_LOG_ERR, "fuse: internal error: failed to read back from pipe: %s\n", strerror(errno)); + fuse_log(FUSE_LOG_ERR, + "fuse: internal error: failed to read back from pipe: %s\n", + strerror(errno)); return -EIO; } if (res != len) { - fuse_log(FUSE_LOG_ERR, "fuse: internal error: short read back from pipe: %i from %zi\n", res, len); + fuse_log(FUSE_LOG_ERR, + "fuse: internal error: short read back from pipe: %i from %zd\n", + res, len); return -EIO; } return 0; @@ -2550,8 +2555,8 @@ static bool want_flags_valid(uint64_t capable, uint64_t want) uint64_t unknown_flags = want & (~capable); if (unknown_flags != 0) { fuse_log(FUSE_LOG_ERR, - "fuse: unknown connection 'want' flags: 0x%08lx\n", - unknown_flags); + "fuse: unknown connection 'want' flags: 0x%08llx\n", + (unsigned long long)unknown_flags); return false; } return true; @@ -2577,10 +2582,12 @@ int fuse_convert_to_conn_want_ext(struct fuse_conn_info *conn) 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); + "%s: Both conn->want_ext and conn->want are set.\n" + "want=%x want_ext=%llx, se->want=%x se->want_ext=%llx\n", + __func__, conn->want, + (unsigned long long)conn->want_ext, + se->conn_want, + (unsigned long long)se->conn_want_ext); return -EINVAL; } diff --git a/lib/fuse_uring.c b/lib/fuse_uring.c index 4c6f0a483..85b5a7fe3 100644 --- a/lib/fuse_uring.c +++ b/lib/fuse_uring.c @@ -18,6 +18,7 @@ #include <liburing.h> #include <sys/sysinfo.h> #include <stdint.h> +#include <inttypes.h> #include <stdbool.h> #include <string.h> #include <unistd.h> @@ -180,7 +181,7 @@ static int fuse_uring_commit_sqe(struct fuse_ring_pool *ring_pool, ring_ent->req_commit_id); if (se->debug) { - fuse_log(FUSE_LOG_DEBUG, " unique: %llu, result=%d\n", + fuse_log(FUSE_LOG_DEBUG, " unique: %" PRIu64 ", result=%d\n", out->unique, ent_in_out->payload_sz); } @@ -453,7 +454,7 @@ static int fuse_uring_prepare_fetch_sqes(struct fuse_ring_queue *queue) sq_ready = io_uring_sq_ready(&queue->ring); if (sq_ready != ring_pool->queue_depth) { fuse_log(FUSE_LOG_ERR, - "SQE ready mismatch, expected %d got %d\n", + "SQE ready mismatch, expected %zu got %u\n", ring_pool->queue_depth, sq_ready); return -EINVAL; } diff --git a/lib/mount.c b/lib/mount.c index 2eb967399..549268006 100644 --- a/lib/mount.c +++ b/lib/mount.c @@ -361,7 +361,7 @@ static int setup_auto_unmount(const char *mountpoint, int quiet) res = socketpair(PF_UNIX, SOCK_STREAM, 0, fds); if(res == -1) { - fuse_log(FUSE_LOG_ERR, "Setting up auto-unmountsocketpair() failed", + fuse_log(FUSE_LOG_ERR, "Setting up auto-unmount socketpair() failed: %s\n", strerror(errno)); return -1; } From 50d1bec4794aae9e77062de5214b91bcc52a890d Mon Sep 17 00:00:00 2001 From: Bernd Schubert <bernd@bsbernd.com> Date: Thu, 18 Sep 2025 23:49:41 +0200 Subject: [PATCH 194/241] io-uring: re-initialize fields in struct fuse_req In io-uring mode these requests are always re-used and not allocated - we need to re-initialize them. In order to set flags to zero a struct holding the flags had to be added. Signed-off-by: Bernd Schubert <bernd@bsbernd.com> --- lib/fuse_i.h | 9 ++++++--- lib/fuse_lowlevel.c | 23 +++++++++++------------ lib/fuse_uring.c | 13 +++++++++---- 3 files changed, 26 insertions(+), 19 deletions(-) diff --git a/lib/fuse_i.h b/lib/fuse_i.h index d3d86d417..d35e1e51d 100644 --- a/lib/fuse_i.h +++ b/lib/fuse_i.h @@ -38,9 +38,11 @@ struct fuse_req { struct fuse_ctx ctx; struct fuse_chan *ch; int interrupted; - unsigned int ioctl_64bit : 1; - unsigned int is_uring : 1; - unsigned int is_copy_file_range_64 : 1; + struct { + unsigned int ioctl_64bit : 1; + unsigned int is_uring : 1; + unsigned int is_copy_file_range_64 : 1; + } flags; union { struct { uint64_t unique; @@ -218,6 +220,7 @@ int fuse_kern_mount(const char *mountpoint, struct mount_opts *mo); int fuse_send_reply_iov_nofree(fuse_req_t req, int error, struct iovec *iov, int count); void fuse_free_req(fuse_req_t req); +void list_init_req(struct fuse_req *req); void _cuse_lowlevel_init(fuse_req_t req, const fuse_ino_t nodeid, const void *req_header, const void *req_payload); diff --git a/lib/fuse_lowlevel.c b/lib/fuse_lowlevel.c index 273d4dc6a..cacab94bc 100644 --- a/lib/fuse_lowlevel.c +++ b/lib/fuse_lowlevel.c @@ -146,7 +146,7 @@ static size_t iov_length(const struct iovec *iov, size_t count) return ret; } -static void list_init_req(struct fuse_req *req) +void list_init_req(struct fuse_req *req) { req->next = req; req->prev = req; @@ -171,7 +171,7 @@ static void list_add_req(struct fuse_req *req, struct fuse_req *next) static void destroy_req(fuse_req_t req) { - if (req->is_uring) { + if (req->flags.is_uring) { fuse_log(FUSE_LOG_ERR, "Refusing to destruct uring req\n"); return; } @@ -189,7 +189,7 @@ void fuse_free_req(fuse_req_t req) * It actually might work already, though. But then would add * a lock across ring queues. */ - if (se->conn.no_interrupt || req->is_uring) { + if (se->conn.no_interrupt || req->flags.is_uring) { ctr = --req->ref_cnt; fuse_chan_put(req->ch); req->ch = NULL; @@ -219,7 +219,6 @@ static struct fuse_req *fuse_ll_alloc_req(struct fuse_session *se) req->ref_cnt = 1; list_init_req(req); pthread_mutex_init(&req->lock, NULL); - req->is_uring = false; } return req; @@ -260,7 +259,7 @@ static int fuse_send_msg(struct fuse_session *se, struct fuse_chan *ch, { struct fuse_out_header *out = iov[0].iov_base; int err; - bool is_uring = req && req->is_uring ? true : false; + bool is_uring = req && req->flags.is_uring ? true : false; if (!is_uring) assert(se != NULL); @@ -328,7 +327,7 @@ static int send_reply_iov(fuse_req_t req, int error, struct iovec *iov, static int send_reply(fuse_req_t req, int error, const void *arg, size_t argsize) { - if (req->is_uring) + if (req->flags.is_uring) return send_reply_uring(req, error, arg, argsize); struct iovec iov[2]; @@ -616,7 +615,7 @@ int fuse_reply_write(fuse_req_t req, size_t count) * This function is also used by FUSE_COPY_FILE_RANGE and its 64-bit * variant. */ - if (req->is_copy_file_range_64) + if (req->flags.is_copy_file_range_64) return do_fuse_reply_copy(req, count); else return do_fuse_reply_write(req, count); @@ -1023,7 +1022,7 @@ int fuse_reply_data(fuse_req_t req, struct fuse_bufvec *bufv, struct fuse_out_header out; int res; - if (req->is_uring) + if (req->flags.is_uring) return fuse_reply_data_uring(req, bufv, flags); iov[0].iov_base = &out; @@ -1141,7 +1140,7 @@ int fuse_reply_ioctl_retry(fuse_req_t req, } } else { /* Can't handle non-compat 64bit ioctls on 32bit */ - if (sizeof(void *) == 4 && req->ioctl_64bit) { + if (sizeof(void *) == 4 && req->flags.ioctl_64bit) { res = fuse_reply_err(req, EINVAL); goto out; } @@ -2349,7 +2348,7 @@ static void _do_ioctl(fuse_req_t req, const fuse_ino_t nodeid, if (sizeof(void *) == 4 && req->se->conn.proto_minor >= 16 && !(flags & FUSE_IOCTL_32BIT)) { - req->ioctl_64bit = 1; + req->flags.ioctl_64bit = 1; } if (req->se->op.ioctl) @@ -2475,7 +2474,7 @@ static void _do_copy_file_range_64(fuse_req_t req, const fuse_ino_t nodeid_in, const void *op_in, const void *in_payload) { (void) in_payload; - req->is_copy_file_range_64 = 1; + req->flags.is_copy_file_range_64 = 1; /* Limit size on 32bit userspace to avoid conversion overflow */ if (sizeof(size_t) == 4) _do_copy_file_range(req, nodeid_in, op_in, NULL); @@ -3375,7 +3374,7 @@ int fuse_req_interrupted(fuse_req_t req) bool fuse_req_is_uring(fuse_req_t req) { - return req->is_uring; + return req->flags.is_uring; } #ifndef HAVE_URING diff --git a/lib/fuse_uring.c b/lib/fuse_uring.c index 85b5a7fe3..2fea05f97 100644 --- a/lib/fuse_uring.c +++ b/lib/fuse_uring.c @@ -197,7 +197,7 @@ int fuse_req_get_payload(fuse_req_t req, char **payload, size_t *payload_sz, struct fuse_ring_ent *ring_ent; /* Not possible without io-uring interface */ - if (!req->is_uring) + if (!req->flags.is_uring) return -EINVAL; ring_ent = container_of(req, struct fuse_ring_ent, req); @@ -560,9 +560,13 @@ static void fuse_uring_handle_cqe(struct fuse_ring_queue *queue, abort(); } - req->is_uring = true; + memset(&req->flags, 0, sizeof(req->flags)); + memset(&req->u, 0, sizeof(req->u)); + req->flags.is_uring = 1; req->ref_cnt++; req->ch = NULL; /* not needed for uring */ + req->interrupted = 0; + list_init_req(req); fuse_session_process_uring_cqe(fuse_ring->se, req, in, &rrh->op_in, ent->op_payload, ent_in_out->payload_sz); @@ -696,8 +700,9 @@ static int fuse_uring_init_queue(struct fuse_ring_queue *queue) req->se = se; pthread_mutex_init(&req->lock, NULL); - req->is_uring = true; - req->ref_cnt = 1; + req->flags.is_uring = 1; + req->ref_cnt = 1; /* extra ref to avoid destruction */ + list_init_req(req); } res = fuse_uring_prepare_fetch_sqes(queue); From 30bf3c86a933887407da901a05d726cebf6c5793 Mon Sep 17 00:00:00 2001 From: Bernd Schubert <bschubert@ddn.com> Date: Sun, 31 Aug 2025 21:56:34 +0200 Subject: [PATCH 195/241] fuse_loop_mt: Don't create new workers before init is received This is to prevent startup races. For example it is hard for threads to know the buffer size the kernel usage. In the past kernel buffer size always was max 1MB, but in the mean time this might be more, depending on system tunings. I thought I had fixed all races, but the report in issue #1296 proves otherwise. Closes: https://github.com/libfuse/libfuse/issues/1296 Signed-off-by: Bernd Schubert <bschubert@ddn.com> --- lib/fuse_loop_mt.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/fuse_loop_mt.c b/lib/fuse_loop_mt.c index 77712b6a2..ec4bb0b2b 100644 --- a/lib/fuse_loop_mt.c +++ b/lib/fuse_loop_mt.c @@ -171,7 +171,8 @@ static void *fuse_do_work(void *data) if (!isforget) mt->numavail--; - if (mt->numavail == 0 && mt->numworker < mt->max_threads) + if (mt->numavail == 0 && mt->numworker < mt->max_threads && + likely(se->got_init)) fuse_loop_start_thread(mt); pthread_mutex_unlock(&se->mt_lock); From 59cf26a2d778b88819db528c67141b788a8b48a1 Mon Sep 17 00:00:00 2001 From: Bernd Schubert <bernd@bsbernd.com> Date: Mon, 22 Sep 2025 10:46:46 +0200 Subject: [PATCH 196/241] FUSE_INIT handler: Set got_init only shortly before send_reply_ok A previous commit added single thread behavior as long as se->got_init is not set, especially as buffer allocation should be done after got_init only. Technically it should not make a difference in current libfuse design, as more worker threads are only allocated after the FUSE_INIT handler is completely done. That might at some point change, though. In general it is cleaner to set se->got_init, when the init handler is done. Done as separate commit, as this introduces a slight risk to break something - backport to older version should be done carefully. Signed-off-by: Bernd Schubert <bernd@bsbernd.com> --- lib/fuse_lowlevel.c | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/lib/fuse_lowlevel.c b/lib/fuse_lowlevel.c index cacab94bc..a1a1427d0 100644 --- a/lib/fuse_lowlevel.c +++ b/lib/fuse_lowlevel.c @@ -2799,7 +2799,6 @@ _do_init(fuse_req_t req, const fuse_ino_t nodeid, const void *op_in, se->conn.time_gran = 1; - se->got_init = 1; if (se->op.init) { // Apply the first 32 bits of capable_ext to capable se->conn.capable = fuse_lower_32_bits(se->conn.capable_ext); @@ -2970,6 +2969,13 @@ _do_init(fuse_req_t req, const fuse_ino_t nodeid, const void *op_in, } } + /* + * Has to be set before replying, as new kernel requests might + * immediately arrive and got_init is used for op-code sanity. + * Especially with external handlers, where we have no control + * over the thread scheduling. + */ + se->got_init = 1; send_reply_ok(req, &outarg, outargsize); if (enable_io_uring) fuse_uring_wake_ring_threads(se); From 4337029adf154d94e6fcb69710e4232cd62a3330 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 29 Sep 2025 22:58:24 +0000 Subject: [PATCH 197/241] build(deps): bump github/codeql-action from 3.30.3 to 3.30.5 Bumps [github/codeql-action](https://github.com/github/codeql-action) from 3.30.3 to 3.30.5. - [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/192325c86100d080feab897ff886c34abd4c83a3...3599b3baa15b485a2e49ef411a7a4bb2452e7f93) --- updated-dependencies: - dependency-name: github/codeql-action dependency-version: 3.30.5 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 19a4569a8..0d3cdd12a 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@192325c86100d080feab897ff886c34abd4c83a3 # v3.29.5 + uses: github/codeql-action/init@3599b3baa15b485a2e49ef411a7a4bb2452e7f93 # v3.29.5 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@192325c86100d080feab897ff886c34abd4c83a3 # v3.29.5 + uses: github/codeql-action/analyze@3599b3baa15b485a2e49ef411a7a4bb2452e7f93 # v3.29.5 with: category: "/language:${{matrix.language}}" From 382b17dd079f82e7f445de9a9993e0bb6cabaa71 Mon Sep 17 00:00:00 2001 From: Bernd Schubert <bernd@bsbernd.com> Date: Tue, 30 Sep 2025 17:41:36 +0200 Subject: [PATCH 198/241] Fix missing \n termination in fusermount_posix_spawn() Also improve the error message a bit. Signed-off-by: Bernd Schubert <bernd@bsbernd.com> --- lib/mount.c | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/lib/mount.c b/lib/mount.c index 549268006..7a856c101 100644 --- a/lib/mount.c +++ b/lib/mount.c @@ -145,9 +145,8 @@ static int fusermount_posix_spawn(posix_spawn_file_actions_t *action, } if (status != 0) { - fuse_log(FUSE_LOG_ERR, - "On calling fusermount posix_spawn failed: %s\n", - strerror(status)); + fuse_log(FUSE_LOG_ERR, "Failed to call '%s': %s\n", + FUSERMOUNT_PROG, strerror(status)); return -status; } From 75e32d9f250e0541551685ff5c412b8daef28226 Mon Sep 17 00:00:00 2001 From: Zeno Sebastian Endemann <zeno.endemann@mailbox.org> Date: Sun, 5 Oct 2025 15:39:03 +0200 Subject: [PATCH 199/241] Fixup passthrough_ll.c documentation As far as I can tell, all important operations are implemented nowadays. Signed-off-by: Zeno Sebastian Endemann <zeno.endemann@mailbox.org> --- example/passthrough_ll.c | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/example/passthrough_ll.c b/example/passthrough_ll.c index dce8355cb..7dfaee370 100644 --- a/example/passthrough_ll.c +++ b/example/passthrough_ll.c @@ -13,11 +13,7 @@ * just "passing through" all requests to the corresponding user-space * libc functions. In contrast to passthrough.c and passthrough_fh.c, * this implementation uses the low-level API. Its performance should - * be the least bad among the three, but many operations are not - * implemented. In particular, it is not possible to remove files (or - * directories) because the code necessary to defer actual removal - * until the file is not opened anymore would make the example much - * more complicated. + * be the least bad among the three. * * When writeback caching is enabled (-o writeback mount option), it * is only possible to write to files for which the mounting user has From e12762b71c1cf37a9da535873a9ae17334610cb6 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 6 Oct 2025 22:16:13 +0000 Subject: [PATCH 200/241] build(deps): bump github/codeql-action from 3.30.5 to 3.30.6 Bumps [github/codeql-action](https://github.com/github/codeql-action) from 3.30.5 to 3.30.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/3599b3baa15b485a2e49ef411a7a4bb2452e7f93...64d10c13136e1c5bce3e5fbde8d4906eeaafc885) --- updated-dependencies: - dependency-name: github/codeql-action dependency-version: 3.30.6 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 0d3cdd12a..e8bbc1e78 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@3599b3baa15b485a2e49ef411a7a4bb2452e7f93 # v3.29.5 + uses: github/codeql-action/init@64d10c13136e1c5bce3e5fbde8d4906eeaafc885 # v3.29.5 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@3599b3baa15b485a2e49ef411a7a4bb2452e7f93 # v3.29.5 + uses: github/codeql-action/analyze@64d10c13136e1c5bce3e5fbde8d4906eeaafc885 # v3.29.5 with: category: "/language:${{matrix.language}}" From 564d79e4d3132924d33f0a0848b9d54c84b42a17 Mon Sep 17 00:00:00 2001 From: Zeno Sebastian Endemann <zeno.endemann@mailbox.org> Date: Sun, 12 Oct 2025 10:51:15 +0200 Subject: [PATCH 201/241] fuse_lowlevel.h: Fix doc typo Signed-off-by: Zeno Sebastian Endemann <zeno.endemann@mailbox.org> --- 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 312b33133..c41ad8f13 100644 --- a/include/fuse_lowlevel.h +++ b/include/fuse_lowlevel.h @@ -453,7 +453,7 @@ struct fuse_lowlevel_ops { * * If this request is answered with an error code of ENOSYS, this is * treated as a permanent failure with error code EINVAL, i.e. all - * future bmap requests will fail with EINVAL without being + * future rename requests will fail with EINVAL without being * send to the filesystem process. * * *flags* may be `RENAME_EXCHANGE` or `RENAME_NOREPLACE`. If From 36a1c49fcb890280f96207e590653a9904fa75c6 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 13 Oct 2025 22:04:56 +0000 Subject: [PATCH 202/241] build(deps): bump github/codeql-action from 3.30.6 to 4.30.8 Bumps [github/codeql-action](https://github.com/github/codeql-action) from 3.30.6 to 4.30.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/64d10c13136e1c5bce3e5fbde8d4906eeaafc885...f443b600d91635bebf5b0d9ebc620189c0d6fba5) --- updated-dependencies: - dependency-name: github/codeql-action dependency-version: 4.30.8 dependency-type: direct:production update-type: version-update:semver-major ... 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 e8bbc1e78..9e987d091 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@64d10c13136e1c5bce3e5fbde8d4906eeaafc885 # v3.29.5 + uses: github/codeql-action/init@f443b600d91635bebf5b0d9ebc620189c0d6fba5 # v3.29.5 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@64d10c13136e1c5bce3e5fbde8d4906eeaafc885 # v3.29.5 + uses: github/codeql-action/analyze@f443b600d91635bebf5b0d9ebc620189c0d6fba5 # v3.29.5 with: category: "/language:${{matrix.language}}" From 2bb125e8c4c34f2ce290d1d7349c344e9fd4376c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 20 Oct 2025 22:05:39 +0000 Subject: [PATCH 203/241] build(deps): bump github/codeql-action from 4.30.8 to 4.30.9 Bumps [github/codeql-action](https://github.com/github/codeql-action) from 4.30.8 to 4.30.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/f443b600d91635bebf5b0d9ebc620189c0d6fba5...16140ae1a102900babc80a33c44059580f687047) --- updated-dependencies: - dependency-name: github/codeql-action dependency-version: 4.30.9 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 9e987d091..f93776fee 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@f443b600d91635bebf5b0d9ebc620189c0d6fba5 # v3.29.5 + uses: github/codeql-action/init@16140ae1a102900babc80a33c44059580f687047 # v3.29.5 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@f443b600d91635bebf5b0d9ebc620189c0d6fba5 # v3.29.5 + uses: github/codeql-action/analyze@16140ae1a102900babc80a33c44059580f687047 # v3.29.5 with: category: "/language:${{matrix.language}}" From f7da4e965bcbae2627db4f40764998a61c5fcfdb Mon Sep 17 00:00:00 2001 From: Joanne Koong <joannelkoong@gmail.com> Date: Thu, 23 Oct 2025 09:55:28 -0700 Subject: [PATCH 204/241] init: fix io-uring mount hang If libfuse cannot set up the server to use io-uring, then it should not send FUSE_OVER_IO_URING in the init reply. outarg.flags needs to be set after FUSE_OVER_IO_URING is unset in the error case where io-uring setup fails, else the mount will hang. Signed-off-by: Joanne Koong <joannelkoong@gmail.com> --- lib/fuse_lowlevel.c | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/lib/fuse_lowlevel.c b/lib/fuse_lowlevel.c index a1a1427d0..d420b257b 100644 --- a/lib/fuse_lowlevel.c +++ b/lib/fuse_lowlevel.c @@ -2910,13 +2910,6 @@ _do_init(fuse_req_t req, const fuse_ino_t nodeid, const void *op_in, outarg.request_timeout = se->conn.request_timeout; } - if (inargflags & FUSE_INIT_EXT) { - outargflags |= FUSE_INIT_EXT; - outarg.flags2 = outargflags >> 32; - } - - outarg.flags = outargflags; - outarg.max_readahead = se->conn.max_readahead; outarg.max_write = se->conn.max_write; if (se->conn.proto_minor >= 13) { @@ -2969,6 +2962,12 @@ _do_init(fuse_req_t req, const fuse_ino_t nodeid, const void *op_in, } } + if (inargflags & FUSE_INIT_EXT) { + outargflags |= FUSE_INIT_EXT; + outarg.flags2 = outargflags >> 32; + } + outarg.flags = outargflags; + /* * Has to be set before replying, as new kernel requests might * immediately arrive and got_init is used for op-code sanity. From bf3dd153fbfcd610d799562490f6555b9d708905 Mon Sep 17 00:00:00 2001 From: Ben Linsay <blinsay@gmail.com> Date: Wed, 22 Oct 2025 12:02:55 -0400 Subject: [PATCH 205/241] fusermount: count mounts with listmount and statmount reading from /proc is not atomic. /etc/mtab specifically is only atomic within a single read syscall, so it's possible to see repeated or stale mounts reading it line by line. this can sometimes cause fusermount to see the same mount hundreds or thousands of times which results in spurious mount failures if fuse_mount_max is not set high enough. listmount and statmount were introduced as apis to work around the limitations of parsing proc. fusermount now tries to use those syscalls when available, and falls back to reading /etc/mtab if that fails. these apis do not have libc wrappers yet. Signed-off-by: Ben Linsay <blinsay@gmail.com> --- meson.build | 19 ++++++++++++ util/fusermount.c | 74 ++++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 92 insertions(+), 1 deletion(-) diff --git a/meson.build b/meson.build index 1dd687c1a..e3c7eeba6 100644 --- a/meson.build +++ b/meson.build @@ -99,6 +99,25 @@ special_funcs = { unsigned int flags = CLOSE_RANGE_UNSHARE; return close_range(3, ~0U, flags); } + ''', + 'listmount': ''' + #include <syscall.h> + #include <linux/mount.h> + #include <unistd.h> + #include <stdint.h> + + int main(int argc, char *argv[]) { + struct mnt_id_req req = { + .size = sizeof(struct mnt_id_req), + .mnt_id = LSMT_ROOT, + }; + uint64_t mnt_ids[1]; + + int n = syscall(SYS_listmount, &req, &mnt_ids, 1, 0); + if (n == -1) { + return -1; + } + } ''' } diff --git a/util/fusermount.c b/util/fusermount.c index 9cc08b4bf..7f19df7a8 100644 --- a/util/fusermount.c +++ b/util/fusermount.c @@ -40,6 +40,12 @@ #include <linux/close_range.h> #endif +#if defined HAVE_LISTMOUNT +#include <linux/mount.h> +#include <syscall.h> +#include <stdint.h> +#endif + #define FUSE_COMMFD_ENV "_FUSE_COMMFD" #define FUSE_KERN_DEVICE_ENV "FUSE_KERN_DEVICE" @@ -566,7 +572,7 @@ static int unmount_fuse(const char *mnt, int quiet, int lazy) return res; } -static int count_fuse_fs(void) +static int count_fuse_fs_mtab(void) { struct mntent *entp; int count = 0; @@ -586,6 +592,72 @@ static int count_fuse_fs(void) return count; } +#ifdef HAVE_LISTMOUNT +static int count_fuse_fs_ls_mnt(void) +{ + #define SMBUF_SIZE 1024 + #define MNT_ID_LEN 128 + + int fuse_count = 0; + int n_mounts = 0; + int ret = 0; + uint64_t mnt_ids[MNT_ID_LEN]; + unsigned char smbuf[SMBUF_SIZE]; + struct mnt_id_req req = { + .size = sizeof(struct mnt_id_req), + }; + struct statmount *sm; + + for (;;) { + req.mnt_id = LSMT_ROOT; + + n_mounts = syscall(SYS_listmount, &req, &mnt_ids, MNT_ID_LEN, 0); + if (n_mounts == -1) { + if (errno != ENOSYS) { + fprintf(stderr, "%s: failed to list mounts: %s\n", progname, + strerror(errno)); + } + return -1; + } + + for (int i = 0; i < n_mounts; i++) { + req.mnt_id = mnt_ids[i]; + req.param = STATMOUNT_FS_TYPE; + ret = syscall(SYS_statmount, &req, &smbuf, SMBUF_SIZE, 0); + if (ret) { + if (errno == ENOENT) + continue; + + fprintf(stderr, "%s: failed to stat mount %lld: %s\n", progname, + req.mnt_id, strerror(errno)); + return -1; + } + + sm = (struct statmount *)smbuf; + if (sm->mask & STATMOUNT_FS_TYPE && + strcmp(&sm->str[sm->fs_type], "fuse") == 0) + fuse_count++; + } + + if (n_mounts < MNT_ID_LEN) + break; + req.param = mnt_ids[MNT_ID_LEN - 1]; + } + return fuse_count; +} + +static int count_fuse_fs(void) +{ + int count = count_fuse_fs_ls_mnt(); + + return count >= 0 ? count : count_fuse_fs_mtab(); +} +#else +static int count_fuse_fs(void) +{ + return count_fuse_fs_mtab(); +} +#endif #else /* IGNORE_MTAB */ static int count_fuse_fs(void) From fd57ba30e35256d1a636bb903aba1d0ad6c01c1b Mon Sep 17 00:00:00 2001 From: Zeno Sebastian Endemann <zeno.endemann@mailbox.org> Date: Wed, 29 Oct 2025 14:55:39 +0100 Subject: [PATCH 206/241] fuse_lowlevel.h: remove incorrect documentation (#1357) It seems fuse_reply_open() cannot actually return -ENOENT under recoverable conditions, so remove the corresponding paragraph claiming otherwise. For a discussion on this see https://github.com/libfuse/libfuse/discussions/1262 Also add an additional note file, which explains fuse_reply errors Signed-off-by: Zeno Endemann <zeno.endemann@mailbox.org> --- doc/README.fuse_reply_errors | 5 +++++ include/fuse_lowlevel.h | 9 ++++----- 2 files changed, 9 insertions(+), 5 deletions(-) create mode 100644 doc/README.fuse_reply_errors diff --git a/doc/README.fuse_reply_errors b/doc/README.fuse_reply_errors new file mode 100644 index 000000000..d60fd7a29 --- /dev/null +++ b/doc/README.fuse_reply_errors @@ -0,0 +1,5 @@ +Under normal operation, a call to any fuse_reply_* function should not result +in an error. + +Should the kernel abort the fuse connection, a fuse_reply_* call can return +-ENOENT. diff --git a/include/fuse_lowlevel.h b/include/fuse_lowlevel.h index c41ad8f13..6be0295b7 100644 --- a/include/fuse_lowlevel.h +++ b/include/fuse_lowlevel.h @@ -197,11 +197,10 @@ enum fuse_notify_entry_flags { * `fuse_session_new()`. In this case, methods will only be called if * the kernel's permission check has succeeded. * - * The filesystem sometimes needs to handle a return value of -ENOENT - * from the reply function, which means, that the request was - * interrupted, and the reply discarded. For example if - * fuse_reply_open() return -ENOENT means, that the release method for - * this file will not be called. + * It is generally not really necessary to check the fuse_reply_* return + * values for errors, as any error in sending a reply indicates an + * unrecoverable problem with the kernel fuse connection, which will also + * terminate the session loop anyway. * * This data structure is ABI sensitive, on adding new functions these need to * be appended at the end of the struct From c2168333c63131b3dcb3fc244fd8f3f6d3fdbde9 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 27 Oct 2025 22:28:05 +0000 Subject: [PATCH 207/241] build(deps): bump github/codeql-action from 4.30.9 to 4.31.0 Bumps [github/codeql-action](https://github.com/github/codeql-action) from 4.30.9 to 4.31.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/16140ae1a102900babc80a33c44059580f687047...4e94bd11f71e507f7f87df81788dff88d1dacbfb) --- updated-dependencies: - dependency-name: github/codeql-action dependency-version: 4.31.0 dependency-type: direct:production update-type: version-update:semver-minor ... 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 f93776fee..be56ede06 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@16140ae1a102900babc80a33c44059580f687047 # v3.29.5 + uses: github/codeql-action/init@4e94bd11f71e507f7f87df81788dff88d1dacbfb # v3.29.5 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@16140ae1a102900babc80a33c44059580f687047 # v3.29.5 + uses: github/codeql-action/analyze@4e94bd11f71e507f7f87df81788dff88d1dacbfb # v3.29.5 with: category: "/language:${{matrix.language}}" From e2886cb91518a885e581c10d80095ed01d44cc6c Mon Sep 17 00:00:00 2001 From: Bernd Schubert <bschubert@ddn.com> Date: Wed, 5 Nov 2025 15:59:30 +0100 Subject: [PATCH 208/241] Add FUSE_COPY_FILE_RANGE_64 to doc/libfuse-operations.txt Just a doc update to add FUSE_COPY_FILE_RANGE_64. Also corrects FUSE_STATFS, which had an incorrects in_args[0], it actually does not have any inargs. Signed-off-by: Bernd Schubert <bschubert@ddn.com> --- doc/libfuse-operations.txt | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/doc/libfuse-operations.txt b/doc/libfuse-operations.txt index a56f89be5..457ff2cdc 100644 --- a/doc/libfuse-operations.txt +++ b/doc/libfuse-operations.txt @@ -1,5 +1,5 @@ List of libfuse operations with their in/out arguments, created with -help of chatgpt. As of kernel 6.9 (protocol 7.40). The list +help of chatgpt. As of kernel 6.18 (protocol 7.45). The list was only partly human verified - use with care. 1. FUSE_LOOKUP (1) @@ -125,7 +125,7 @@ was only partly human verified - use with care. - out_args[2]: Not used 17. FUSE_STATFS (17) - - in_args[0]: Size of fuse_statfs_in (16 bytes) + - in_args[0]: Not used - in_args[1]: Not used - in_args[2]: Not used - out_args[0]: Size of fuse_statfs_out (typically 96 bytes) @@ -368,7 +368,7 @@ was only partly human verified - use with care. - out_args[2]: Not used 47. FUSE_COPY_FILE_RANGE (47) - - in_args[0]: Size of fuse_copy_file_range_in (48 bytes) + - in_args[0]: Size of fuse_copy_file_range_in (56 bytes) - in_args[1]: Not used - in_args[2]: Not used - out_args[0]: Size of fuse_write_out (24 bytes) @@ -416,3 +416,11 @@ was only partly human verified - use with care. - out_args[0]: Size of fuse_statx_out (typically 256 bytes) - out_args[1]: Not used - out_args[2]: Not used + +53. FUSE_COPY_FILE_RANGE_64 (53) + - in_args[0]: Size of fuse_copy_file_range_in (56 bytes) + - in_args[1]: Not used + - in_args[2]: Not used + - out_args[0]: Size of fuse_copy_file_range_out (8 bytes) + - out_args[1]: Not used + - out_args[2]: Not used From 6247606ed8e7fd5dc046061d0e8795fec6d6eb10 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 3 Nov 2025 22:08:18 +0000 Subject: [PATCH 209/241] build(deps): bump github/codeql-action from 4.31.0 to 4.31.2 Bumps [github/codeql-action](https://github.com/github/codeql-action) from 4.31.0 to 4.31.2. - [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/4e94bd11f71e507f7f87df81788dff88d1dacbfb...0499de31b99561a6d14a36a5f662c2a54f91beee) --- updated-dependencies: - dependency-name: github/codeql-action dependency-version: 4.31.2 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 be56ede06..39f6e5ba4 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@4e94bd11f71e507f7f87df81788dff88d1dacbfb # v3.29.5 + uses: github/codeql-action/init@0499de31b99561a6d14a36a5f662c2a54f91beee # v3.29.5 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@4e94bd11f71e507f7f87df81788dff88d1dacbfb # v3.29.5 + uses: github/codeql-action/analyze@0499de31b99561a6d14a36a5f662c2a54f91beee # v3.29.5 with: category: "/language:${{matrix.language}}" From f58d4c5b0d56116d8870753f6b9d1620ee082709 Mon Sep 17 00:00:00 2001 From: Miklos Szeredi <mszeredi@redhat.com> Date: Mon, 10 Nov 2025 14:24:22 +0100 Subject: [PATCH 210/241] tests: fix a race in wait_for_mount() Recheck if target is mounted after the mount process exited, otherwise the test can fail erronously. Signed-off-by: Miklos Szeredi <mszeredi@redhat.com> --- test/util.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/test/util.py b/test/util.py index a421e7213..125fd50fe 100644 --- a/test/util.py +++ b/test/util.py @@ -50,6 +50,8 @@ def wait_for_mount(mount_process, mnt_dir, if test_fn(mnt_dir): return True if mount_process.poll() is not None: + if test_fn(mnt_dir): + return True pytest.fail('file system process terminated prematurely') time.sleep(0.1) elapsed += 0.1 From c71727d992c7817cf7a8da8c5a6e5876babc8314 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 10 Nov 2025 22:08:54 +0000 Subject: [PATCH 211/241] build(deps): bump codespell-project/actions-codespell from 2.1 to 2.2 Bumps [codespell-project/actions-codespell](https://github.com/codespell-project/actions-codespell) from 2.1 to 2.2. - [Release notes](https://github.com/codespell-project/actions-codespell/releases) - [Commits](https://github.com/codespell-project/actions-codespell/compare/406322ec52dd7b488e48c1c4b82e2a8b3a1bf630...8f01853be192eb0f849a5c7d721450e7a467c579) --- updated-dependencies: - dependency-name: codespell-project/actions-codespell dependency-version: '2.2' dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] <support@github.com> --- .github/workflows/codespell.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/codespell.yml b/.github/workflows/codespell.yml index 4c2288b7a..629673606 100644 --- a/.github/workflows/codespell.yml +++ b/.github/workflows/codespell.yml @@ -23,6 +23,6 @@ jobs: - name: Checkout uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 - name: Codespell - uses: codespell-project/actions-codespell@406322ec52dd7b488e48c1c4b82e2a8b3a1bf630 # v2.1 + uses: codespell-project/actions-codespell@8f01853be192eb0f849a5c7d721450e7a467c579 # v2.2 with: skip: checkpatch.pl From 6c9a4d698386df1217579615918535afa40380eb Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 17 Nov 2025 22:26:33 +0000 Subject: [PATCH 212/241] build(deps): bump github/codeql-action from 4.31.2 to 4.31.3 Bumps [github/codeql-action](https://github.com/github/codeql-action) from 4.31.2 to 4.31.3. - [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/0499de31b99561a6d14a36a5f662c2a54f91beee...014f16e7ab1402f30e7c3329d33797e7948572db) --- updated-dependencies: - dependency-name: github/codeql-action dependency-version: 4.31.3 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 39f6e5ba4..d6885c8da 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@0499de31b99561a6d14a36a5f662c2a54f91beee # v3.29.5 + uses: github/codeql-action/init@014f16e7ab1402f30e7c3329d33797e7948572db # v3.29.5 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@0499de31b99561a6d14a36a5f662c2a54f91beee # v3.29.5 + uses: github/codeql-action/analyze@014f16e7ab1402f30e7c3329d33797e7948572db # v3.29.5 with: category: "/language:${{matrix.language}}" From 0fcaec3276d4e380d4084fb22854bf02ae31712e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 17 Nov 2025 22:28:12 +0000 Subject: [PATCH 213/241] build(deps): bump actions/checkout from 5.0.0 to 5.0.1 Bumps [actions/checkout](https://github.com/actions/checkout) from 5.0.0 to 5.0.1. - [Release notes](https://github.com/actions/checkout/releases) - [Commits](https://github.com/actions/checkout/compare/v5...v5.0.1) --- updated-dependencies: - dependency-name: actions/checkout dependency-version: 5.0.1 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] <support@github.com> --- .github/workflows/abicheck.yml | 4 ++-- .github/workflows/bsd.yaml | 2 +- .github/workflows/checkpatch.yml | 2 +- .github/workflows/codeql.yml | 2 +- .github/workflows/codespell.yml | 2 +- .github/workflows/iwyi-check.yml | 2 +- .github/workflows/pr-ci.yml | 2 +- 7 files changed, 8 insertions(+), 8 deletions(-) diff --git a/.github/workflows/abicheck.yml b/.github/workflows/abicheck.yml index a00449c3a..0dc38a833 100644 --- a/.github/workflows/abicheck.yml +++ b/.github/workflows/abicheck.yml @@ -28,11 +28,11 @@ jobs: sudo apt-get update sudo apt-get -y install abigail-tools clang gcc liburing-dev libnuma-dev - - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 + - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 with: path: current - - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 + - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 with: path: previous ref: ${{ github.event.pull_request.base.ref }} diff --git a/.github/workflows/bsd.yaml b/.github/workflows/bsd.yaml index 13b30299b..02c1c1c01 100644 --- a/.github/workflows/bsd.yaml +++ b/.github/workflows/bsd.yaml @@ -18,7 +18,7 @@ jobs: name: Build under FreeBSD steps: - name: Checkout - uses: actions/checkout@v5 + uses: actions/checkout@v5.0.1 - name: Build uses: vmactions/freebsd-vm@v1 with: diff --git a/.github/workflows/checkpatch.yml b/.github/workflows/checkpatch.yml index 86b16aae1..5d71d4bcc 100644 --- a/.github/workflows/checkpatch.yml +++ b/.github/workflows/checkpatch.yml @@ -10,7 +10,7 @@ jobs: checkpatch: runs-on: ubuntu-latest steps: - - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 + - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 with: fetch-depth: 0 - name: Install dependencies diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index d6885c8da..3e8b265d7 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -49,7 +49,7 @@ jobs: build-mode: manual steps: - name: Checkout repository - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 + uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL diff --git a/.github/workflows/codespell.yml b/.github/workflows/codespell.yml index 629673606..fd73ec5c5 100644 --- a/.github/workflows/codespell.yml +++ b/.github/workflows/codespell.yml @@ -21,7 +21,7 @@ jobs: steps: - name: Checkout - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 + uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 - name: Codespell uses: codespell-project/actions-codespell@8f01853be192eb0f849a5c7d721450e7a467c579 # v2.2 with: diff --git a/.github/workflows/iwyi-check.yml b/.github/workflows/iwyi-check.yml index 0718c3155..a2ff1c29b 100644 --- a/.github/workflows/iwyi-check.yml +++ b/.github/workflows/iwyi-check.yml @@ -18,7 +18,7 @@ jobs: name: Include What You Use Check runs-on: ubuntu-latest steps: - - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 + - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 with: fetch-depth: 0 diff --git a/.github/workflows/pr-ci.yml b/.github/workflows/pr-ci.yml index ebd79c427..ea7ebd8e8 100644 --- a/.github/workflows/pr-ci.yml +++ b/.github/workflows/pr-ci.yml @@ -33,7 +33,7 @@ jobs: gcc-multilib g++-multilib libc6-dev-i386 \ libpcap0.8-dev:i386 libudev-dev:i386 pkg-config:i386 \ liburing-dev libnuma-dev - - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 + - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 - uses: actions/setup-python@v6 with: python-version: '3.12' From 8ad076e45834f94857b88a0b70f577273d986b77 Mon Sep 17 00:00:00 2001 From: Bernd Schubert <bernd@bsbernd.com> Date: Mon, 17 Nov 2025 23:23:10 +0100 Subject: [PATCH 214/241] Initialize fuse_entry_param in fuse_lib_lookup() "struct fuse_entry_param e" was not initialized when get_path_name() returned an error, which made recent clang versions to complain. However, the compiler missed that reply_entry() checks for an error code and ignores fuse_entry_param then. Closes: https://github.com/libfuse/libfuse/issues/1360 Signed-off-by: Bernd Schubert <bernd@bsbernd.com> --- lib/fuse.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/fuse.c b/lib/fuse.c index 4cc6f3b1c..c09564b28 100644 --- a/lib/fuse.c +++ b/lib/fuse.c @@ -2659,7 +2659,7 @@ static void fuse_lib_lookup(fuse_req_t req, fuse_ino_t parent, const char *name) { struct fuse *f = req_fuse_prepare(req); - struct fuse_entry_param e; + struct fuse_entry_param e = { .ino = 0 }; /* invalid ino */ char *path; int err; struct node *dot = NULL; From 1e19235c546ae3ea002b3b1f5b7e877a5687803e Mon Sep 17 00:00:00 2001 From: Long Li <leo.lilong@huawei.com> Date: Sat, 22 Nov 2025 22:57:23 +0800 Subject: [PATCH 215/241] example/passthrough_hp: fix race between forget_one and do_lookup During fsstress stress testing using passthrough_hp as the backend, the backend process crashes. The root cause is that when forget_one() and do_lookup() concurrently process the same inode, do_lookup may return either an invalid inode or a different inode reusing the same memory address. CPU0 CPU1 ----------------------- -------------------- forget_one do_lookup lock fs inode = fs.inodes[id] //inode.fd > 0 unlock fs lock inode inode.nlookup -= n <inode.nlookup equal to 0> lock fs unlock inode fs.inodes.erase unlock fs lock inode inode.nlookup++ unlock inode <lookup a invalid inode> This can lead to abnormalities in the inode nlookup count. Since the value of inode.nlookup determines the inode's lifecycle, and considering the locking order requirements between the inode lock and fs lock, using the inode lock alone does not resolve the issue effectively. The fix is to convert inode.nlookup to an atomic type, which removes the need for write protection via inode lock, while using fs lock to guard the inode's lifetime. Signed-off-by: Long Li <leo.lilong@huawei.com> --- example/passthrough_hp.cc | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/example/passthrough_hp.cc b/example/passthrough_hp.cc index 1a67704dc..1fa49e739 100644 --- a/example/passthrough_hp.cc +++ b/example/passthrough_hp.cc @@ -78,6 +78,7 @@ #include "cxxopts.hpp" #include <mutex> #include <syslog.h> +#include <atomic> #include "passthrough_helpers.h" @@ -130,7 +131,7 @@ struct Inode { int generation{ 0 }; int backing_id{ 0 }; uint64_t nopen{ 0 }; - uint64_t nlookup{ 0 }; + std::atomic<uint64_t> nlookup{ 0 }; std::mutex m; // Delete copy constructor and assignments. We could implement @@ -411,12 +412,10 @@ static int do_lookup(fuse_ino_t parent, const char *name, fuse_entry_param *e) } if (inode.fd > 0) { // found existing inode - fs_lock.unlock(); if (fs.debug) cerr << "DEBUG: lookup(): inode " << e->attr.st_ino << " (userspace) already known; fd = " << inode.fd << endl; - lock_guard<mutex> g{ inode.m }; inode.nlookup++; if (fs.debug) @@ -424,6 +423,7 @@ static int do_lookup(fuse_ino_t parent, const char *name, fuse_entry_param *e) << "inode " << inode.src_ino << " count " << inode.nlookup << endl; + fs_lock.unlock(); close(newfd); } else { // no existing inode /* This is just here to make Helgrind happy. It violates the @@ -548,7 +548,6 @@ static void sfs_link(fuse_req_t req, fuse_ino_t ino, fuse_ino_t parent, } e.ino = reinterpret_cast<fuse_ino_t>(&inode); { - lock_guard<mutex> g{ inode.m }; inode.nlookup++; if (fs.debug) cerr << "DEBUG:" << __func__ << ":" << __LINE__ << " " @@ -621,7 +620,6 @@ static void sfs_unlink(fuse_req_t req, fuse_ino_t parent, const char *name) static void forget_one(fuse_ino_t ino, uint64_t n) { Inode &inode = get_inode(ino); - unique_lock<mutex> l{ inode.m }; if (n > inode.nlookup) { cerr << "INTERNAL ERROR: Negative lookup count for inode " @@ -636,12 +634,10 @@ static void forget_one(fuse_ino_t ino, uint64_t n) << endl; if (!inode.nlookup) { - if (fs.debug) + lock_guard<mutex> g_fs{ fs.mutex }; + if (!inode.nlookup) { cerr << "DEBUG: forget: cleaning up inode " - << inode.src_ino << endl; - { - lock_guard<mutex> g_fs{ fs.mutex }; - l.unlock(); + << inode.src_ino << endl; fs.inodes.erase({ inode.src_ino, inode.src_dev }); } } else if (fs.debug) From 02c0fc628006b0dcd18cf03f70021619ed817f08 Mon Sep 17 00:00:00 2001 From: Long Li <leo.lilong@huawei.com> Date: Tue, 25 Nov 2025 11:03:59 +0800 Subject: [PATCH 216/241] example/passthrough_hp: fix race in forget_one() When multiple threads concurrently call forget_one() on the same inode, a use-after-free memory issue can occur. forget_one() forget_one() ---------------- --------------- <inode.nlookup == 2> inode.nlookup -= 1 inode.nlookup -= 1 <inode.nlookup == 0> if (!inode.nlookup) fs.inodes.erase() if (!inode.nlookup) {} //UAF Fix it by restoring the inode lock protection in forget_one(). Signed-off-by: Long Li <leo.lilong@huawei.com> --- example/passthrough_hp.cc | 2 ++ 1 file changed, 2 insertions(+) diff --git a/example/passthrough_hp.cc b/example/passthrough_hp.cc index 1fa49e739..fcdac4bf2 100644 --- a/example/passthrough_hp.cc +++ b/example/passthrough_hp.cc @@ -620,6 +620,7 @@ static void sfs_unlink(fuse_req_t req, fuse_ino_t parent, const char *name) static void forget_one(fuse_ino_t ino, uint64_t n) { Inode &inode = get_inode(ino); + unique_lock<mutex> l{ inode.m }; if (n > inode.nlookup) { cerr << "INTERNAL ERROR: Negative lookup count for inode " @@ -635,6 +636,7 @@ static void forget_one(fuse_ino_t ino, uint64_t n) if (!inode.nlookup) { lock_guard<mutex> g_fs{ fs.mutex }; + l.unlock(); if (!inode.nlookup) { cerr << "DEBUG: forget: cleaning up inode " << inode.src_ino << endl; From ab5033f5d1bd1630870dc725eb3eae6ef3ef4859 Mon Sep 17 00:00:00 2001 From: Long Li <leo.lilong@huawei.com> Date: Tue, 25 Nov 2025 22:01:48 +0800 Subject: [PATCH 217/241] example/passthrough_hp: fix debug output in forget_one The incorrect removeal of the fs.debug check caused the message "DEBUG: forget: cleaning up inode" to be printed even when debug was not enabled. Signed-off-by: Long Li <leo.lilong@huawei.com> --- example/passthrough_hp.cc | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/example/passthrough_hp.cc b/example/passthrough_hp.cc index fcdac4bf2..2dc0f9503 100644 --- a/example/passthrough_hp.cc +++ b/example/passthrough_hp.cc @@ -638,8 +638,9 @@ static void forget_one(fuse_ino_t ino, uint64_t n) lock_guard<mutex> g_fs{ fs.mutex }; l.unlock(); if (!inode.nlookup) { - cerr << "DEBUG: forget: cleaning up inode " - << inode.src_ino << endl; + if (fs.debug) + cerr << "DEBUG: forget: cleaning up inode " + << inode.src_ino << endl; fs.inodes.erase({ inode.src_ino, inode.src_dev }); } } else if (fs.debug) From 6268c6690141d093a378e55061212c6739d709af Mon Sep 17 00:00:00 2001 From: Alexander Monakov <amonakov@ispras.ru> Date: Thu, 27 Nov 2025 00:29:48 +0300 Subject: [PATCH 218/241] lib/fuse.c: fix deallocation in fuse_session_loop_remember Starting from commit 752b59ac0876, the buffer must be freed with fuse_buf_free, not plain free. Signed-off-by: Alexander Monakov <amonakov@ispras.ru> --- lib/fuse.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/fuse.c b/lib/fuse.c index c09564b28..c7838e2a1 100644 --- a/lib/fuse.c +++ b/lib/fuse.c @@ -4626,7 +4626,7 @@ static int fuse_session_loop_remember(struct fuse *f) } } - free(fbuf.mem); + fuse_buf_free(&fbuf); return res < 0 ? -1 : 0; } From e9a81fda21fca6af8abcd786792d12937f2ac235 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 24 Nov 2025 22:13:49 +0000 Subject: [PATCH 219/241] build(deps): bump actions/checkout from 5.0.1 to 6.0.0 Bumps [actions/checkout](https://github.com/actions/checkout) from 5.0.1 to 6.0.0. - [Release notes](https://github.com/actions/checkout/releases) - [Commits](https://github.com/actions/checkout/compare/v5.0.1...v6) --- updated-dependencies: - dependency-name: actions/checkout dependency-version: 6.0.0 dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] <support@github.com> --- .github/workflows/abicheck.yml | 4 ++-- .github/workflows/bsd.yaml | 2 +- .github/workflows/checkpatch.yml | 2 +- .github/workflows/codeql.yml | 2 +- .github/workflows/codespell.yml | 2 +- .github/workflows/iwyi-check.yml | 2 +- .github/workflows/pr-ci.yml | 2 +- 7 files changed, 8 insertions(+), 8 deletions(-) diff --git a/.github/workflows/abicheck.yml b/.github/workflows/abicheck.yml index 0dc38a833..08ad24ddb 100644 --- a/.github/workflows/abicheck.yml +++ b/.github/workflows/abicheck.yml @@ -28,11 +28,11 @@ jobs: sudo apt-get update sudo apt-get -y install abigail-tools clang gcc liburing-dev libnuma-dev - - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 + - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0 with: path: current - - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 + - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0 with: path: previous ref: ${{ github.event.pull_request.base.ref }} diff --git a/.github/workflows/bsd.yaml b/.github/workflows/bsd.yaml index 02c1c1c01..0e50b4b25 100644 --- a/.github/workflows/bsd.yaml +++ b/.github/workflows/bsd.yaml @@ -18,7 +18,7 @@ jobs: name: Build under FreeBSD steps: - name: Checkout - uses: actions/checkout@v5.0.1 + uses: actions/checkout@v6.0.0 - name: Build uses: vmactions/freebsd-vm@v1 with: diff --git a/.github/workflows/checkpatch.yml b/.github/workflows/checkpatch.yml index 5d71d4bcc..947a457e8 100644 --- a/.github/workflows/checkpatch.yml +++ b/.github/workflows/checkpatch.yml @@ -10,7 +10,7 @@ jobs: checkpatch: runs-on: ubuntu-latest steps: - - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 + - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0 with: fetch-depth: 0 - name: Install dependencies diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 3e8b265d7..3916b5abe 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -49,7 +49,7 @@ jobs: build-mode: manual steps: - name: Checkout repository - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 + uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0 # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL diff --git a/.github/workflows/codespell.yml b/.github/workflows/codespell.yml index fd73ec5c5..2dd8a4e78 100644 --- a/.github/workflows/codespell.yml +++ b/.github/workflows/codespell.yml @@ -21,7 +21,7 @@ jobs: steps: - name: Checkout - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 + uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0 - name: Codespell uses: codespell-project/actions-codespell@8f01853be192eb0f849a5c7d721450e7a467c579 # v2.2 with: diff --git a/.github/workflows/iwyi-check.yml b/.github/workflows/iwyi-check.yml index a2ff1c29b..a5a0f44b2 100644 --- a/.github/workflows/iwyi-check.yml +++ b/.github/workflows/iwyi-check.yml @@ -18,7 +18,7 @@ jobs: name: Include What You Use Check runs-on: ubuntu-latest steps: - - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 + - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0 with: fetch-depth: 0 diff --git a/.github/workflows/pr-ci.yml b/.github/workflows/pr-ci.yml index ea7ebd8e8..0f65e275e 100644 --- a/.github/workflows/pr-ci.yml +++ b/.github/workflows/pr-ci.yml @@ -33,7 +33,7 @@ jobs: gcc-multilib g++-multilib libc6-dev-i386 \ libpcap0.8-dev:i386 libudev-dev:i386 pkg-config:i386 \ liburing-dev libnuma-dev - - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 + - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0 - uses: actions/setup-python@v6 with: python-version: '3.12' From f1a375e1f789ae6637d18c4cb699cc2b609ddd17 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 24 Nov 2025 22:13:40 +0000 Subject: [PATCH 220/241] build(deps): bump github/codeql-action from 4.31.3 to 4.31.5 Bumps [github/codeql-action](https://github.com/github/codeql-action) from 4.31.3 to 4.31.5. - [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/014f16e7ab1402f30e7c3329d33797e7948572db...fdbfb4d2750291e159f0156def62b853c2798ca2) --- updated-dependencies: - dependency-name: github/codeql-action dependency-version: 4.31.5 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 3916b5abe..3978b7705 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@014f16e7ab1402f30e7c3329d33797e7948572db # v3.29.5 + uses: github/codeql-action/init@fdbfb4d2750291e159f0156def62b853c2798ca2 # v3.29.5 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@014f16e7ab1402f30e7c3329d33797e7948572db # v3.29.5 + uses: github/codeql-action/analyze@fdbfb4d2750291e159f0156def62b853c2798ca2 # v3.29.5 with: category: "/language:${{matrix.language}}" From 4ed0ed7adcf74bc266de79869c5952cc8a1af287 Mon Sep 17 00:00:00 2001 From: Jingbo Xu <jefflexu@linux.alibaba.com> Date: Mon, 11 Aug 2025 19:55:29 +0800 Subject: [PATCH 221/241] fuse-io-uring: fix narrowing integer conversion of commit_id commid_id is 64 bits. fuse_uring_sqe_set_req_data() accepts commid_id as 'unsigned int' type, which is only guaranteed to be no less than 32 bits. Thus the high 32 bits are dropped, and the replied commit_id is truncated to the lower 32 bits as well in the following replied fuse_uring_cmd_req when issuing FUSE_IO_URING_CMD_COMMIT_AND_FETCH subcmd. This can lead to "fuse: qid=XX commit_id YY not found" error, where YY is the low 32 bits of the actual commid_id. Signed-off-by: Jingbo Xu <jefflexu@linux.alibaba.com> --- lib/fuse_uring.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/fuse_uring.c b/lib/fuse_uring.c index 2fea05f97..1311f5a58 100644 --- a/lib/fuse_uring.c +++ b/lib/fuse_uring.c @@ -119,7 +119,7 @@ static void *fuse_uring_get_sqe_cmd(struct io_uring_sqe *sqe) static void fuse_uring_sqe_set_req_data(struct fuse_uring_cmd_req *req, const unsigned int qid, - const unsigned int commit_id) + const uint64_t commit_id) { req->qid = qid; req->commit_id = commit_id; From f96ec67050a6530b71a624140c36676495521f84 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 2 Dec 2025 02:04:50 +0000 Subject: [PATCH 222/241] build(deps): bump github/codeql-action from 4.31.5 to 4.31.6 Bumps [github/codeql-action](https://github.com/github/codeql-action) from 4.31.5 to 4.31.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/fdbfb4d2750291e159f0156def62b853c2798ca2...fe4161a26a8629af62121b670040955b330f9af2) --- updated-dependencies: - dependency-name: github/codeql-action dependency-version: 4.31.6 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 3978b7705..d84baa685 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@fdbfb4d2750291e159f0156def62b853c2798ca2 # v3.29.5 + uses: github/codeql-action/init@fe4161a26a8629af62121b670040955b330f9af2 # v3.29.5 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@fdbfb4d2750291e159f0156def62b853c2798ca2 # v3.29.5 + uses: github/codeql-action/analyze@fe4161a26a8629af62121b670040955b330f9af2 # v3.29.5 with: category: "/language:${{matrix.language}}" From 7bf09951de7afc5f72e30ebc7eaec32720bdaa2c Mon Sep 17 00:00:00 2001 From: Dave Vasilevsky <dave@vasilevsky.ca> Date: Mon, 24 Nov 2025 03:41:14 -0500 Subject: [PATCH 223/241] Don't yield zero d_ino with adaptive readdirplus Under certain conditions, libfuse was yielding a zero d_ino from high-level filesystems. This caused a number of bugs as other software mis-handled these entries. To fix, ensure that direntries stored in a fuse_dh always have either FUSE_UNKNOWN_INO or an intentionally-set st_ino. This bug was triggered if all the following conditions were met: * High-level FS is readdirplus-capable, and does not set use_ino or readdir_ino. * FS does not use offsets in readdir. * FS passes to the dir filler the FUSE_FILL_DIR_PLUS flag, and a non-NULL struct stat with st_ino == 0. * A directory is large enough to need multiple readdir calls. * Adaptive readdirplus causes a readdirplus to be followed by a regular readdir. When this occurred, the fuse_dh was filled with entries with st_ino == 0. On the initial readdirplus we were calling do_lookup() to convert these to synthetic inode numbers, but on the subsequent regular readdirs we were returning the zero inode numbers verbatim. Historically, d_ino == 0 indicated that a direntry should be skipped. Several tools have treated it this way, including Glibc before 2022 (or 2024 for readdir64_r), and current versions of Go. This has caused a number of bugs: * https://github.com/libfuse/libfuse/issues/1338 * https://github.com/golang/go/issues/76428 * https://github.com/restic/restic/pull/5607 * https://gitlab.gnome.org/World/deja-dup/-/issues/623 When libfuse receives st_ino == 0 in readdir, we should therefore treat it as the FS having no opinion about the inode number. We should only truly trust that it wants a zero inode if use_ino or readdir_ino is true. In addition to the fix, this commit adds a mode to passthrough to return st_ino == 0 from readdir, and uses that to test libfuse's behavior in test_examples.py. Signed-off-by: Dave Vasilevsky <dave@vasilevsky.ca> --- example/passthrough.c | 8 +++++- lib/fuse.c | 22 ++++++++-------- test/test_examples.py | 60 +++++++++++++++++++++++++++++++------------ 3 files changed, 62 insertions(+), 28 deletions(-) diff --git a/example/passthrough.c b/example/passthrough.c index 1f09c2dc0..81a265aa4 100644 --- a/example/passthrough.c +++ b/example/passthrough.c @@ -48,12 +48,13 @@ #include "passthrough_helpers.h" static int fill_dir_plus = 0; +static int readdir_zero_ino; static void *xmp_init(struct fuse_conn_info *conn, struct fuse_config *cfg) { (void) conn; - cfg->use_ino = 1; + cfg->use_ino = !readdir_zero_ino; /* parallel_direct_writes feature depends on direct_io features. To make parallel_direct_writes valid, need either set cfg->direct_io @@ -140,6 +141,8 @@ static int xmp_readdir(const char *path, void *buf, fuse_fill_dir_t filler, st.st_ino = de->d_ino; st.st_mode = de->d_type << 12; } + if (readdir_zero_ino) + st.st_ino = 0; if (filler(buf, de->d_name, &st, 0, fill_dir_plus)) break; } @@ -590,6 +593,9 @@ int main(int argc, char *argv[]) for (i=0, new_argc=0; (i<argc) && (new_argc<MAX_ARGS); i++) { if (!strcmp(argv[i], "--plus")) { fill_dir_plus = FUSE_FILL_DIR_PLUS; + } else if (!strcmp(argv[i], "--readdir-zero-inodes")) { + // Return zero inodes from readdir + readdir_zero_ino = 1; } else { new_argv[new_argc++] = argv[i]; } diff --git a/lib/fuse.c b/lib/fuse.c index c7838e2a1..9607bb0d2 100644 --- a/lib/fuse.c +++ b/lib/fuse.c @@ -3568,17 +3568,17 @@ static int fill_dir_plus(void *dh_, const char *name, const struct stat *statp, if (statp && (flags & FUSE_FILL_DIR_PLUS)) { e.attr = *statp; - } else { - e.attr.st_ino = FUSE_UNKNOWN_INO; - if (statp) { - e.attr.st_mode = statp->st_mode; - if (f->conf.use_ino) - e.attr.st_ino = statp->st_ino; - } - if (!f->conf.use_ino && f->conf.readdir_ino) { - e.attr.st_ino = (ino_t) - lookup_nodeid(f, dh->nodeid, name); - } + } + + e.attr.st_ino = FUSE_UNKNOWN_INO; + if (statp) { + e.attr.st_mode = statp->st_mode; + if (f->conf.use_ino) + e.attr.st_ino = statp->st_ino; + } + if (!f->conf.use_ino && f->conf.readdir_ino) { + e.attr.st_ino = (ino_t) + lookup_nodeid(f, dh->nodeid, name); } if (off) { diff --git a/test/test_examples.py b/test/test_examples.py index 2658081fc..05efa0993 100755 --- a/test/test_examples.py +++ b/test/test_examples.py @@ -27,6 +27,11 @@ fuse_proto, fuse_caps, powerset, parse_kernel_version) from os.path import join as pjoin import logging +from enum import Enum + +class InodeCheck(Enum): + EXACT = 1 + NONZERO = 2 pytestmark = fuse_test_marker() @@ -138,7 +143,7 @@ def test_hello(tmpdir, name, options, cmdline_builder, output_checker): @pytest.mark.parametrize("writeback", (False, True)) @pytest.mark.parametrize("name", ('passthrough', 'passthrough_plus', - 'passthrough_fh', 'passthrough_ll')) + 'passthrough_fh', 'passthrough_ll', 'passthrough_zero_ino')) @pytest.mark.parametrize("debug", (False, True)) def test_passthrough(short_tmpdir, name, debug, output_checker, writeback): # Avoid false positives from libfuse debug messages @@ -153,10 +158,16 @@ def test_passthrough(short_tmpdir, name, debug, output_checker, writeback): mnt_dir = str(short_tmpdir.mkdir('mnt')) src_dir = str(short_tmpdir.mkdir('src')) + inode_check = InodeCheck.EXACT if name == 'passthrough_plus': cmdline = base_cmdline + \ [ pjoin(basename, 'example', 'passthrough'), '--plus', '-f', mnt_dir ] + elif name == 'passthrough_zero_ino': + cmdline = base_cmdline + \ + [ pjoin(basename, 'example', 'passthrough'), + '--plus', '--readdir-zero-inodes', '-f', mnt_dir ] + inode_check = InodeCheck.NONZERO elif name == 'passthrough_ll': cmdline = base_cmdline + \ [ pjoin(basename, 'example', name), @@ -189,12 +200,12 @@ def test_passthrough(short_tmpdir, name, debug, output_checker, writeback): work_dir = mnt_dir + src_dir tst_statvfs(work_dir) - tst_readdir(src_dir, work_dir) - tst_readdir_big(src_dir, work_dir) + tst_readdir(src_dir, work_dir, inode_check) + tst_readdir_big(src_dir, work_dir, inode_check) tst_open_read(src_dir, work_dir) tst_open_write(src_dir, work_dir) tst_create(work_dir) - tst_passthrough(src_dir, work_dir) + tst_passthrough(src_dir, work_dir, inode_check) tst_append(src_dir, work_dir) tst_seek(src_dir, work_dir) tst_mkdir(work_dir) @@ -207,7 +218,8 @@ def test_passthrough(short_tmpdir, name, debug, output_checker, writeback): # Underlying fs may not have full nanosecond resolution tst_utimens(work_dir, ns_tol=1000) - tst_link(work_dir) + if inode_check == InodeCheck.EXACT: + tst_link(work_dir) tst_truncate_path(work_dir) tst_truncate_fd(work_dir) tst_open_unlink(work_dir) @@ -757,7 +769,17 @@ def tst_link(mnt_dir): os.unlink(name1) -def tst_readdir(src_dir, mnt_dir): +def tst_inodes_nonzero(lines): + inode_nums = [int(line.split()[0]) for line in lines] + assert all(i != 0 for i in inode_nums), inode_nums + +def tst_inode(inode_check, actual, expected): + if inode_check == InodeCheck.EXACT: + assert expected == actual + elif inode_check == InodeCheck.NONZERO: + assert actual != 0 + +def tst_readdir(src_dir, mnt_dir, inode_check=InodeCheck.EXACT): newdir = name_generator() src_newdir = pjoin(src_dir, newdir) @@ -778,16 +800,18 @@ def tst_readdir(src_dir, mnt_dir): assert listdir_is == listdir_should inodes_is = readdir_inode(mnt_newdir) - inodes_should = readdir_inode(src_newdir) - assert inodes_is == inodes_should + if inode_check == InodeCheck.EXACT: + inodes_should = readdir_inode(src_newdir) + assert inodes_is == inodes_should + elif inode_check == InodeCheck.NONZERO: + tst_inodes_nonzero(inodes_is) os.unlink(file_) os.unlink(subfile) os.rmdir(subdir) os.rmdir(src_newdir) -def tst_readdir_big(src_dir, mnt_dir): - +def tst_readdir_big(src_dir, mnt_dir, inode_check=InodeCheck.EXACT): # Add enough entries so that readdir needs to be called # multiple times. fnames = [] @@ -803,13 +827,17 @@ def tst_readdir_big(src_dir, mnt_dir): assert listdir_is == listdir_should inodes_is = readdir_inode(mnt_dir) - inodes_should = readdir_inode(src_dir) - assert inodes_is == inodes_should + if inode_check == InodeCheck.EXACT: + inodes_should = readdir_inode(src_dir) + assert inodes_is == inodes_should + elif inode_check == InodeCheck.NONZERO: + tst_inodes_nonzero(inodes_is) for fname in fnames: + # A comment just to get a diff stat_src = os.stat(pjoin(src_dir, fname)) stat_mnt = os.stat(pjoin(mnt_dir, fname)) - assert stat_src.st_ino == stat_mnt.st_ino + tst_inode(inode_check, stat_mnt.st_ino, stat_src.st_ino) assert stat_src.st_mtime == stat_mnt.st_mtime assert stat_src.st_ctime == stat_mnt.st_ctime assert stat_src.st_size == stat_mnt.st_size @@ -885,7 +913,7 @@ def tst_utimens(mnt_dir, ns_tol=0): assert abs(fstat.st_atime_ns - atime_ns) <= ns_tol assert abs(fstat.st_mtime_ns - mtime_ns) <= ns_tol -def tst_passthrough(src_dir, mnt_dir): +def tst_passthrough(src_dir, mnt_dir, inode_check=InodeCheck.EXACT): name = name_generator() src_name = pjoin(src_dir, name) mnt_name = pjoin(mnt_dir, name) @@ -918,7 +946,7 @@ def tst_passthrough(src_dir, mnt_dir): src_stat = os.stat(src_name) mnt_stat = os.stat(mnt_name) assert src_stat.st_mode == mnt_stat.st_mode - assert src_stat.st_ino == mnt_stat.st_ino + tst_inode(inode_check, mnt_stat.st_ino, src_stat.st_ino) assert src_stat.st_size == mnt_stat.st_size assert src_stat.st_mtime == mnt_stat.st_mtime @@ -937,7 +965,7 @@ def tst_passthrough(src_dir, mnt_dir): src_stat = os.stat(src_name) mnt_stat = os.stat(mnt_name) assert src_stat.st_mode == mnt_stat.st_mode - assert src_stat.st_ino == mnt_stat.st_ino + tst_inode(inode_check, mnt_stat.st_ino, src_stat.st_ino) assert src_stat.st_size == mnt_stat.st_size assert abs(src_stat.st_mtime - mnt_stat.st_mtime) < 0.01 From e1525c4361f38278ad1b08595d7f9db37923b5ea Mon Sep 17 00:00:00 2001 From: Dave Vasilevsky <dave@vasilevsky.ca> Date: Mon, 1 Dec 2025 22:48:39 -0800 Subject: [PATCH 224/241] Clarify that use_ino takes priority over FUSE_FILL_DIR_PLUS The docs previously said that FUSE_FILL_DIR_PLUS makes "all file attributes" valid, but also that use_ino controls whether the st_ino field is honored. These docs conflict! Since it doesn't make sense for getattr() and readdir() to have different behavior for st_ino, it's best to decide that use_ino will always take priority. This should not break any filesystems, as use of non-zero st_ino with use_ino false already had weird and broken behavior before this PR. Signed-off-by: Dave Vasilevsky <dave@vasilevsky.ca> --- include/fuse.h | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/include/fuse.h b/include/fuse.h index 209102651..32d921b38 100644 --- a/include/fuse.h +++ b/include/fuse.h @@ -60,13 +60,16 @@ enum fuse_readdir_flags { */ enum fuse_fill_dir_flags { /** - * "Plus" mode: all file attributes are valid + * "Plus" mode: file attributes are valid * * The attributes are used by the kernel to prefill the inode cache * during a readdir. * * It is okay to set FUSE_FILL_DIR_PLUS if FUSE_READDIR_PLUS is not set * and vice versa. + * + * This does not make libfuse honor the 'st_ino' field. That is + * controlled by the 'use_ino' option instead. */ FUSE_FILL_DIR_DEFAULTS = 0, FUSE_FILL_DIR_PLUS = (1 << 1) From c2fb67471e23ac70ed1cf56904199380ac7574a9 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 8 Dec 2025 22:05:17 +0000 Subject: [PATCH 225/241] build(deps): bump actions/checkout from 6.0.0 to 6.0.1 Bumps [actions/checkout](https://github.com/actions/checkout) from 6.0.0 to 6.0.1. - [Release notes](https://github.com/actions/checkout/releases) - [Commits](https://github.com/actions/checkout/compare/v6...v6.0.1) --- updated-dependencies: - dependency-name: actions/checkout dependency-version: 6.0.1 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] <support@github.com> --- .github/workflows/abicheck.yml | 4 ++-- .github/workflows/bsd.yaml | 2 +- .github/workflows/checkpatch.yml | 2 +- .github/workflows/codeql.yml | 2 +- .github/workflows/codespell.yml | 2 +- .github/workflows/iwyi-check.yml | 2 +- .github/workflows/pr-ci.yml | 2 +- 7 files changed, 8 insertions(+), 8 deletions(-) diff --git a/.github/workflows/abicheck.yml b/.github/workflows/abicheck.yml index 08ad24ddb..802431b2e 100644 --- a/.github/workflows/abicheck.yml +++ b/.github/workflows/abicheck.yml @@ -28,11 +28,11 @@ jobs: sudo apt-get update sudo apt-get -y install abigail-tools clang gcc liburing-dev libnuma-dev - - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0 + - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 with: path: current - - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0 + - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 with: path: previous ref: ${{ github.event.pull_request.base.ref }} diff --git a/.github/workflows/bsd.yaml b/.github/workflows/bsd.yaml index 0e50b4b25..8edb96bce 100644 --- a/.github/workflows/bsd.yaml +++ b/.github/workflows/bsd.yaml @@ -18,7 +18,7 @@ jobs: name: Build under FreeBSD steps: - name: Checkout - uses: actions/checkout@v6.0.0 + uses: actions/checkout@v6.0.1 - name: Build uses: vmactions/freebsd-vm@v1 with: diff --git a/.github/workflows/checkpatch.yml b/.github/workflows/checkpatch.yml index 947a457e8..2d17cf500 100644 --- a/.github/workflows/checkpatch.yml +++ b/.github/workflows/checkpatch.yml @@ -10,7 +10,7 @@ jobs: checkpatch: runs-on: ubuntu-latest steps: - - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0 + - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 with: fetch-depth: 0 - name: Install dependencies diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index d84baa685..701d8518d 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -49,7 +49,7 @@ jobs: build-mode: manual steps: - name: Checkout repository - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0 + uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL diff --git a/.github/workflows/codespell.yml b/.github/workflows/codespell.yml index 2dd8a4e78..af68e2c9d 100644 --- a/.github/workflows/codespell.yml +++ b/.github/workflows/codespell.yml @@ -21,7 +21,7 @@ jobs: steps: - name: Checkout - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0 + uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 - name: Codespell uses: codespell-project/actions-codespell@8f01853be192eb0f849a5c7d721450e7a467c579 # v2.2 with: diff --git a/.github/workflows/iwyi-check.yml b/.github/workflows/iwyi-check.yml index a5a0f44b2..46c5a8ad4 100644 --- a/.github/workflows/iwyi-check.yml +++ b/.github/workflows/iwyi-check.yml @@ -18,7 +18,7 @@ jobs: name: Include What You Use Check runs-on: ubuntu-latest steps: - - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0 + - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 with: fetch-depth: 0 diff --git a/.github/workflows/pr-ci.yml b/.github/workflows/pr-ci.yml index 0f65e275e..5a53cce58 100644 --- a/.github/workflows/pr-ci.yml +++ b/.github/workflows/pr-ci.yml @@ -33,7 +33,7 @@ jobs: gcc-multilib g++-multilib libc6-dev-i386 \ libpcap0.8-dev:i386 libudev-dev:i386 pkg-config:i386 \ liburing-dev libnuma-dev - - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0 + - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 - uses: actions/setup-python@v6 with: python-version: '3.12' From 4f81b84d604e57a7c0bd33af375c4405d2ff689e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 8 Dec 2025 22:05:09 +0000 Subject: [PATCH 226/241] build(deps): bump github/codeql-action from 4.31.6 to 4.31.7 Bumps [github/codeql-action](https://github.com/github/codeql-action) from 4.31.6 to 4.31.7. - [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/fe4161a26a8629af62121b670040955b330f9af2...cf1bb45a277cb3c205638b2cd5c984db1c46a412) --- updated-dependencies: - dependency-name: github/codeql-action dependency-version: 4.31.7 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 701d8518d..e054a8622 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@fe4161a26a8629af62121b670040955b330f9af2 # v3.29.5 + uses: github/codeql-action/init@cf1bb45a277cb3c205638b2cd5c984db1c46a412 # v3.29.5 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@fe4161a26a8629af62121b670040955b330f9af2 # v3.29.5 + uses: github/codeql-action/analyze@cf1bb45a277cb3c205638b2cd5c984db1c46a412 # v3.29.5 with: category: "/language:${{matrix.language}}" From a6fdf8c4f795f671f818ac8f88d3fb89f18ab86c Mon Sep 17 00:00:00 2001 From: WekaJosh <80121792+WekaJosh@users.noreply.github.com> Date: Wed, 3 Dec 2025 10:03:07 -0800 Subject: [PATCH 227/241] Update fusermount.c Add WEKAFS to FS whitelist Signed-off-by: WekaJosh <80121792+WekaJosh@users.noreply.github.com> --- util/fusermount.c | 1 + 1 file changed, 1 insertion(+) diff --git a/util/fusermount.c b/util/fusermount.c index 7f19df7a8..f17b44f51 100644 --- a/util/fusermount.c +++ b/util/fusermount.c @@ -1219,6 +1219,7 @@ static int check_perm(const char **mntp, struct stat *stbuf, int *mountpoint_fd) 0x73717368 /* SQUASHFS_MAGIC */, 0x01021994 /* TMPFS_MAGIC */, 0x24051905 /* UBIFS_SUPER_MAGIC */, + 0x18031977 /* WEKAFS_SUPER_MAGIC */, #if __SIZEOF_LONG__ > 4 0x736675005346544e /* UFSD */, #endif From 033e0c3fbeb643bbd4e7775849975b6c3f754972 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 15 Dec 2025 22:05:25 +0000 Subject: [PATCH 228/241] build(deps): bump github/codeql-action from 4.31.7 to 4.31.8 Bumps [github/codeql-action](https://github.com/github/codeql-action) from 4.31.7 to 4.31.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/cf1bb45a277cb3c205638b2cd5c984db1c46a412...1b168cd39490f61582a9beae412bb7057a6b2c4e) --- updated-dependencies: - dependency-name: github/codeql-action dependency-version: 4.31.8 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 e054a8622..914d81613 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@cf1bb45a277cb3c205638b2cd5c984db1c46a412 # v3.29.5 + uses: github/codeql-action/init@1b168cd39490f61582a9beae412bb7057a6b2c4e # v3.29.5 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@cf1bb45a277cb3c205638b2cd5c984db1c46a412 # v3.29.5 + uses: github/codeql-action/analyze@1b168cd39490f61582a9beae412bb7057a6b2c4e # v3.29.5 with: category: "/language:${{matrix.language}}" From 2bb27d8197cb9a3c6f0e7f17aa0cd96f1053ff47 Mon Sep 17 00:00:00 2001 From: Bernd Schubert <bschubert@ddn.com> Date: Sun, 23 Nov 2025 22:55:58 +0100 Subject: [PATCH 229/241] fuse-io-uring: Handle EAGAIN/EINTR in fuse-io-uring Especially FUSE_IO_URING_CMD_REGISTER can easily get -EAGAIN - if it arrives in kernel before FUSE_INIT was finally handled. Easiest solution is to send it again. I'm not sure if FUSE_IO_URING_CMD_COMMIT_AND_FETCH might fail with -EINTR or -EGAIN - not from fuse client/kernel point of view, but possibly by io-uring. Better if we handle that as well. Signed-off-by: Bernd Schubert <bschubert@ddn.com> --- lib/fuse_uring.c | 68 ++++++++++++++++++++++++++++++++++++++++++------ lib/util.h | 6 +++++ 2 files changed, 66 insertions(+), 8 deletions(-) diff --git a/lib/fuse_uring.c b/lib/fuse_uring.c index 1311f5a58..5b044b0c3 100644 --- a/lib/fuse_uring.c +++ b/lib/fuse_uring.c @@ -43,6 +43,8 @@ struct fuse_ring_ent { /* commit id of a fuse request */ uint64_t req_commit_id; + enum fuse_uring_cmd last_cmd; + /* header and payload */ struct iovec iov[2]; }; @@ -174,9 +176,8 @@ static int fuse_uring_commit_sqe(struct fuse_ring_pool *ring_pool, return -EIO; } - fuse_uring_sqe_prepare(sqe, ring_ent, - FUSE_IO_URING_CMD_COMMIT_AND_FETCH); - + ring_ent->last_cmd = FUSE_IO_URING_CMD_COMMIT_AND_FETCH; + fuse_uring_sqe_prepare(sqe, ring_ent, ring_ent->last_cmd); fuse_uring_sqe_set_req_data(fuse_uring_get_sqe_cmd(sqe), queue->qid, ring_ent->req_commit_id); @@ -532,6 +533,48 @@ static struct fuse_ring_pool *fuse_create_ring(struct fuse_session *se) return NULL; } +static void fuse_uring_resubmit(struct fuse_ring_queue *queue, + struct fuse_ring_ent *ent) +{ + struct io_uring_sqe *sqe; + + sqe = io_uring_get_sqe(&queue->ring); + if (sqe == NULL) { + /* This is an impossible condition, unless there is a bug. + * The kernel sent back an SQEs, which is assigned to a request. + * There is no way to get out of SQEs, as the number of + * SQEs matches the number tof requests. + */ + + queue->ring_pool->se->error = -EIO; + fuse_log(FUSE_LOG_ERR, "Failed to get a ring SQEs\n"); + + return; + } + + fuse_uring_sqe_prepare(sqe, ent, ent->last_cmd); + + switch (ent->last_cmd) { + case FUSE_IO_URING_CMD_REGISTER: + sqe->addr = (uint64_t)(ent->iov); + sqe->len = 2; + fuse_uring_sqe_set_req_data(fuse_uring_get_sqe_cmd(sqe), + queue->qid, 0); + break; + case FUSE_IO_URING_CMD_COMMIT_AND_FETCH: + fuse_uring_sqe_set_req_data(fuse_uring_get_sqe_cmd(sqe), + queue->qid, ent->req_commit_id); + break; + default: + fuse_log(FUSE_LOG_ERR, "Unknown command type: %d\n", + ent->last_cmd); + queue->ring_pool->se->error = -EINVAL; + break; + } + + /* caller submits */ +} + static void fuse_uring_handle_cqe(struct fuse_ring_queue *queue, struct io_uring_cqe *cqe) { @@ -579,6 +622,7 @@ static int fuse_uring_queue_handle_cqes(struct fuse_ring_queue *queue) size_t num_completed = 0; struct io_uring_cqe *cqe; unsigned int head; + struct fuse_ring_ent *ent; int ret = 0; io_uring_for_each_cqe(&queue->ring, head, cqe) { @@ -587,19 +631,27 @@ static int fuse_uring_queue_handle_cqes(struct fuse_ring_queue *queue) num_completed++; err = cqe->res; - if (err != 0) { + if (unlikely(err != 0)) { if (err > 0 && ((uintptr_t)io_uring_cqe_get_data(cqe) == (unsigned int)queue->eventfd)) { /* teardown from eventfd */ return -ENOTCONN; } - // XXX: Needs rate limited logs, otherwise log spam - //fuse_log(FUSE_LOG_ERR, "cqe res: %d\n", cqe->res); + + switch (err) { + case -EAGAIN: + fallthrough; + case -EINTR: + ent = io_uring_cqe_get_data(cqe); + fuse_uring_resubmit(queue, ent); + continue; + default: + break; + } /* -ENOTCONN is ok on umount */ - if (err != -EINTR && err != -EAGAIN && - err != -ENOTCONN) { + if (err != -ENOTCONN) { se->error = cqe->res; /* return first error */ diff --git a/lib/util.h b/lib/util.h index aae40e21d..107a2bfdd 100644 --- a/lib/util.h +++ b/lib/util.h @@ -40,4 +40,10 @@ static inline uint64_t fuse_higher_32_bits(uint64_t nr) ((type *)(__mptr - offsetof(type, member))); \ }) +#if __has_attribute(__fallthrough__) +#define fallthrough __attribute__((__fallthrough__)) +#else +#define fallthrough do {} while (0) +#endif + #endif /* FUSE_UTIL_H_ */ From 834a496b5ec0380db12f1e0d1d8435d118965e83 Mon Sep 17 00:00:00 2001 From: Bernd Schubert <bschubert@ddn.com> Date: Sun, 14 Dec 2025 22:04:33 +0100 Subject: [PATCH 230/241] fuse-io-uring: Function rename and split into two function The better name is fuse_uring_register_queue() and it also splits out registering the entries into a separate function for readability. Signed-off-by: Bernd Schubert <bschubert@ddn.com> --- lib/fuse_uring.c | 64 +++++++++++++++++++++++++++++------------------- 1 file changed, 39 insertions(+), 25 deletions(-) diff --git a/lib/fuse_uring.c b/lib/fuse_uring.c index 5b044b0c3..0ed87495c 100644 --- a/lib/fuse_uring.c +++ b/lib/fuse_uring.c @@ -416,40 +416,54 @@ static void fuse_session_destruct_uring(struct fuse_ring_pool *fuse_ring) free(fuse_ring); } -static int fuse_uring_prepare_fetch_sqes(struct fuse_ring_queue *queue) +static int fuse_uring_register_ent(struct fuse_ring_queue *queue, + struct fuse_ring_ent *ent) { - struct fuse_ring_pool *ring_pool = queue->ring_pool; - unsigned int sq_ready; struct io_uring_sqe *sqe; - for (size_t idx = 0; idx < ring_pool->queue_depth; idx++) { - struct fuse_ring_ent *ent = &queue->ent[idx]; + sqe = io_uring_get_sqe(&queue->ring); + if (sqe == NULL) { + /* + * All SQEs are idle here - no good reason this + * could fail + */ + fuse_log(FUSE_LOG_ERR, "Failed to get all ring SQEs"); + return -EIO; + } - sqe = io_uring_get_sqe(&queue->ring); - if (sqe == NULL) { - /* All SQEs are idle here - no good reason this - * could fail - */ + ent->last_cmd = FUSE_IO_URING_CMD_REGISTER; + fuse_uring_sqe_prepare(sqe, ent, ent->last_cmd); - fuse_log(FUSE_LOG_ERR, "Failed to get all ring SQEs"); - return -EIO; - } + /* only needed for fetch */ + ent->iov[0].iov_base = ent->req_header; + ent->iov[0].iov_len = queue->req_header_sz; - fuse_uring_sqe_prepare(sqe, ent, FUSE_IO_URING_CMD_REGISTER); + ent->iov[1].iov_base = ent->op_payload; + ent->iov[1].iov_len = ent->req_payload_sz; - /* only needed for fetch */ - ent->iov[0].iov_base = ent->req_header; - ent->iov[0].iov_len = queue->req_header_sz; + sqe->addr = (uint64_t)(ent->iov); + sqe->len = 2; - ent->iov[1].iov_base = ent->op_payload; - ent->iov[1].iov_len = ent->req_payload_sz; + /* this is a fetch, kernel does not read commit id */ + fuse_uring_sqe_set_req_data(fuse_uring_get_sqe_cmd(sqe), queue->qid, 0); - sqe->addr = (uint64_t)(ent->iov); - sqe->len = 2; + return 0; - /* this is a fetch, kernel does not read commit id */ - fuse_uring_sqe_set_req_data(fuse_uring_get_sqe_cmd(sqe), - queue->qid, 0); +} + +static int fuse_uring_register_queue(struct fuse_ring_queue *queue) +{ + struct fuse_ring_pool *ring_pool = queue->ring_pool; + unsigned int sq_ready; + struct io_uring_sqe *sqe; + int res; + + for (size_t idx = 0; idx < ring_pool->queue_depth; idx++) { + struct fuse_ring_ent *ent = &queue->ent[idx]; + + res = fuse_uring_register_ent(queue, ent); + if (res != 0) + return res; } sq_ready = io_uring_sq_ready(&queue->ring); @@ -757,7 +771,7 @@ static int fuse_uring_init_queue(struct fuse_ring_queue *queue) list_init_req(req); } - res = fuse_uring_prepare_fetch_sqes(queue); + res = fuse_uring_register_queue(queue); if (res != 0) { fuse_log( FUSE_LOG_ERR, From b9cae498ff9df22d785128576c0b13077452a7c4 Mon Sep 17 00:00:00 2001 From: Bernd Schubert <bschubert@ddn.com> Date: Sun, 14 Dec 2025 23:21:41 +0100 Subject: [PATCH 231/241] fuse-io-uring: Initialize queue->eventfd This was forgotten before. Signed-off-by: Bernd Schubert <bschubert@ddn.com> --- lib/fuse_uring.c | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/fuse_uring.c b/lib/fuse_uring.c index 0ed87495c..a90a0a789 100644 --- a/lib/fuse_uring.c +++ b/lib/fuse_uring.c @@ -532,6 +532,7 @@ static struct fuse_ring_pool *fuse_create_ring(struct fuse_session *se) queue->numa_node = numa_node_of_cpu(qid); queue->qid = qid; queue->ring_pool = fuse_ring; + queue->eventfd = -1; } pthread_cond_init(&fuse_ring->thread_start_cond, NULL); From 8e536dcb767f31aae986cc4aff5a1a56420c7de5 Mon Sep 17 00:00:00 2001 From: Bernd Schubert <bschubert@ddn.com> Date: Sun, 14 Dec 2025 23:12:27 +0100 Subject: [PATCH 232/241] fuse-io-uring: Add a lock around io_uring_submit / io_uring_submit_and_wait There were two issues 1) If the application used an async thread outside of the ring thread to submit results, the ring thread might have submitted via io_uring_submit_and_wait() SQEs that were not ready yet. I.e. in fuse_uring_commit_sqe() it might have already called sqe = io_uring_get_sqe(&queue->ring); but then fuse_uring_sqe_prepare() and fuse_uring_sqe_prepare() might not have been run yet. 2) If run from a single thread only and without any corouting / reactor feature (only available in my private branch right now) the function fuse_uring_commit_sqe() didn't need to call io_uring_submit() at all. I.e. before this commit there were one system call per request too much. Signed-off-by: Bernd Schubert <bschubert@ddn.com> --- lib/fuse_uring.c | 27 ++++++++++++++++++++++++--- 1 file changed, 24 insertions(+), 3 deletions(-) diff --git a/lib/fuse_uring.c b/lib/fuse_uring.c index a90a0a789..0e5e8e9e1 100644 --- a/lib/fuse_uring.c +++ b/lib/fuse_uring.c @@ -59,6 +59,9 @@ struct fuse_ring_queue { size_t req_header_sz; struct io_uring ring; + pthread_mutex_t ring_lock; + bool cqe_processing; + /* size depends on queue depth */ struct fuse_ring_ent ent[]; }; @@ -156,12 +159,20 @@ static int fuse_uring_commit_sqe(struct fuse_ring_pool *ring_pool, struct fuse_ring_queue *queue, struct fuse_ring_ent *ring_ent) { + bool locked = false; struct fuse_session *se = ring_pool->se; struct fuse_uring_req_header *rrh = ring_ent->req_header; struct fuse_out_header *out = (struct fuse_out_header *)&rrh->in_out; struct fuse_uring_ent_in_out *ent_in_out = (struct fuse_uring_ent_in_out *)&rrh->ring_ent_in_out; - struct io_uring_sqe *sqe = io_uring_get_sqe(&queue->ring); + struct io_uring_sqe *sqe; + + if (pthread_self() != queue->tid) { + pthread_mutex_lock(&queue->ring_lock); + locked = true; + } + + sqe = io_uring_get_sqe(&queue->ring); if (sqe == NULL) { /* This is an impossible condition, unless there is a bug. @@ -186,8 +197,11 @@ static int fuse_uring_commit_sqe(struct fuse_ring_pool *ring_pool, out->unique, ent_in_out->payload_sz); } - /* XXX: This needs to be a ring config option */ - io_uring_submit(&queue->ring); + if (!queue->cqe_processing) + io_uring_submit(&queue->ring); + + if (locked) + pthread_mutex_unlock(&queue->ring_lock); return 0; } @@ -408,6 +422,8 @@ static void fuse_session_destruct_uring(struct fuse_ring_pool *fuse_ring) numa_free(ent->op_payload, ent->req_payload_sz); numa_free(ent->req_header, queue->req_header_sz); } + + pthread_mutex_destroy(&queue->ring_lock); } free(fuse_ring->queues); @@ -533,6 +549,7 @@ static struct fuse_ring_pool *fuse_create_ring(struct fuse_session *se) queue->qid = qid; queue->ring_pool = fuse_ring; queue->eventfd = -1; + pthread_mutex_init(&queue->ring_lock, NULL); } pthread_cond_init(&fuse_ring->thread_start_cond, NULL); @@ -822,7 +839,11 @@ static void *fuse_uring_thread(void *arg) while (!atomic_load_explicit(&se->mt_exited, memory_order_relaxed)) { io_uring_submit_and_wait(&queue->ring, 1); + pthread_mutex_lock(&queue->ring_lock); + queue->cqe_processing = true; err = fuse_uring_queue_handle_cqes(queue); + queue->cqe_processing = false; + pthread_mutex_unlock(&queue->ring_lock); if (err < 0) goto err; } From 4dea65b46e5fdcd33cf6575f2c030f68e4e37f0b Mon Sep 17 00:00:00 2001 From: Bernd Schubert <bernd@bsbernd.com> Date: Tue, 12 Aug 2025 17:20:45 +0200 Subject: [PATCH 233/241] Add signify verification in make_release_tarball.sh Signed-off-by: Bernd Schubert <bernd@bsbernd.com> (cherry picked from commit 8c8a62b1ef8b22641325ed8584d0e23e039ac42d) --- 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 8bfc50dfcfa05a7e884ecae5e1cb0e88c4a4f67a Mon Sep 17 00:00:00 2001 From: Bernd Schubert <bschubert@ddn.com> Date: Fri, 19 Dec 2025 00:05:19 +0100 Subject: [PATCH 234/241] Prepare 3.18.0 release Update AUTHORS and ChangeLog.rst, added signify/fuse-3.19.pub. Signed-off-by: Bernd Schubert <bschubert@ddn.com> --- AUTHORS | 21 ++++++++++- ChangeLog.rst | 81 +++++++++++++++++++++++++++++++++++++++++-- signify/fuse-3.19.pub | 2 ++ 3 files changed, 100 insertions(+), 4 deletions(-) create mode 100644 signify/fuse-3.19.pub diff --git a/AUTHORS b/AUTHORS index 837a62c84..e07747388 100644 --- a/AUTHORS +++ b/AUTHORS @@ -260,6 +260,25 @@ Tyler Hall <tylerwhall@gmail.com> yangyun <yangyun50@huawei.com> Abhishek <abhi_45645@yahoo.com> -# New authors since 666b2c3fa5159e3c72a0d08117507475117c9319 +# New authors since fuse-3.17.1 Luis Henriques <luis@igalia.com> Zegang <zegang.luo@qq.com> +swj <1186093704@qq.com> +Gleb Popov <6yearold@gmail.com> +WekaJosh <80121792+WekaJosh@users.noreply.github.com> +Alexander Monakov <amonakov@ispras.ru> +Ben Dooks <ben.dooks@codethink.co.uk> +Ben Linsay <blinsay@gmail.com> +Dave Vasilevsky <dave@vasilevsky.ca> +Darrick J. Wong <djwong@kernel.org> +Georgi Valkov <gvalkov@gmail.com> +Alik Aslanyan <inline0@pm.me> +jnr0006 <jacob.nick.riley@gmail.com> +Jingbo Xu <jefflexu@linux.alibaba.com> +Long Li <leo.lilong@huawei.com> +Maksim Harbachou <maksim.harbachou@resilio.com> +Meng Lu Wang <mwang@ddn.com> +Vassili Tchersky <vt+git@vbc.su> +Vassili Tchersky <vt+git@vbcy.org> +izxl007 <zeng.zheng@zte.com.cn> +Zeno Sebastian Endemann <zeno.endemann@mailbox.org> diff --git a/ChangeLog.rst b/ChangeLog.rst index 505d9dba8..15c998cf1 100644 --- a/ChangeLog.rst +++ b/ChangeLog.rst @@ -1,6 +1,81 @@ -libfuse 3.18 +libfuse 3.18.0 (2025-12-18) +=========================== + +New Features +------------ + +* fuse-over-io-uring communication +* statx support +* Request timeouts: Prevent hung operations +* FUSE_NOTIFY_INC_EPOCH: New notification mechanism for epoch counters + +Important Fixes +---------------- + +* Fixed double unmount on FUSE_DESTROY +* Fixed junk readdirplus results when filesystem doesn't fill stat info +* Fixed memory deallocation in fuse_session_loop_remember +* Fixed COPY_FILE_RANGE interface + +Platform Support +---------------- + +* Improved FreeBSD support (mount error reporting, test runner, build fixes) +* Fixed 32-bit architecture builds +* Fixed build with musl libc and older kernels (< 5.9) + +Other Improvements +------------------ + +* Added PanFS to fusermount whitelist +* Thread naming support for easier debugging + + +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 +* 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 + 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 +* 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 + build warnings +* signify public key added for future 3.18 -libfuse 3.17.1-rc0 (2024-02.10) +libfuse 3.17.1-rc0 (2025-02.10) =============================== * Fix libfuse build with FUSE_USE_VERSION 30 @@ -16,7 +91,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/signify/fuse-3.19.pub b/signify/fuse-3.19.pub new file mode 100644 index 000000000..317f8f78f --- /dev/null +++ b/signify/fuse-3.19.pub @@ -0,0 +1,2 @@ +untrusted comment: signify public key +RWR4cEcMGJhD3Dnd3NOeJck3WiuVt9A7mrkq+nQYwrwwmMdDDAan/YiU From 9157d99f17384f362c65b02cec065e0aec5f0b56 Mon Sep 17 00:00:00 2001 From: Bernd Schubert <bernd@bsbernd.com> Date: Fri, 19 Dec 2025 00:17:47 +0100 Subject: [PATCH 235/241] prepare 3.18.0 release v2 Forgot meson.build and also some updates to make_release_tarball.sh Signed-off-by: Bernd Schubert <bernd@bsbernd.com> --- make_release_tarball.sh | 2 +- meson.build | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/make_release_tarball.sh b/make_release_tarball.sh index ce2fe16c5..45438a5cd 100755 --- a/make_release_tarball.sh +++ b/make_release_tarball.sh @@ -1,4 +1,4 @@ -#!/bin/sh +#!/bin/sh -x # # Create tarball from Git tag, removing and adding # some files. diff --git a/meson.build b/meson.build index e3c7eeba6..e8e7c448b 100644 --- a/meson.build +++ b/meson.build @@ -1,5 +1,5 @@ project('libfuse3', ['c'], - version: '3.18.0-rc0', # Version with RC suffix + version: '3.18.0', meson_version: '>= 0.60.0', default_options: [ 'buildtype=debugoptimized', From 98f94e0b1d0b1479d833a3584442a95b3700ad19 Mon Sep 17 00:00:00 2001 From: Bernd Schubert <bernd@bsbernd.com> Date: Sat, 20 Dec 2025 13:22:25 +0100 Subject: [PATCH 236/241] Fix fuse version script / 3.17.3 to 3.18 Several functions like fuse_set_feature_flag had been introduced some time ago in master (3.18-rc0) first and then backported to 3.17.3. In order to handle the backport a new section FUSE_3.17.3 was introduced in fuse_version_script. The master branch kept these symbols in the 3.18 section, which now causes an ABI issue with the 3.18 release. This commit attempts to fix that and creates the 3.17.3 section in the master and 3.18.x branch. Closes: https://github.com/libfuse/libfuse/issues/1397 Signed-off-by: Bernd Schubert <bernd@bsbernd.com> --- lib/fuse_versionscript | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/lib/fuse_versionscript b/lib/fuse_versionscript index 2feafcf83..b58360feb 100644 --- a/lib/fuse_versionscript +++ b/lib/fuse_versionscript @@ -202,17 +202,21 @@ FUSE_3.17 { fuse_log_close_syslog; } FUSE_3.12; -FUSE_3.18 { +FUSE_3.17.3 { global: - fuse_req_is_uring; - fuse_req_get_payload; fuse_set_feature_flag; fuse_unset_feature_flag; fuse_get_feature_flag; - fuse_lowlevel_notify_increment_epoch; - # Not part of public API, for internal test use only + # Not part of public API, for internal testing only fuse_convert_to_conn_want_ext; +} FUSE_3.17; + +FUSE_3.18 { + global: + fuse_req_is_uring; + fuse_req_get_payload; + fuse_lowlevel_notify_increment_epoch; fuse_reply_statx; fuse_fs_statx; From c35865b22ef55c38238a7d3630683aa47fdc76ff Mon Sep 17 00:00:00 2001 From: Bernd Schubert <bernd@bsbernd.com> Date: Sat, 20 Dec 2025 14:03:20 +0100 Subject: [PATCH 237/241] Add an ABI check for the last tag of the previous release The 3.18.0 ABI issue came up as we didn't have an ABI check to the last previous release. The supressions file needs to compare to the previous version is also used for the existing ABI check - will reduce false positives. This will fail ABI checks against libfuse-3.18.0, as some symbols in 3.18.0 were marked as the wrong version. Signed-off-by: Bernd Schubert <bernd@bsbernd.com> (cherry picked from commit fef782261a53c72c2dfcb3a3b2ca3cd1abace92c) --- .github/workflows/abicheck.yml | 7 +- .github/workflows/abicheck_prev_release.yml | 76 +++++++++++++++++++ .../workflows/abidiff_suppressions.abignore | 36 +++++++++ .../workflows/find_previous_release_tag.sh | 69 +++++++++++++++++ 4 files changed, 185 insertions(+), 3 deletions(-) create mode 100644 .github/workflows/abicheck_prev_release.yml create mode 100644 .github/workflows/abidiff_suppressions.abignore create mode 100755 .github/workflows/find_previous_release_tag.sh diff --git a/.github/workflows/abicheck.yml b/.github/workflows/abicheck.yml index 802431b2e..245b1bbc1 100644 --- a/.github/workflows/abicheck.yml +++ b/.github/workflows/abicheck.yml @@ -59,7 +59,8 @@ jobs: - name: Run abidiff run: abidiff --no-added-syms - --headers-dir1 previous/include/ - --headers-dir2 current/include/ - previous/build/lib/libfuse3.so + --suppressions current/.github/workflows/abidiff_suppressions.abignore + --headers-dir1 previous/include/ + --headers-dir2 current/include/ + previous/build/lib/libfuse3.so current/build/lib/libfuse3.so diff --git a/.github/workflows/abicheck_prev_release.yml b/.github/workflows/abicheck_prev_release.yml new file mode 100644 index 000000000..235341b55 --- /dev/null +++ b/.github/workflows/abicheck_prev_release.yml @@ -0,0 +1,76 @@ +--- +name: 'libfuse ABI check against previous major release' + +on: + push: + branches: + - master + - 'fuse-[0-9]+.[0-9]+*' # This will match branches like 3.17, 3.18, 4.0, etc. + pull_request: + branches: + - master + - 'fuse-[0-9]+.[0-9]+*' +permissions: + contents: read + +jobs: + abi: + runs-on: '${{ matrix.os }}' + strategy: + matrix: + os: + - ubuntu-latest + + steps: + - name: Install dependencies (Ubuntu) + if: runner.os == 'Linux' + run: | + sudo apt-get update + sudo apt-get -y install abigail-tools clang gcc liburing-dev libnuma-dev + + - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 + with: + path: current + fetch-depth: 0 # Fetch all history and tags + + - name: Determine previous major release tag + id: prev_release + run: | + cd current + chmod +x .github/workflows/find_previous_release_tag.sh + PREV_TAG=$(.github/workflows/find_previous_release_tag.sh) + echo "prev_tag=$PREV_TAG" >> $GITHUB_OUTPUT + + - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 + with: + path: previous + ref: ${{ steps.prev_release.outputs.prev_tag }} + + - uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6.0.0 + with: + python-version: '3.12' + + - name: Build current + working-directory: current + run: | + pip install -r requirements.txt + meson setup build --buildtype=debug + meson compile -C build + + - name: Build previous + working-directory: previous + run: | + echo "Previous release tag: ${{ steps.prev_release.outputs.prev_tag }}" + echo "Commit-id of previous release: $(git show HEAD)" + pip install -r requirements.txt + meson setup build --buildtype=debug + meson compile -C build + + - name: Run abidiff + run: abidiff + --no-added-syms + --suppressions current/.github/workflows/abidiff_suppressions.abignore + --headers-dir1 previous/include/ + --headers-dir2 current/include/ + previous/build/lib/libfuse3.so + current/build/lib/libfuse3.so diff --git a/.github/workflows/abidiff_suppressions.abignore b/.github/workflows/abidiff_suppressions.abignore new file mode 100644 index 000000000..949efda6b --- /dev/null +++ b/.github/workflows/abidiff_suppressions.abignore @@ -0,0 +1,36 @@ +# Suppression file for abidiff false positives in libfuse +# This file suppresses ABI changes that are actually compatible but flagged by abidiff + +[suppress_type] +# Suppress the fuse_conn_info reserved array transformation +# This change is ABI-compatible: uint32_t[16] -> uint16_t request_timeout + uint16_t[31] +# Both use exactly 64 bytes (16*4 = 32*2 = 64 bytes) +name = fuse_conn_info +# Suppress changes to the reserved field that are size/offset related +change_kind = size-or-offset-change +has_data_member_inserted_at = offset_in_bits(512) + +[suppress_type] +# Also suppress the general struct size change for fuse_conn_info +# since the total size remains the same (128 bytes) by a static assertion +# in the code +name = fuse_conn_info +change_kind = size-change + +[suppress_type] +# Suppress ALL changes to fuse_operations struct +# These are backward compatible due to the op_size mechanism in fuse_main() +# Applications pass sizeof(struct fuse_operations) at compile time, +# and the library uses memcpy(&fs->op, op, op_size) to safely copy only +# the fields the application knows about. New fields remain NULL. +name = fuse_operations +has_data_member_inserted_at = end +has_size_change = yes + +[suppress_type] +# Suppress ALL changes to fuse_lowlevel_ops struct +# These are backward compatible due to the op_size mechanism in fuse_session_new() +# Same pattern as fuse_operations - op_size controls safe copying +name = fuse_lowlevel_ops +has_data_member_inserted_at = end +has_size_change = yes diff --git a/.github/workflows/find_previous_release_tag.sh b/.github/workflows/find_previous_release_tag.sh new file mode 100755 index 000000000..0e73336a1 --- /dev/null +++ b/.github/workflows/find_previous_release_tag.sh @@ -0,0 +1,69 @@ +#!/bin/bash + +set -e + +# Script to find the previous major release tag for libfuse +# Usage: ./find_previous_release_tag.sh + +# Get current version from meson.build +# Pattern matches: version : "3.18.0" or version: '3.18.0' +VERSION_PATTERN="version\s*:\s*['\"]" +VERSION_EXTRACT="s/.*version\s*:\s*['\"]([^'\"]+)['\"].*/\1/" + +CURRENT_VERSION=$(grep -E "$VERSION_PATTERN" meson.build | \ + sed -E "$VERSION_EXTRACT") +echo "Current version: $CURRENT_VERSION" >&2 + +# Extract major.minor version (e.g., 3.18 from 3.18.0) +# Pattern captures first two numbers separated by dot +MAJOR_MINOR_PATTERN='s/^([0-9]+\.[0-9]+).*/\1/' + +CURRENT_MAJOR_MINOR=$(echo "$CURRENT_VERSION" | \ + sed -E "$MAJOR_MINOR_PATTERN") +echo "Current major.minor: $CURRENT_MAJOR_MINOR" >&2 + +# Get all major.minor versions from tags, sort them, and find the one before +# current +# Pattern matches tags like: fuse-3.17.0, fuse-3.18.1, etc. +FUSE_TAG_PATTERN="^fuse-[0-9]+\.[0-9]+" +# Pattern extracts major.minor from version strings +TAG_MAJOR_MINOR_PATTERN='s/^([0-9]+\.[0-9]+).*/\1/' + +ALL_MAJOR_MINOR=$(git tag --list | \ + grep -E "$FUSE_TAG_PATTERN" | \ + sed 's/fuse-//' | \ + sed -E "$TAG_MAJOR_MINOR_PATTERN" | \ + sort -V -u) +echo "All major.minor versions found:" >&2 +echo "$ALL_MAJOR_MINOR" >&2 + +# Find the previous major.minor version +PREV_MAJOR_MINOR=$(echo "$ALL_MAJOR_MINOR" | \ + grep -B1 "^${CURRENT_MAJOR_MINOR}$" | \ + head -1) + +if [ -z "$PREV_MAJOR_MINOR" ] || [ "$PREV_MAJOR_MINOR" = "$CURRENT_MAJOR_MINOR" ]; then + echo "Error: No previous major.minor version found before $CURRENT_MAJOR_MINOR" >&2 + exit 1 +fi + +echo "Previous major.minor: $PREV_MAJOR_MINOR" >&2 + +# Get the latest tag for the previous major.minor version +# Pattern matches tags like: fuse-3.17.0, fuse-3.17.1, fuse-3.17.2, etc. +PREV_TAG_PATTERN="^fuse-${PREV_MAJOR_MINOR}\.[0-9]+" + +PREV_TAG=$(git tag --list | \ + grep -E "$PREV_TAG_PATTERN" | \ + sort -V | \ + tail -1) + +if [ -z "$PREV_TAG" ]; then + echo "Error: No previous major release tag found for version $PREV_MAJOR_MINOR" >&2 + exit 1 +fi + +echo "Previous release tag: $PREV_TAG" >&2 + +# Output the tag to stdout (this is what the workflow will capture) +echo "$PREV_TAG" From 2a6da8cb338b1718e899240d1d43d52e311e504a Mon Sep 17 00:00:00 2001 From: Bernd Schubert <bernd@bsbernd.com> Date: Sat, 20 Dec 2025 23:33:15 +0100 Subject: [PATCH 238/241] Prepare the 3.18.1 release --- ChangeLog.rst | 6 ++++++ meson.build | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/ChangeLog.rst b/ChangeLog.rst index 15c998cf1..b5e8402c7 100644 --- a/ChangeLog.rst +++ b/ChangeLog.rst @@ -1,3 +1,9 @@ +libfuse 3.18.1 (2025-12-20) +=========================== +* Fix a critical ABI issue compared to libfuse-3.17.3+ +* Note: This breaks ABI compatibility to libfuse-3.18.0 + (given that 3.18.0 is out for 2 days only, probably the lesser evil) + libfuse 3.18.0 (2025-12-18) =========================== diff --git a/meson.build b/meson.build index e8e7c448b..4eb6c0032 100644 --- a/meson.build +++ b/meson.build @@ -1,5 +1,5 @@ project('libfuse3', ['c'], - version: '3.18.0', + version: '3.18.1', meson_version: '>= 0.60.0', default_options: [ 'buildtype=debugoptimized', From 7beb86c09b6ec5aab14dc25256ed8a5ad18554d7 Mon Sep 17 00:00:00 2001 From: Abhinav Agarwal <abhinavagarwal1996@gmail.com> Date: Tue, 17 Mar 2026 19:03:57 +0000 Subject: [PATCH 239/241] fuse-io-uring: Fix NULL deref and memory leak in fuse_uring_init_queue MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two bugs in fuse_uring_init_queue(): 1. numa_alloc_local() return values are not checked. On allocation failure the code proceeds with NULL pointers, causing a NULL pointer dereference when the SQE registration subsequently accesses the header/payload buffers. 2. When fuse_uring_register_queue() fails, the function falls through to return queue->ring.ring_fd (success) instead of propagating the error. The NUMA allocations are then leaked, and the caller uses a broken queue for I/O. Fix by returning -ENOMEM on allocation failure and returning the error from fuse_uring_register_queue() on registration failure. In both cases, cleanup (including NUMA frees and eventfd close) is delegated to fuse_session_destruct_uring() via the fuse_uring_start() error path — which is the intended cleanup owner for this subsystem. Fixes CVE-2026-33179 Signed-off-by: Abhinav Agarwal <abhinav.agarwal@rubrik.com> --- lib/fuse_uring.c | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/lib/fuse_uring.c b/lib/fuse_uring.c index 0e5e8e9e1..e841abdcb 100644 --- a/lib/fuse_uring.c +++ b/lib/fuse_uring.c @@ -759,7 +759,7 @@ static int fuse_uring_init_queue(struct fuse_ring_queue *queue) if (res != 0) { fuse_log(FUSE_LOG_ERR, "qid=%d io_uring init failed\n", queue->qid); - goto err; + return res; } queue->req_header_sz = ROUND_UP(sizeof(struct fuse_ring_ent), @@ -777,10 +777,14 @@ static int fuse_uring_init_queue(struct fuse_ring_queue *queue) */ ring_ent->req_header = numa_alloc_local(queue->req_header_sz); + if (!ring_ent->req_header) + return -ENOMEM; ring_ent->req_payload_sz = ring->max_req_payload_sz; ring_ent->op_payload = numa_alloc_local(ring_ent->req_payload_sz); + if (!ring_ent->op_payload) + return -ENOMEM; req->se = se; pthread_mutex_init(&req->lock, NULL); @@ -796,13 +800,10 @@ static int fuse_uring_init_queue(struct fuse_ring_queue *queue) "Grave fuse-uring error on preparing SQEs, aborting\n"); se->error = -EIO; fuse_session_exit(se); + return res; } return queue->ring.ring_fd; - -err: - close(queue->eventfd); - return res; } static void *fuse_uring_thread(void *arg) From 49fcd891a58f622c098e2ca67d66086f7b213836 Mon Sep 17 00:00:00 2001 From: Abhinav Agarwal <abhinavagarwal1996@gmail.com> Date: Mon, 16 Mar 2026 23:14:44 +0000 Subject: [PATCH 240/241] fuse-io-uring: Fix UAF and NULL deref in startup error path MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit In fuse_uring_start(), the error path called fuse_session_destruct_uring() which frees fuse_ring, then stored the freed pointer in se->uring.pool. On session shutdown, the session loop cleanup checks if (se->uring.pool) and calls fuse_uring_stop() — dereferencing the freed memory (use-after-free). Fix by setting se->uring.pool = NULL in the error path so the cleanup check is skipped. Also add a NULL guard before the destruct call to handle the case where fuse_create_ring() itself returns NULL, which would cause a NULL pointer dereference at fuse_ring->nr_queues. Fixes CVE-2026-33150 Signed-off-by: Abhinav Agarwal <abhinav.agarwal@rubrik.com> --- lib/fuse_uring.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/lib/fuse_uring.c b/lib/fuse_uring.c index e841abdcb..c3127b7e5 100644 --- a/lib/fuse_uring.c +++ b/lib/fuse_uring.c @@ -925,8 +925,9 @@ int fuse_uring_start(struct fuse_session *se) err: if (err) { /* Note all threads need to have been started */ - fuse_session_destruct_uring(fuse_ring); - se->uring.pool = fuse_ring; + if (fuse_ring) + fuse_session_destruct_uring(fuse_ring); + se->uring.pool = NULL; } return err; } From 033844748010a3b8265bf1c90b9ae8ffe4cd9ca7 Mon Sep 17 00:00:00 2001 From: Bernd Schubert <bernd@bsbernd.com> Date: Wed, 18 Mar 2026 21:40:39 +0100 Subject: [PATCH 241/241] Prepare 3.18.2 release --- AUTHORS | 3 +++ ChangeLog.rst | 6 ++++++ meson.build | 2 +- 3 files changed, 10 insertions(+), 1 deletion(-) diff --git a/AUTHORS b/AUTHORS index e07747388..2c63b1feb 100644 --- a/AUTHORS +++ b/AUTHORS @@ -282,3 +282,6 @@ Vassili Tchersky <vt+git@vbc.su> Vassili Tchersky <vt+git@vbcy.org> izxl007 <zeng.zheng@zte.com.cn> Zeno Sebastian Endemann <zeno.endemann@mailbox.org> + +# New authors since fuse-3.18.1 +Abhinav Agarwal <abhinavagarwal1996@gmail.com> diff --git a/ChangeLog.rst b/ChangeLog.rst index b5e8402c7..9a364b518 100644 --- a/ChangeLog.rst +++ b/ChangeLog.rst @@ -1,3 +1,9 @@ +libfuse 3.18.2 (2026-03-18) +=========================== +* Fix two io-uring issues that might be security critical + * fuse-io-uring: Fix UAF and NULL deref in startup error path + * fuse-io-uring: Fix NULL deref and memory leak in fuse_uring_init_queue + libfuse 3.18.1 (2025-12-20) =========================== * Fix a critical ABI issue compared to libfuse-3.17.3+ diff --git a/meson.build b/meson.build index 4eb6c0032..f0570dc2c 100644 --- a/meson.build +++ b/meson.build @@ -1,5 +1,5 @@ project('libfuse3', ['c'], - version: '3.18.1', + version: '3.18.2', meson_version: '>= 0.60.0', default_options: [ 'buildtype=debugoptimized',