summaryrefslogtreecommitdiffstats
path: root/tools
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-09 13:16:35 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-09 13:16:35 +0000
commite2bbf175a2184bd76f6c54ccf8456babeb1a46fc (patch)
treef0b76550d6e6f500ada964a3a4ee933a45e5a6f1 /tools
parentInitial commit. (diff)
downloadfrr-e2bbf175a2184bd76f6c54ccf8456babeb1a46fc.tar.xz
frr-e2bbf175a2184bd76f6c54ccf8456babeb1a46fc.zip
Adding upstream version 9.1.upstream/9.1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
-rw-r--r--tools/.gitignore11
-rw-r--r--tools/Makefile10
-rwxr-xr-xtools/build-debian-package.sh76
-rwxr-xr-xtools/checkpatch.pl7708
-rwxr-xr-xtools/checkpatch.sh107
-rw-r--r--tools/cocci.h124
-rw-r--r--tools/coccinelle/README.md14
-rw-r--r--tools/coccinelle/__func__.cocci10
-rw-r--r--tools/coccinelle/alloc_cast.cocci101
-rw-r--r--tools/coccinelle/argv_find.cocci23
-rw-r--r--tools/coccinelle/array_size.cocci83
-rw-r--r--tools/coccinelle/badty.cocci76
-rw-r--r--tools/coccinelle/badzero.cocci238
-rw-r--r--tools/coccinelle/bool_assignment.cocci13
-rw-r--r--tools/coccinelle/bool_expression.cocci29
-rw-r--r--tools/coccinelle/bool_function.cocci21
-rw-r--r--tools/coccinelle/bool_function_type.cocci19
-rw-r--r--tools/coccinelle/boolconv.cocci90
-rw-r--r--tools/coccinelle/boolinit.cocci194
-rw-r--r--tools/coccinelle/boolreturn.cocci59
-rw-r--r--tools/coccinelle/cast_to_larger_sizes.cocci20
-rw-r--r--tools/coccinelle/cond_no_effect.cocci64
-rw-r--r--tools/coccinelle/ctype_cast.cocci11
-rw-r--r--tools/coccinelle/deref_null.cocci282
-rw-r--r--tools/coccinelle/double_lock.cocci92
-rw-r--r--tools/coccinelle/doublebitand.cocci54
-rw-r--r--tools/coccinelle/doubleinit.cocci53
-rw-r--r--tools/coccinelle/doubletest.cocci58
-rw-r--r--tools/coccinelle/frr_with_mutex.cocci23
-rw-r--r--tools/coccinelle/hash_compare_null_values_check.cocci20
-rw-r--r--tools/coccinelle/hash_const.cocci76
-rw-r--r--tools/coccinelle/ifaddr.cocci34
-rw-r--r--tools/coccinelle/ifnullxfree.cocci15
-rw-r--r--tools/coccinelle/int_to_bool_function.cocci24
-rw-r--r--tools/coccinelle/itnull.cocci94
-rw-r--r--tools/coccinelle/json_object_add_camel_case.cocci19
-rw-r--r--tools/coccinelle/json_object_string_addf_inet_ntop.cocci19
-rw-r--r--tools/coccinelle/json_object_string_addf_prefix2str.cocci16
-rw-r--r--tools/coccinelle/memset.cocci21
-rw-r--r--tools/coccinelle/mini_lock.cocci98
-rw-r--r--tools/coccinelle/nb-cbs.cocci298
-rw-r--r--tools/coccinelle/noderef.cocci81
-rw-r--r--tools/coccinelle/replace-strncpy.cocci8
-rw-r--r--tools/coccinelle/replace_bgp_flag_functions.cocci14
-rw-r--r--tools/coccinelle/return_without_parenthesis.cocci9
-rw-r--r--tools/coccinelle/returnvar.cocci66
-rw-r--r--tools/coccinelle/route_map_apply.cocci15
-rw-r--r--tools/coccinelle/s_addr_0_to_INADDR_ANY.cocci14
-rw-r--r--tools/coccinelle/same_type_casting.cocci7
-rw-r--r--tools/coccinelle/semicolon.cocci83
-rw-r--r--tools/coccinelle/shorthand_operator.cocci12
-rw-r--r--tools/coccinelle/strncpy_truncation.cocci41
-rw-r--r--tools/coccinelle/struct_thread_double_pointer.cocci35
-rw-r--r--tools/coccinelle/struct_thread_null.cocci9
-rw-r--r--tools/coccinelle/test_after_assert.cocci7
-rw-r--r--tools/coccinelle/thread_cancel_api.cocci68
-rw-r--r--tools/coccinelle/unsigned_lesser_than_zero.cocci75
-rw-r--r--tools/coccinelle/void_no_return.cocci9
-rw-r--r--tools/coccinelle/vty_check.cocci22
-rw-r--r--tools/coccinelle/vty_index.cocci244
-rw-r--r--tools/coccinelle/vty_json.cocci10
-rw-r--r--tools/coccinelle/xcalloc-simple.cocci52
-rw-r--r--tools/coccinelle/xcalloc-xmalloc.cocci17
-rw-r--r--tools/coccinelle/xfree.cocci122
-rw-r--r--tools/coccinelle/xfreeaddr.cocci33
-rw-r--r--tools/coccinelle/xmalloc_returnval.cocci37
-rw-r--r--tools/coccinelle/zlog_no_newline.cocci20
-rw-r--r--tools/coccinelle/zprivs.cocci76
-rwxr-xr-xtools/convert-fixedwidth.sh44
-rw-r--r--tools/emacs.dir-locals.el8
-rw-r--r--tools/etc/frr/daemons126
-rw-r--r--tools/etc/frr/daemons.conf1
-rw-r--r--tools/etc/frr/frr.conf10
-rw-r--r--tools/etc/frr/support_bundle_commands.conf259
-rw-r--r--tools/etc/frr/vtysh.conf1
-rw-r--r--tools/etc/iproute2/rt_protos.d/frr.conf14
-rw-r--r--tools/etc/logrotate.d/frr27
-rw-r--r--tools/etc/rsyslog.d/45-frr.conf48
-rwxr-xr-xtools/fixup-deprecated.py82
-rw-r--r--tools/frr-llvm-cg.c950
-rw-r--r--tools/frr-llvm-debuginfo.cpp89
-rw-r--r--tools/frr-llvm-debuginfo.h24
-rwxr-xr-xtools/frr-reload7
-rwxr-xr-xtools/frr-reload.py2275
-rwxr-xr-xtools/frr.in612
-rw-r--r--tools/frr.service.in26
-rw-r--r--tools/frr.vim78
-rw-r--r--tools/frr@.service.in26
-rwxr-xr-xtools/frr_babeltrace.py253
-rwxr-xr-xtools/frrcommon.sh.in385
-rw-r--r--tools/frrinit.sh.in135
-rw-r--r--tools/gcc-plugins/.gitignore7
-rw-r--r--tools/gcc-plugins/Makefile23
-rw-r--r--tools/gcc-plugins/README.md102
-rw-r--r--tools/gcc-plugins/debian/changelog11
-rw-r--r--tools/gcc-plugins/debian/compat1
-rw-r--r--tools/gcc-plugins/debian/control19
-rw-r--r--tools/gcc-plugins/debian/copyright9
-rwxr-xr-xtools/gcc-plugins/debian/rules11
-rw-r--r--tools/gcc-plugins/debian/source/format1
-rw-r--r--tools/gcc-plugins/format-test.c113
-rw-r--r--tools/gcc-plugins/format-test.py74
-rw-r--r--tools/gcc-plugins/frr-format.c4468
-rw-r--r--tools/gcc-plugins/frr-format.h352
-rw-r--r--tools/gcc-plugins/gcc-common.h994
-rw-r--r--tools/gcc-plugins/gcc-retain-typeinfo.patch11
-rw-r--r--tools/gen_northbound_callbacks.c390
-rw-r--r--tools/gen_yang_deviations.c67
-rwxr-xr-xtools/generate_support_bundle.py86
-rw-r--r--tools/git-reindent-branch.py104
-rwxr-xr-xtools/indent.py53
-rw-r--r--tools/lua.scr29
-rw-r--r--tools/mrlg.txt5
-rw-r--r--tools/multiple-bgpd.sh84
-rwxr-xr-xtools/nhrpd-event-handler.sh203
-rw-r--r--tools/permutations.c96
-rwxr-xr-xtools/release_notes.py94
-rwxr-xr-xtools/releasedate.py61
-rw-r--r--tools/render_md.py31
-rw-r--r--tools/rrcheck.pl135
-rw-r--r--tools/rrlookup.pl123
-rw-r--r--tools/start-stop-daemon.c1062
-rw-r--r--tools/stringmangle.py62
-rw-r--r--tools/subdir.am69
-rw-r--r--tools/symalyzer.html348
-rwxr-xr-xtools/symalyzer.py407
-rw-r--r--tools/valgrind.supp88
-rwxr-xr-xtools/vty_index.sh21
-rw-r--r--tools/watchfrr.sh.in33
-rwxr-xr-xtools/zc.pl95
-rw-r--r--tools/zebra.el108
131 files changed, 27113 insertions, 0 deletions
diff --git a/tools/.gitignore b/tools/.gitignore
new file mode 100644
index 0000000..1cc343a
--- /dev/null
+++ b/tools/.gitignore
@@ -0,0 +1,11 @@
+/frr
+/gen_northbound_callbacks
+/gen_yang_deviations
+/permutations
+/ssd
+/watchfrr.sh
+/frrinit.sh
+/frrcommon.sh
+/frr.service
+/frr@.service
+/frr-llvm-cg
diff --git a/tools/Makefile b/tools/Makefile
new file mode 100644
index 0000000..0614da7
--- /dev/null
+++ b/tools/Makefile
@@ -0,0 +1,10 @@
+all: ALWAYS
+ @echo please specify a target to build
+%: ALWAYS
+ @$(MAKE) -s -C .. tools/$@
+
+Makefile:
+ #nothing
+ALWAYS:
+.PHONY: ALWAYS makefiles
+.SUFFIXES:
diff --git a/tools/build-debian-package.sh b/tools/build-debian-package.sh
new file mode 100755
index 0000000..d614ba0
--- /dev/null
+++ b/tools/build-debian-package.sh
@@ -0,0 +1,76 @@
+#!/bin/sh
+# SPDX-License-Identifier: NONE
+#
+# Written by Daniil Baturin, 2018
+# Rewritten by Ondřej Surý, 2020
+# This file is public domain
+set -e
+
+cd "$(dirname "$0")/.."
+
+#
+# Checking requirements
+#
+
+if [ "$(id -u)" = 0 ]; then
+ echo "Running as root - installing dependencies"
+ apt-get install fakeroot debhelper devscripts git-buildpackage lsb-release
+ mk-build-deps --install debian/control
+ exit 0
+fi
+
+git diff-index --quiet HEAD || echo "Warning: git working directory is not clean!"
+
+############################
+# Build the Debian package #
+############################
+
+#
+# Now we will construct an "upstream" version out of:
+# 1. version in AC_INIT
+# 2. the unix time from the last commit (HEAD)
+# (alternatively %Y%m%d%H%M%S could be used here)
+# 4. Debian version (always -1)
+#
+
+UPSTREAM_VERSION=$(sed -ne 's/AC_INIT(\[frr\],\s\[\([^]]*\)\],.*/\1/p' configure.ac | sed -e 's/-\(\(dev\|alpha\|beta\)\d*\)/~\1/')
+LAST_TIMESTAMP=$(git log --format=format:%ad --date=format:%s -1 "HEAD")
+DEBIAN_VERSION="$UPSTREAM_VERSION-$LAST_TIMESTAMP-1"
+DEBIAN_BRANCH=$(git rev-parse --abbrev-ref HEAD)
+
+#
+# We add a Debian changelog entry, and use artifical "since commit"
+# so there's not a whole git history in the debian/changelog.
+#
+# The --snapshot option appends ~1.<shorthash> to the debian version, so for the
+# release build, this needs to be replaces with --release
+#
+
+echo "Adding new snapshot debian/changelog entry for $DEBIAN_VERSION..."
+
+gbp dch \
+ --debian-branch="$DEBIAN_BRANCH" \
+ --new-version="$DEBIAN_VERSION" \
+ --dch-opt="--force-bad-version" \
+ --since="HEAD~" \
+ --snapshot \
+ --commit \
+ --git-author
+
+echo "Building package..."
+
+#
+# git-buildpackage will use $BUILDER command to just build new binary package
+#
+
+BUILDER="dpkg-buildpackage -uc -us --build=binary --no-check-builddeps --no-pre-clean -sa"
+UPSTREAM_COMPRESSION=xz
+
+gbp buildpackage \
+ --git-export-dir="$WORKDIR" \
+ --git-builder="$BUILDER" \
+ --git-debian-branch="$DEBIAN_BRANCH" \
+ --git-force-create \
+ --git-compression=$UPSTREAM_COMPRESSION \
+ --git-no-pristine-tar \
+ --git-ignore-new
diff --git a/tools/checkpatch.pl b/tools/checkpatch.pl
new file mode 100755
index 0000000..d007c1d
--- /dev/null
+++ b/tools/checkpatch.pl
@@ -0,0 +1,7708 @@
+#!/usr/bin/env perl
+# SPDX-License-Identifier: GPL-2.0-or-later
+#
+# (c) 2001, Dave Jones. (the file handling bit)
+# (c) 2005, Joel Schopp <jschopp@austin.ibm.com> (the ugly bit)
+# (c) 2007,2008, Andy Whitcroft <apw@uk.ibm.com> (new conditions, test suite)
+# (c) 2008-2010 Andy Whitcroft <apw@canonical.com>
+# (c) 2010-2018 Joe Perches <joe@perches.com>
+
+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 $frr = 1;
+
+my $quiet = 0;
+my $verbose = 0;
+my %verbose_messages = ();
+my %verbose_emitted = ();
+my $tree = 1;
+my $chk_signoff = 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/../doc/developer/checkpatch.rst";
+my $typedefsfile;
+my $color = "auto";
+my $allow_c99_comments = 0; # Not in FRR.
+#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_";
+
+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
+ --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:
+ <rev>
+ <rev>^
+ <rev>~n
+ multiple git commits with:
+ <rev1>..<rev2>
+ <rev1>...<rev2>
+ <rev>-<count>
+ 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
+ "<inputfile>.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,
+ '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: <codespell_root>/data/dictionary.txt
+ if (($codespell || $help) && which("codespell") ne "" && which("python") 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 = `python -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{
+ _Atomic|
+ 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|
+ ____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 $Iterators = qr{
+ frr_each|frr_each_safe|frr_each_from|
+ frr_with_mutex|frr_with_privs|
+ LIST_FOREACH|LIST_FOREACH_SAFE|
+ SLIST_FOREACH|SLIST_FOREACH_SAFE|SLIST_FOREACH_PREVPTR|
+ STAILQ_FOREACH|STAILQ_FOREACH_SAFE|
+ TAILQ_FOREACH|TAILQ_FOREACH_SAFE|TAILQ_FOREACH_REVERSE|TAILQ_FOREACH_REVERSE_SAFE|
+ RB_FOREACH|RB_FOREACH_SAFE|RB_FOREACH_REVERSE|RB_FOREACH_REVERSE_SAFE|
+ SPLAY_FOREACH|
+ FOR_ALL_INTERFACES|FOR_ALL_INTERFACES_ADDRESSES|JSON_FOREACH|
+ LY_FOR_KEYS|LY_LIST_FOR|LY_TREE_FOR|LY_TREE_DFS_BEGIN|LYD_TREE_DFS_BEGIN|
+ RE_DEST_FOREACH_ROUTE|RE_DEST_FOREACH_ROUTE_SAFE|
+ RNODE_FOREACH_RE|RNODE_FOREACH_RE_SAFE|
+ UPDGRP_FOREACH_SUBGRP|UPDGRP_FOREACH_SUBGRP_SAFE|
+ SUBGRP_FOREACH_PEER|SUBGRP_FOREACH_PEER_SAFE|
+ SUBGRP_FOREACH_ADJ|SUBGRP_FOREACH_ADJ_SAFE|
+ AF_FOREACH|FOREACH_AFI_SAFI|FOREACH_SAFI|
+ LSDB_LOOP
+ }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 $typeTypedefs = qr{(?x:
+ $typeC99Typedefs\b|
+ $typeOtherOSTypedefs\b|
+ $typeKernelTypedefs\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 $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 @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",
+);
+
+#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);
+} elsif (!$frr) {
+ 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 (!$frr &&
+ 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*\(
+)};
+
+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);
+
+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", "configure.ac", "Makefile.am",
+ "README.md", "doc", "lib", "vtysh", "watchfrr", "tests",
+ "zebra", "bgpd", "ospfd", "ospf6d", "isisd", "staticd",
+
+ );
+
+ 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 <joe@smith.com" bad
+ if ($name ne "" && $address !~ /^<[^>]+>$/) {
+ $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/(?<!\\)"/\\"/g; ##escape quotes
+ $name = "\"$name\"";
+ }
+
+ return ($name, $name_comment, $address, $comment);
+}
+
+sub format_email {
+ my ($name, $name_comment, $address, $comment) = @_;
+
+ my $formatted_email;
+
+ $name =~ s/^[ \"]+|[ \"]+$//g;
+ $address = trim($address);
+ $address =~ s/(?:\.|\,|\")+$//; ##trailing commas, dots or quotes
+
+ if ($name =~ /[^\w \-]/i) { ##has "must quote" chars
+ $name =~ s/(?<!\\)"/\\"/g; ##escape quotes
+ $name = "\"$name\"";
+ }
+
+ $name_comment = trim($name_comment);
+ $name_comment = " $name_comment" if ($name_comment ne "");
+ $comment = trim($comment);
+ $comment = " $comment" if ($comment ne "");
+
+ if ("$name" eq "") {
+ $formatted_email = "$address";
+ } else {
+ $formatted_email = "$name$name_comment <$address>";
+ }
+ $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 remove_defuns {
+ my @breakfast = ();
+ my $milktoast;
+ for my $tasty (@rawlines) {
+ $milktoast = $tasty;
+ if (($tasty =~ /^\+DEFPY/ ||
+ $tasty =~ /^\+DEFUN/ ||
+ $tasty =~ /^\+ALIAS/) .. ($tasty =~ /^\+\{/)) {
+ $milktoast = "\n";
+ }
+ push(@breakfast, $milktoast);
+ }
+ @rawlines = @breakfast;
+}
+
+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 $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();
+ remove_defuns();
+
+ 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-<architecture>\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) {
+ # FRR: not used by FRR
+ # 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/" </ </;
+ # Don't force email to have quotes
+ # Allow just an angle bracketed address
+ if (!same_email_addresses($email, $suggested_email)) {
+ if (WARN("BAD_SIGN_OFF",
+ "email address '$email' might be better as '$suggested_email'\n" . $herecurr) &&
+ $fix) {
+ $fixed[$fixlinenr] =~ s/\Q$email\E/$suggested_email/;
+ }
+ }
+
+ # Address part shouldn't have comments
+ my $stripped_address = $email_address;
+ $stripped_address =~ s/\([^\(\)]*\)//g;
+ if ($email_address ne $stripped_address) {
+ if (WARN("BAD_SIGN_OFF",
+ "address part of email should not have comments: '$email_address'\n" . $herecurr) &&
+ $fix) {
+ $fixed[$fixlinenr] =~ s/\Q$email_address\E/$stripped_address/;
+ }
+ }
+
+ # Only one name comment should be allowed
+ my $comment_count = () = $name_comment =~ /\([^\)]+\)/g;
+ if ($comment_count > 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" . "$here\n" . $rawline);
+ }
+ if (!defined $lines[$linenr]) {
+ WARN("BAD_SIGN_OFF",
+ "Co-developed-by: must be immediately followed by Signed-off-by:\n" . "$here\n" . $rawline);
+ } elsif ($rawlines[$linenr] !~ /^\s*signed-off-by:\s*(.*)/i) {
+ WARN("BAD_SIGN_OFF",
+ "Co-developed-by: must be immediately followed by Signed-off-by:\n" . "$here\n" . $rawline . "\n" .$rawlines[$linenr]);
+ } elsif ($1 ne $email) {
+ WARN("BAD_SIGN_OFF",
+ "Co-developed-by and Signed-off-by: name/email do not match \n" . "$here\n" . $rawline . "\n" .$rawlines[$linenr]);
+ }
+ }
+ }
+
+# 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:|$signature_tags)/i ||
+ # A Fixes: or Link: line or signature tag line
+ $commit_log_possible_stack_dump)) {
+ WARN("COMMIT_LOG_LONG_LINE",
+ "Possible unwrapped commit description (prefer a maximum 75 chars per line)\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 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 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 $length = 0;
+ my $cnt = $realcnt;
+ my $ln = $linenr + 1;
+ my $f;
+ my $is_start = 0;
+ my $is_end = 0;
+ for (; $cnt > 0 && defined $lines[$ln - 1]; $ln++) {
+ $f = $lines[$ln - 1];
+ $cnt-- if ($lines[$ln - 1] !~ /^-/);
+ $is_end = $lines[$ln - 1] =~ /^\+/;
+
+ next if ($f =~ /^-/);
+ last if (!$file && $f =~ /^\@\@/);
+
+ if ($lines[$ln - 1] =~ /^\+\s*(?:bool|tristate|prompt)\s*["']/) {
+ $is_start = 1;
+ } elsif ($lines[$ln - 1] =~ /^\+\s*(?:---)?help(?:---)?$/) {
+ $length = -1;
+ }
+
+ $f =~ s/^.//;
+ $f =~ s/#.*//;
+ $f =~ s/^\s+//;
+ next if ($f =~ /^$/);
+
+ # 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 =~ /^\s*(?:config|menuconfig|choice|endchoice|
+ if|endif|menu|endmenu|source)\b/x) {
+ $is_end = 1;
+ last;
+ }
+ $length++;
+ }
+ if ($is_start && $is_end && $length < $min_conf_desc_length) {
+ WARN("CONFIG_DESCRIPTION",
+ "please write a paragraph that describes the config symbol fully\n" . $herecurr);
+ }
+ #print "is_start<$is_start> is_end<$is_end> length<$length>\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 (!$frr &&
+ $realfile =~ /\.(h|s|S)$/) {
+ $comment = '/*';
+ } elsif ($frr &&
+ $realfile =~ /\.(h|s|S)$/) {
+ $comment = '//';
+ } elsif ($realfile =~ /\.(c|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/@ &&
+ not $spdx_license =~ /GPL-2\.0.*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)/;
+ }
+ }
+ }
+ }
+ }
+
+# check for embedded filenames
+ if ($rawline =~ /^\+.*\Q$realfile\E/) {
+ 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|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/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 comment styles
+# Networking with an initial /*
+ if ($realfile =~ m@^(drivers/net/|net/)@ &&
+ $prevrawline =~ /^\+[ \t]*\/\*[ \t]*$/ &&
+ $rawline =~ /^\+[ \t]*\*/ &&
+ $realline > 3) { # Do not warn about the initial copyright comment block after SPDX-License-Identifier
+ WARN("NETWORKING_BLOCK_COMMENT_STYLE",
+ "networking block comments don't use an empty /* line, use /* Comment...\n" . $hereprev);
+ }
+
+# 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/ ||
+ $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_]+|)frr_(each|with)[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_]+|)frr_(each|with)[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 (!$allow_c99_comments && 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 \*/@;
+ }
+ } else {
+ WARN("C99_COMMENTS",
+ "C99 // comments do not match recommendation\n" . $herecurr);
+ }
+ }
+ # 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@;
+ }
+ }
+ }
+
+# avoid BUG() or BUG_ON()
+ if ($line =~ /\b(?:BUG|BUG_ON)\b/) {
+ my $msg_level = \&WARN;
+ $msg_level = \&CHK if ($file);
+ &{$msg_level}("AVOID_BUG",
+ "Avoid crashing the kernel - try using WARN_ON & recovery code rather than BUG() or BUG_ON()\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__|
+ $Iterators)$/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$/) {
+
+ # All-uppercase function names are usually macros,
+ # ignore those
+ } elsif ($name eq uc $name) {
+
+ } 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 ($line=~/\#\s*define/) {
+ # FRR: ignore , spacing in macros
+ } elsif ($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
+ # FRR: has indented labels
+ if (!$frr &&
+ $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);
+
+ 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);
+ }
+ }
+ }
+
+ # 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";
+ }
+
+ ERROR("TRAILING_STATEMENTS",
+ "trailing statements should be on next line\n" . $herecurr . $stat_real);
+ }
+ }
+
+# 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 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{($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;
+ my $complex = 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);
+ $complex = 1;
+ }
+
+ $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 !~ /^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");
+ } elsif ($complex) {
+ 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 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
+
+ } 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);
+ }
+ }
+
+ if (!defined $suppress_ifbraces{$linenr - 1} &&
+ $line =~ /\b(frr_with_)/) {
+ 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 ($level == 0 && $block !~ /^\s*\{/) {
+ my $herectx = $here . "\n";
+ my $cnt = statement_rawlines($block);
+
+ for (my $n = 0; $n < $cnt; $n++) {
+ $herectx .= raw_line($linenr, $n) . "\n";
+ }
+
+ WARN("BRACES",
+ "braces {} are mandatory for frr_with_* 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]?"/) {
+ # FRR: WARN -> CHK
+ if (CHK("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 Documentation/timers/timers-howto.rst\n" . $herecurr);
+ }
+ if ($delay > 2000) {
+ WARN("LONG_UDELAY",
+ "long udelay - prefer mdelay; see arch/arm/include/asm/delay.h\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 Documentation/timers/timers-howto.rst\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
+ # FRR: doesn't use kernel macro replacements for compiler attributes.
+ if (!$frr &&
+ $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 (!$frr &&
+ $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 (!$frr &&
+ $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 (!$frr &&
+ $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 $ext_type = "Invalid";
+ my $use = "";
+ if ($bad_specifier =~ /p[Ff]/) {
+ $use = " - use %pS instead";
+ $use =~ s/pS/ps/ if ($bad_specifier =~ /pf/);
+ }
+
+ WARN("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)/;
+# }
+# }
+# }
+
+# strlcpy uses that should likely be strscpy
+ if (!$frr &&
+ $line =~ /\bstrlcpy\s*\(/) {
+ WARN("STRLCPY",
+ "Prefer strscpy over strlcpy - see: https://lore.kernel.org/r/CAHk-=wgfRnXz0W3D37d01q3JFkr_i_uTL=V6A6G1oUZcprmknw\@mail.gmail.com/\n" . $herecurr);
+ }
+
+# 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 Documentation/timers/timers-howto.rst\n" . "$here\n$stat\n");
+ } elsif ($min =~ /^\d+$/ && $max =~ /^\d+$/ &&
+ $min > $max) {
+ WARN("USLEEP_RANGE",
+ "usleep_range args reversed, use min then max; see Documentation/timers/timers-howto.rst\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 (!$frr &&
+ $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 =~ /^.\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 k[mz]alloc with multiplies that could be kmalloc_array/kcalloc
+ if ($perl_version_ok &&
+ defined $stat &&
+ $stat =~ /^\+\s*($Lval)\s*\=\s*(?:$balanced_parens)?\s*(k[mz]alloc)\s*\(\s*($FuncArg)\s*\*\s*($FuncArg)\s*,/) {
+ my $oldfunc = $3;
+ my $a1 = $4;
+ my $a2 = $10;
+ my $newfunc = "kmalloc_array";
+ $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*(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_)?(?:kcalloc|kmalloc_array))\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 (!$frr &&
+ $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 (!$frr &&
+ 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';
+ }
+ }
+
+# 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);
+ }
+ }
+
+# 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);
+ }
+ }
+
+# 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);
+ }
+
+# check for usage of nonstandard fixed-width integral types
+ if ($line =~ /u_int8_t/ ||
+ $line =~ /u_int32_t/ ||
+ $line =~ /u_int16_t/ ||
+ $line =~ /u_int64_t/ ||
+ $line =~ /[^a-z_]u_char[^a-z_]/ ||
+ $line =~ /[^a-z_]u_short[^a-z_]/ ||
+ $line =~ /[^a-z_]u_int[^a-z_]/ ||
+ $line =~ /[^a-z_]u_long[^a-z_]/) {
+ ERROR("NONSTANDARD_INTEGRAL_TYPES",
+ "Please, no nonstandard integer types in new code.\n" . $herecurr)
+ }
+
+# check for usage of non-32 bit atomics
+ if ($line =~ /_Atomic [u]?int(?!32)[0-9]+_t/) {
+ WARN("NON_32BIT_ATOMIC",
+ "Please, only use 32 bit atomics.\n" . $herecurr);
+ }
+
+# check for use of strcpy()
+ if ($line =~ /\bstrcpy\s*\(.*\)/) {
+ ERROR("STRCPY",
+ "strcpy() is error-prone; please use strlcpy()" . $herecurr);
+ }
+
+# check for use of strncpy()
+ if ($line =~ /\bstrncpy\s*\(.*\)/) {
+ WARN("STRNCPY",
+ "strncpy() is error-prone; please use strlcpy() if possible, or memcpy()" . $herecurr);
+ }
+
+# check for use of strcat()
+ if ($line =~ /\bstrcat\s*\(.*\)/) {
+ ERROR("STRCAT",
+ "strcat() is error-prone; please use strlcat() if possible" . $herecurr);
+ }
+
+# check for use of strncat()
+ if ($line =~ /\bstrncat\s*\(.*\)/) {
+ WARN("STRNCAT",
+ "strncat() is error-prone; please use strlcat() if possible" . $herecurr);
+ }
+
+# check for use of bzero()
+ if ($line =~ /\bbzero\s*\(.*\)/) {
+ ERROR("BZERO",
+ "bzero() is deprecated; use memset()" . $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_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;
+}
diff --git a/tools/checkpatch.sh b/tools/checkpatch.sh
new file mode 100755
index 0000000..6071f48
--- /dev/null
+++ b/tools/checkpatch.sh
@@ -0,0 +1,107 @@
+#!/bin/bash
+# Check a patch for style errors.
+usage="./checkpatch.sh <patch> <tree>"
+patch=$1
+tree=$2
+checkpatch="$tree/tools/checkpatch.pl --no-tree -f"
+ignore="ldpd\|babeld"
+cwd=${PWD##*/}
+dirty=0
+stat=0
+tmp1=/tmp/f1-$$
+tmp2=/tmp/f2-$$
+
+if [[ -z "$1" || -z "$2" ]]; then
+ echo "$usage"
+ exit 0
+fi
+
+# remove temp directories
+rm -rf ${tmp1} ${tmp2}
+
+# save working tree
+if git -C $tree status --porcelain | egrep --silent '^(\?\?|.[DM])'; then
+ echo "Detected dirty tree, caching state..."
+ dirty=1
+ git -C $tree config gc.auto 0;
+ td=$(git -C $tree status -z | grep -z "^[ARM]D" | cut -z -d' ' -f2- | tr '\0' '\n')
+ INDEX=$(git -C $tree write-tree)
+ git -C $tree add -f .
+ WORKTREE=$(git -C $tree write-tree)
+ echo "Saved index to $INDEX"
+ echo "Saved working tree to $WORKTREE"
+fi
+
+# double check
+if git -C $tree status --porcelain | egrep --silent '^(\?\?|.[DM])'; then
+ echo "[!] git working directory must be clean."
+ exit 1
+fi
+
+git -C $tree reset --hard
+git -C $tree apply < $patch
+mkdir -p ${tmp1} ${tmp2}
+mod=$(git -C $tree ls-files -m | grep ".*\.[ch]" | grep -v $ignore)
+mod+=" $(git -C $tree ls-files --others --exclude-standard | grep '.*\.[ch]' | grep -v $ignore)"
+echo $mod
+if [ -z "$mod" ]; then
+ echo "There doesn't seem to be any changes."
+else
+ echo "Copying source to temp directory..."
+ for file in $mod; do
+ echo "$tree/$file --> ${tmp1}/$file"
+ cp $tree/$file ${tmp1}/
+ done
+ git -C $tree reset --hard
+ git -C $tree clean -fd
+ for file in $mod; do
+ if [ -f $tree/$file ]; then
+ echo "$tree/$file --> ${tmp2}/$file"
+ cp $tree/$file ${tmp2}/
+ fi
+ done
+ echo "Running style checks..."
+ for file in ${tmp1}/*; do
+ echo "$checkpatch $file > $file _cp"
+ $checkpatch $file > "$file"_cp 2> /dev/null
+ done
+ for file in ${tmp2}/*; do
+ echo "$checkpatch $file > $file _cp"
+ $checkpatch $file > "$file"_cp 2> /dev/null
+ done
+ echo "Done."
+ for file in ${tmp1}/*_cp; do
+ if [ -a ${tmp2}/$(basename $file) ]; then
+ result=$(diff $file ${tmp2}/$(basename $file) | awk '/< ERROR|< WARNING/,/^< $|^< #|^<[^ ]/ { print $0; ++n }; END { exit n }')
+ else
+ result=$(cat $file | awk '/ERROR|WARNING/,/^$/ { print $0; ++n }; END { exit n }')
+ fi
+ ni="$?"
+ if [ "$ni" -ne "0" ]; then
+ echo "Report for $(basename $file _cp) | $ni issues" 1>&2
+ echo "===============================================" 1>&2
+ echo "$result" 1>&2
+ if echo $result | grep -q "ERROR"; then
+ stat=2
+ elif [ "$stat" -eq "0" ]; then
+ stat=1
+ fi
+ fi
+ done
+fi
+
+# restore working tree
+if [ $dirty -eq 1 ]; then
+ git -C $tree read-tree $WORKTREE;
+ git -C $tree checkout-index -af;
+ git -C $tree read-tree $INDEX;
+ if [ -n "$td" ]; then
+ rm $td
+ fi
+ git -C $tree config --unset gc.auto;
+fi
+
+# remove temp directories
+rm -rf ${tmp1} ${tmp2}
+
+exit $stat
diff --git a/tools/cocci.h b/tools/cocci.h
new file mode 100644
index 0000000..7d6bb4c
--- /dev/null
+++ b/tools/cocci.h
@@ -0,0 +1,124 @@
+/* some of this stuff doesn't seem to parse properly in coccinelle
+ */
+
+#define DEFUN(funcname, cmdname, str, help) \
+ static int funcname(const struct cmd_element *self, struct vty *vty, \
+ int argc, struct cmd_token *argv[])
+#define DEFUN_HIDDEN(funcname, cmdname, str, help) \
+ static int funcname(const struct cmd_element *self, struct vty *vty, \
+ int argc, struct cmd_token *argv[])
+#define DEFUN_NOSH(funcname, cmdname, str, help) \
+ static int funcname(const struct cmd_element *self, struct vty *vty, \
+ int argc, struct cmd_token *argv[])
+#define DEFPY(funcname, cmdname, str, help) \
+ static int funcname(const struct cmd_element *self, struct vty *vty, \
+ int argc, struct cmd_token *argv[])
+#define DEFPY_HIDDEN(funcname, cmdname, str, help) \
+ static int funcname(const struct cmd_element *self, struct vty *vty, \
+ int argc, struct cmd_token *argv[])
+#define DEFPY_NOSH(funcname, cmdname, str, help) \
+ static int funcname(const struct cmd_element *self, struct vty *vty, \
+ int argc, struct cmd_token *argv[])
+
+#define ENABLE_BGP_VNC 1
+#define ALL_LIST_ELEMENTS_RO(list, node, data) \
+ (node) = listhead(list), ((data) = NULL); \
+ (node) != NULL && ((data) = listgetdata(node)); \
+ (node) = listnextnode(node), ((data) = NULL)
+#define ALL_LIST_ELEMENTS(list, node, nextnode, data) \
+ (node) = listhead(list), ((data) = NULL); \
+ (node) != NULL \
+ && ((data) = listgetdata(node), (nextnode) = node->next); \
+ (node) = (nextnode), ((data) = NULL)
+#define LIST_HEAD(name, type) \
+ struct name { \
+ struct type *lh_first; /* first element */ \
+ }
+#define LIST_ENTRY(type) \
+ struct { \
+ struct type *le_next; /* next element */ \
+ struct type **le_prev; /* address of previous next element */ \
+ }
+
+#define STREAM_GETC(S, P) \
+ do { \
+ uint8_t _pval; \
+ if (!stream_getc2((S), &_pval)) \
+ goto stream_failure; \
+ (P) = _pval; \
+ } while (0)
+
+#define STREAM_GETW(S, P) \
+ do { \
+ uint16_t _pval; \
+ if (!stream_getw2((S), &_pval)) \
+ goto stream_failure; \
+ (P) = _pval; \
+ } while (0)
+
+#define STREAM_GETL(S, P) \
+ do { \
+ uint32_t _pval; \
+ if (!stream_getl2((S), &_pval)) \
+ goto stream_failure; \
+ (P) = _pval; \
+ } while (0)
+
+#define STREAM_GETF(S, P) \
+ do { \
+ union { \
+ float r; \
+ uint32_t d; \
+ } _pval; \
+ if (stream_getl2((S), &_pval.d)) \
+ goto stream_failure; \
+ (P) = _pval.r; \
+ } while (0)
+
+#define STREAM_GETQ(S, P) \
+ do { \
+ uint64_t _pval; \
+ if (!stream_getq2((S), &_pval)) \
+ goto stream_failure; \
+ (P) = _pval; \
+ } while (0)
+
+#define STREAM_GET(P, STR, SIZE) \
+ do { \
+ if (!stream_get2((P), (STR), (SIZE))) \
+ goto stream_failure; \
+ } while (0)
+
+#define AF_FOREACH(af) for ((af) = BGP_AF_START; (af) < BGP_AF_MAX; (af)++)
+
+#define FOREACH_AFI_SAFI(afi, safi) \
+ \
+ for (afi = AFI_IP; afi < AFI_MAX; afi++) \
+ for (safi = SAFI_UNICAST; safi < SAFI_MAX; safi++)
+
+#define FOREACH_SAFI(safi) for (safi = SAFI_UNICAST; safi < SAFI_MAX; safi++)
+
+#define frr_with_privs(p) \
+ for (int x = 1; x; x--)
+#define frr_with_mutex(m) \
+ for (int x = 1; x; x--)
+
+#define ALL_LSDB_TYPED_ADVRTR(lsdb, type, adv_router, lsa) \
+ const struct route_node *iterend = \
+ ospf6_lsdb_head(lsdb, 2, type, adv_router, &lsa); \
+ lsa; \
+ lsa = ospf6_lsdb_next(iterend, lsa)
+
+#define ALL_LSDB_TYPED(lsdb, type, lsa) \
+ const struct route_node *iterend = \
+ ospf6_lsdb_head(lsdb, 1, type, 0, &lsa); \
+ lsa; \
+ lsa = ospf6_lsdb_next(iterend, lsa)
+
+#define ALL_LSDB(lsdb, lsa) \
+ const struct route_node *iterend = \
+ ospf6_lsdb_head(lsdb, 0, 0, 0, &lsa); \
+ lsa; \
+ lsa = ospf6_lsdb_next(iterend, lsa)
+
+#define QOBJ_FIELDS struct qobj_node qobj_node;
diff --git a/tools/coccinelle/README.md b/tools/coccinelle/README.md
new file mode 100644
index 0000000..262ccc1
--- /dev/null
+++ b/tools/coccinelle/README.md
@@ -0,0 +1,14 @@
+Coccinelle patches
+==================
+
+This collection of coccinelle patches represents some of the broader,
+codebase-wide changes that have been made. If you maintain a fork of
+FRR and find that your codebase needs to be updated to align with
+these changes, the coccinelle tool should help you make that update.
+
+The coccinelle tool is documented at:
+ https://coccinelle.gitlabpages.inria.fr/website/
+
+To run a coccinelle patch script:
+
+ spatch --sp-file tools/coccinelle/semicolon.cocci zebra/*.c
diff --git a/tools/coccinelle/__func__.cocci b/tools/coccinelle/__func__.cocci
new file mode 100644
index 0000000..fb68494
--- /dev/null
+++ b/tools/coccinelle/__func__.cocci
@@ -0,0 +1,10 @@
+@@
+@@
+
+(
+- __PRETTY_FUNCTION__
++ __func__
+|
+- __FUNCTION__
++ __func__
+)
diff --git a/tools/coccinelle/alloc_cast.cocci b/tools/coccinelle/alloc_cast.cocci
new file mode 100644
index 0000000..b1497c7
--- /dev/null
+++ b/tools/coccinelle/alloc_cast.cocci
@@ -0,0 +1,101 @@
+/// Remove casting the values returned by memory allocation functions
+/// like XMALLOC and XCALLOC.
+///
+// This makes an effort to find cases of casting of values returned by #
+// XMALLOC and XCALLOC and removes the casting as it is not required. The
+// result in the patch case may need some reformatting.
+//
+// Confidence: High
+// Copyright: (C) 2014 Himangi Saraogi GPLv2.
+// Copyright: (C) 2017 Himanshu Jha GPLv2.
+// Copyright: (C) 2019 Quentin Young GPLv2.
+// Comments:
+// Options: --no-includes --include-headers
+//
+
+virtual context
+virtual patch
+virtual org
+virtual report
+
+@initialize:python@
+@@
+import re
+pattern = '__'
+m = re.compile(pattern)
+
+@r1 depends on context || patch@
+type T;
+@@
+
+ (T *)
+ \(XMALLOC\|XCALLOC\)(...)
+
+//----------------------------------------------------------
+// For context mode
+//----------------------------------------------------------
+
+@script:python depends on context@
+t << r1.T;
+@@
+
+if m.search(t) != None:
+ cocci.include_match(False)
+
+@depends on context && r1@
+type r1.T;
+@@
+
+* (T *)
+ \(XMALLOC\|XCALLOC\)(...)
+
+//----------------------------------------------------------
+// For patch mode
+//----------------------------------------------------------
+
+@script:python depends on patch@
+t << r1.T;
+@@
+
+if m.search(t) != None:
+ cocci.include_match(False)
+
+@depends on patch && r1@
+type r1.T;
+@@
+
+- (T *)
+ \(XMALLOC\|XCALLOC\)(...)
+
+//----------------------------------------------------------
+// For org and report mode
+//----------------------------------------------------------
+
+@r2 depends on org || report@
+type T;
+position p;
+@@
+
+ (T@p *)
+ \(XMALLOC\|XCALLOC\)(...)
+
+@script:python depends on org@
+p << r2.p;
+t << r2.T;
+@@
+
+if m.search(t) != None:
+ cocci.include_match(False)
+else:
+ coccilib.org.print_safe_todo(p[0], t)
+
+@script:python depends on report@
+p << r2.p;
+t << r2.T;
+@@
+
+if m.search(t) != None:
+ cocci.include_match(False)
+else:
+ msg="WARNING: casting value returned by memory allocation function to (%s *) is useless." % (t)
+ coccilib.report.print_report(p[0], msg)
diff --git a/tools/coccinelle/argv_find.cocci b/tools/coccinelle/argv_find.cocci
new file mode 100644
index 0000000..1ab19b7
--- /dev/null
+++ b/tools/coccinelle/argv_find.cocci
@@ -0,0 +1,23 @@
+@@
+identifier idx;
+identifier argv;
+identifier argc;
+expression e1;
+expression e2;
+identifier I;
+@@
+
+(
+- argv_find(argv, argc, e1, &idx);
+ if (
+- idx
++ argv_find(argv, argc, e1, &idx)
+ )
+ {
+ e2;
+ }
+|
+- argv_find(argv, argc, e1, &idx);
+... when != I = idx;
+ when strict
+)
diff --git a/tools/coccinelle/array_size.cocci b/tools/coccinelle/array_size.cocci
new file mode 100644
index 0000000..f977b8a
--- /dev/null
+++ b/tools/coccinelle/array_size.cocci
@@ -0,0 +1,83 @@
+/// Use array_size instead of dividing sizeof array with sizeof an element
+///
+//# This makes an effort to find cases where array_size can be used such as
+//# where there is a division of sizeof the array by the sizeof its first
+//# element or by any indexed element or the element type. It replaces the
+//# division of the two sizeofs by array_size.
+//
+// Confidence: High
+// Copyright: (C) 2014 Himangi Saraogi. GPLv2.
+// Copyright: (C) 2019 Quentin Young. GPLv2.
+// Comments:
+// Options: --no-includes --include-headers
+
+virtual patch
+virtual context
+virtual org
+virtual report
+
+//----------------------------------------------------------
+// For context mode
+//----------------------------------------------------------
+
+@depends on context@
+type T;
+T[] E;
+@@
+(
+* (sizeof(E)/sizeof(*E))
+|
+* (sizeof(E)/sizeof(E[...]))
+|
+* (sizeof(E)/sizeof(T))
+)
+
+//----------------------------------------------------------
+// For patch mode
+//----------------------------------------------------------
+
+@depends on patch@
+type T;
+T[] E;
+@@
+(
+- (sizeof(E)/sizeof(*E))
++ array_size(E)
+|
+- (sizeof(E)/sizeof(E[...]))
++ array_size(E)
+|
+- (sizeof(E)/sizeof(T))
++ array_size(E)
+)
+
+//----------------------------------------------------------
+// For org and report mode
+//----------------------------------------------------------
+
+@r depends on (org || report)@
+type T;
+T[] E;
+position p;
+@@
+(
+ (sizeof(E)@p /sizeof(*E))
+|
+ (sizeof(E)@p /sizeof(E[...]))
+|
+ (sizeof(E)@p /sizeof(T))
+)
+
+@script:python depends on org@
+p << r.p;
+@@
+
+coccilib.org.print_todo(p[0], "WARNING should use array_size")
+
+@script:python depends on report@
+p << r.p;
+@@
+
+msg="WARNING: Use array_size"
+coccilib.report.print_report(p[0], msg)
+
diff --git a/tools/coccinelle/badty.cocci b/tools/coccinelle/badty.cocci
new file mode 100644
index 0000000..481cf30
--- /dev/null
+++ b/tools/coccinelle/badty.cocci
@@ -0,0 +1,76 @@
+/// Use ARRAY_SIZE instead of dividing sizeof array with sizeof an element
+///
+//# This makes an effort to find cases where the argument to sizeof is wrong
+//# in memory allocation functions by checking the type of the allocated memory
+//# when it is a double pointer and ensuring the sizeof argument takes a pointer
+//# to the the memory being allocated. There are false positives in cases the
+//# sizeof argument is not used in constructing the return value. The result
+//# may need some reformatting.
+//
+// Confidence: Moderate
+// Copyright: (C) 2014 Himangi Saraogi. GPLv2.
+// Comments:
+// Options:
+
+virtual patch
+virtual context
+virtual org
+virtual report
+
+//----------------------------------------------------------
+// For context mode
+//----------------------------------------------------------
+
+@depends on context disable sizeof_type_expr@
+type T;
+T **x;
+@@
+
+ x =
+ <+...sizeof(
+* T
+ )...+>
+
+//----------------------------------------------------------
+// For patch mode
+//----------------------------------------------------------
+
+@depends on patch disable sizeof_type_expr@
+type T;
+T **x;
+@@
+
+ x =
+ <+...sizeof(
+- T
++ *x
+ )...+>
+
+//----------------------------------------------------------
+// For org and report mode
+//----------------------------------------------------------
+
+@r depends on (org || report) disable sizeof_type_expr@
+type T;
+T **x;
+position p;
+@@
+
+ x =
+ <+...sizeof(
+ T@p
+ )...+>
+
+@script:python depends on org@
+p << r.p;
+@@
+
+coccilib.org.print_todo(p[0], "WARNING sizeof argument should be pointer type, not structure type")
+
+@script:python depends on report@
+p << r.p;
+@@
+
+msg="WARNING: Use correct pointer type argument for sizeof"
+coccilib.report.print_report(p[0], msg)
+
diff --git a/tools/coccinelle/badzero.cocci b/tools/coccinelle/badzero.cocci
new file mode 100644
index 0000000..f597c80
--- /dev/null
+++ b/tools/coccinelle/badzero.cocci
@@ -0,0 +1,238 @@
+/// Compare pointer-typed values to NULL rather than 0
+///
+//# This makes an effort to choose between !x and x == NULL. !x is used
+//# if it has previously been used with the function used to initialize x.
+//# This relies on type information. More type information can be obtained
+//# using the option -all_includes and the option -I to specify an
+//# include path.
+//
+// Confidence: High
+// Copyright: (C) 2012 Julia Lawall, INRIA/LIP6. GPLv2.
+// Copyright: (C) 2012 Gilles Muller, INRIA/LiP6. GPLv2.
+// URL: http://coccinelle.lip6.fr/
+// Requires: 1.0.0
+// Options:
+
+virtual patch
+virtual context
+virtual org
+virtual report
+
+@initialize:ocaml@
+@@
+let negtable = Hashtbl.create 101
+
+@depends on patch@
+expression *E;
+identifier f;
+@@
+
+(
+ (E = f(...)) ==
+- 0
++ NULL
+|
+ (E = f(...)) !=
+- 0
++ NULL
+|
+- 0
++ NULL
+ == (E = f(...))
+|
+- 0
++ NULL
+ != (E = f(...))
+)
+
+
+@t1 depends on !patch@
+expression *E;
+identifier f;
+position p;
+@@
+
+(
+ (E = f(...)) ==
+* 0@p
+|
+ (E = f(...)) !=
+* 0@p
+|
+* 0@p
+ == (E = f(...))
+|
+* 0@p
+ != (E = f(...))
+)
+
+@script:python depends on org@
+p << t1.p;
+@@
+
+coccilib.org.print_todo(p[0], "WARNING comparing pointer to 0")
+
+@script:python depends on report@
+p << t1.p;
+@@
+
+coccilib.report.print_report(p[0], "WARNING comparing pointer to 0")
+
+// Tests of returned values
+
+@s@
+identifier f;
+expression E,E1;
+@@
+
+ E = f(...)
+ ... when != E = E1
+ !E
+
+@script:ocaml depends on s@
+f << s.f;
+@@
+
+try let _ = Hashtbl.find negtable f in ()
+with Not_found -> Hashtbl.add negtable f ()
+
+@ r disable is_zero,isnt_zero exists @
+expression *E;
+identifier f;
+@@
+
+E = f(...)
+...
+(E == 0
+|E != 0
+|0 == E
+|0 != E
+)
+
+@script:ocaml@
+f << r.f;
+@@
+
+try let _ = Hashtbl.find negtable f in ()
+with Not_found -> include_match false
+
+// This rule may lead to inconsistent path problems, if E is defined in two
+// places
+@ depends on patch disable is_zero,isnt_zero @
+expression *E;
+expression E1;
+identifier r.f;
+@@
+
+E = f(...)
+<...
+(
+- E == 0
++ !E
+|
+- E != 0
++ E
+|
+- 0 == E
++ !E
+|
+- 0 != E
++ E
+)
+...>
+?E = E1
+
+@t2 depends on !patch disable is_zero,isnt_zero @
+expression *E;
+expression E1;
+identifier r.f;
+position p1;
+position p2;
+@@
+
+E = f(...)
+<...
+(
+* E == 0@p1
+|
+* E != 0@p2
+|
+* 0@p1 == E
+|
+* 0@p1 != E
+)
+...>
+?E = E1
+
+@script:python depends on org@
+p << t2.p1;
+@@
+
+coccilib.org.print_todo(p[0], "WARNING comparing pointer to 0, suggest !E")
+
+@script:python depends on org@
+p << t2.p2;
+@@
+
+coccilib.org.print_todo(p[0], "WARNING comparing pointer to 0")
+
+@script:python depends on report@
+p << t2.p1;
+@@
+
+coccilib.report.print_report(p[0], "WARNING comparing pointer to 0, suggest !E")
+
+@script:python depends on report@
+p << t2.p2;
+@@
+
+coccilib.report.print_report(p[0], "WARNING comparing pointer to 0")
+
+@ depends on patch disable is_zero,isnt_zero @
+expression *E;
+@@
+
+(
+ E ==
+- 0
++ NULL
+|
+ E !=
+- 0
++ NULL
+|
+- 0
++ NULL
+ == E
+|
+- 0
++ NULL
+ != E
+)
+
+@ t3 depends on !patch disable is_zero,isnt_zero @
+expression *E;
+position p;
+@@
+
+(
+* E == 0@p
+|
+* E != 0@p
+|
+* 0@p == E
+|
+* 0@p != E
+)
+
+@script:python depends on org@
+p << t3.p;
+@@
+
+coccilib.org.print_todo(p[0], "WARNING comparing pointer to 0")
+
+@script:python depends on report@
+p << t3.p;
+@@
+
+coccilib.report.print_report(p[0], "WARNING comparing pointer to 0")
diff --git a/tools/coccinelle/bool_assignment.cocci b/tools/coccinelle/bool_assignment.cocci
new file mode 100644
index 0000000..e6146ea
--- /dev/null
+++ b/tools/coccinelle/bool_assignment.cocci
@@ -0,0 +1,13 @@
+@@
+bool b;
+@@
+
+(
+ b =
+- 0
++ false
+|
+ b =
+- 1
++ true
+)
diff --git a/tools/coccinelle/bool_expression.cocci b/tools/coccinelle/bool_expression.cocci
new file mode 100644
index 0000000..c0c329c
--- /dev/null
+++ b/tools/coccinelle/bool_expression.cocci
@@ -0,0 +1,29 @@
+@@
+bool t;
+@@
+
+(
+- t == true
++ t
+|
+- true == t
++ t
+|
+- t != true
++ !t
+|
+- true != t
++ !t
+|
+- t == false
++ !t
+|
+- false == t
++ !t
+|
+- t != false
++ t
+|
+- false != t
++ t
+)
diff --git a/tools/coccinelle/bool_function.cocci b/tools/coccinelle/bool_function.cocci
new file mode 100644
index 0000000..0328ecf
--- /dev/null
+++ b/tools/coccinelle/bool_function.cocci
@@ -0,0 +1,21 @@
+@@
+identifier fn;
+typedef bool;
+symbol false;
+symbol true;
+@@
+
+bool fn ( ... )
+{
+<...
+return
+(
+- 0
++ false
+|
+- 1
++ true
+)
+ ;
+...>
+}
diff --git a/tools/coccinelle/bool_function_type.cocci b/tools/coccinelle/bool_function_type.cocci
new file mode 100644
index 0000000..71bf4f5
--- /dev/null
+++ b/tools/coccinelle/bool_function_type.cocci
@@ -0,0 +1,19 @@
+@@
+identifier fn;
+typedef bool;
+symbol false;
+symbol true;
+@@
+
+- int
++ bool
+fn (...)
+{
+?...
+return
+(
+ true
+|
+ false
+);
+}
diff --git a/tools/coccinelle/boolconv.cocci b/tools/coccinelle/boolconv.cocci
new file mode 100644
index 0000000..33c464d
--- /dev/null
+++ b/tools/coccinelle/boolconv.cocci
@@ -0,0 +1,90 @@
+/// Remove unneeded conversion to bool
+///
+//# Relational and logical operators evaluate to bool,
+//# explicit conversion is overly verbose and unneeded.
+//
+// Copyright: (C) 2016 Andrew F. Davis <afd@ti.com> GPLv2.
+
+virtual patch
+virtual context
+virtual org
+virtual report
+
+//----------------------------------------------------------
+// For patch mode
+//----------------------------------------------------------
+
+@depends on patch@
+expression A, B;
+symbol true, false;
+@@
+
+(
+ A == B
+|
+ A != B
+|
+ A > B
+|
+ A < B
+|
+ A >= B
+|
+ A <= B
+|
+ A && B
+|
+ A || B
+)
+- ? true : false
+
+//----------------------------------------------------------
+// For context mode
+//----------------------------------------------------------
+
+@r depends on !patch@
+expression A, B;
+symbol true, false;
+position p;
+@@
+
+(
+ A == B
+|
+ A != B
+|
+ A > B
+|
+ A < B
+|
+ A >= B
+|
+ A <= B
+|
+ A && B
+|
+ A || B
+)
+* ? true : false@p
+
+//----------------------------------------------------------
+// For org mode
+//----------------------------------------------------------
+
+@script:python depends on r&&org@
+p << r.p;
+@@
+
+msg = "WARNING: conversion to bool not needed here"
+coccilib.org.print_todo(p[0], msg)
+
+//----------------------------------------------------------
+// For report mode
+//----------------------------------------------------------
+
+@script:python depends on r&&report@
+p << r.p;
+@@
+
+msg = "WARNING: conversion to bool not needed here"
+coccilib.report.print_report(p[0], msg)
diff --git a/tools/coccinelle/boolinit.cocci b/tools/coccinelle/boolinit.cocci
new file mode 100644
index 0000000..aabb581
--- /dev/null
+++ b/tools/coccinelle/boolinit.cocci
@@ -0,0 +1,194 @@
+/// Bool initializations should use true and false. Bool tests don't need
+/// comparisons. Based on contributions from Joe Perches, Rusty Russell
+/// and Bruce W Allan.
+///
+// Confidence: High
+// Copyright: (C) 2012 Julia Lawall, INRIA/LIP6. GPLv2.
+// Copyright: (C) 2012 Gilles Muller, INRIA/LiP6. GPLv2.
+// URL: http://coccinelle.lip6.fr/
+// Options: --include-headers
+
+virtual patch
+virtual context
+virtual org
+virtual report
+
+@boolok@
+symbol true,false;
+@@
+(
+true
+|
+false
+)
+
+@depends on patch@
+bool t;
+@@
+
+(
+- t == true
++ t
+|
+- true == t
++ t
+|
+- t != true
++ !t
+|
+- true != t
++ !t
+|
+- t == false
++ !t
+|
+- false == t
++ !t
+|
+- t != false
++ t
+|
+- false != t
++ t
+)
+
+@depends on patch disable is_zero, isnt_zero@
+bool t;
+@@
+
+(
+- t == 1
++ t
+|
+- t != 1
++ !t
+|
+- t == 0
++ !t
+|
+- t != 0
++ t
+)
+
+@depends on patch && boolok@
+bool b;
+@@
+(
+ b =
+- 0
++ false
+|
+ b =
+- 1
++ true
+)
+
+// ---------------------------------------------------------------------
+
+@r1 depends on !patch@
+bool t;
+position p;
+@@
+
+(
+* t@p == true
+|
+* true == t@p
+|
+* t@p != true
+|
+* true != t@p
+|
+* t@p == false
+|
+* false == t@p
+|
+* t@p != false
+|
+* false != t@p
+)
+
+@r2 depends on !patch disable is_zero, isnt_zero@
+bool t;
+position p;
+@@
+
+(
+* t@p == 1
+|
+* t@p != 1
+|
+* t@p == 0
+|
+* t@p != 0
+)
+
+@r3 depends on !patch && boolok@
+bool b;
+position p1;
+@@
+(
+*b@p1 = 0
+|
+*b@p1 = 1
+)
+
+@r4 depends on !patch@
+bool b;
+position p2;
+identifier i;
+constant c != {0,1};
+@@
+(
+ b = i
+|
+*b@p2 = c
+)
+
+@script:python depends on org@
+p << r1.p;
+@@
+
+cocci.print_main("WARNING: Comparison to bool",p)
+
+@script:python depends on org@
+p << r2.p;
+@@
+
+cocci.print_main("WARNING: Comparison of 0/1 to bool variable",p)
+
+@script:python depends on org@
+p1 << r3.p1;
+@@
+
+cocci.print_main("WARNING: Assignment of 0/1 to bool variable",p1)
+
+@script:python depends on org@
+p2 << r4.p2;
+@@
+
+cocci.print_main("ERROR: Assignment of non-0/1 constant to bool variable",p2)
+
+@script:python depends on report@
+p << r1.p;
+@@
+
+coccilib.report.print_report(p[0],"WARNING: Comparison to bool")
+
+@script:python depends on report@
+p << r2.p;
+@@
+
+coccilib.report.print_report(p[0],"WARNING: Comparison of 0/1 to bool variable")
+
+@script:python depends on report@
+p1 << r3.p1;
+@@
+
+coccilib.report.print_report(p1[0],"WARNING: Assignment of 0/1 to bool variable")
+
+@script:python depends on report@
+p2 << r4.p2;
+@@
+
+coccilib.report.print_report(p2[0],"ERROR: Assignment of non-0/1 constant to bool variable")
diff --git a/tools/coccinelle/boolreturn.cocci b/tools/coccinelle/boolreturn.cocci
new file mode 100644
index 0000000..29d2bf4
--- /dev/null
+++ b/tools/coccinelle/boolreturn.cocci
@@ -0,0 +1,59 @@
+// SPDX-License-Identifier: GPL-2.0
+/// Return statements in functions returning bool should use
+/// true/false instead of 1/0.
+//
+// Confidence: High
+// Options: --no-includes --include-headers
+
+virtual patch
+virtual report
+virtual context
+
+@r1 depends on patch@
+identifier fn;
+typedef bool;
+symbol false;
+symbol true;
+@@
+
+bool fn ( ... )
+{
+<...
+return
+(
+- 0
++ false
+|
+- 1
++ true
+)
+ ;
+...>
+}
+
+@r2 depends on report || context@
+identifier fn;
+position p;
+@@
+
+bool fn ( ... )
+{
+<...
+return
+(
+* 0@p
+|
+* 1@p
+)
+ ;
+...>
+}
+
+
+@script:python depends on report@
+p << r2.p;
+fn << r2.fn;
+@@
+
+msg = "WARNING: return of 0/1 in function '%s' with return type bool" % fn
+coccilib.report.print_report(p[0], msg)
diff --git a/tools/coccinelle/cast_to_larger_sizes.cocci b/tools/coccinelle/cast_to_larger_sizes.cocci
new file mode 100644
index 0000000..d97e1f9
--- /dev/null
+++ b/tools/coccinelle/cast_to_larger_sizes.cocci
@@ -0,0 +1,20 @@
+// spatch -sp_file tools/coccinelle/cast_to_larger_sizes.cocci --recursive-includes ./
+
+@r@
+typedef uint8_t;
+typedef uint16_t;
+typedef uint32_t;
+typedef uint64_t;
+uint8_t *i8;
+position p;
+@@
+
+ \(
+ (uint64_t *) i8@p\|(uint32_t *) i8@p\|(uint16_t *) i8@p
+ \)
+
+@script:python@
+p << r.p;
+@@
+
+coccilib.report.print_report(p[0],"Bad typecast to larger size")
diff --git a/tools/coccinelle/cond_no_effect.cocci b/tools/coccinelle/cond_no_effect.cocci
new file mode 100644
index 0000000..8467dbd
--- /dev/null
+++ b/tools/coccinelle/cond_no_effect.cocci
@@ -0,0 +1,64 @@
+///Find conditions where if and else branch are functionally
+// identical.
+//
+// There can be false positives in cases where the positional
+// information is used (as with lockdep) or where the identity
+// is a placeholder for not yet handled cases.
+// Unfortunately there also seems to be a tendency to use
+// the last if else/else as a "default behavior" - which some
+// might consider a legitimate coding pattern. From discussion
+// on kernelnewbies though it seems that this is not really an
+// accepted pattern and if at all it would need to be commented
+//
+// In the Linux kernel it does not seem to actually report
+// false positives except for those that were documented as
+// being intentional.
+// the two known cases are:
+// arch/sh/kernel/traps_64.c:read_opcode()
+// } else if ((pc & 1) == 0) {
+// /* SHcompact */
+// /* TODO : provide handling for this. We don't really support
+// user-mode SHcompact yet, and for a kernel fault, this would
+// have to come from a module built for SHcompact. */
+// return -EFAULT;
+// } else {
+// /* misaligned */
+// return -EFAULT;
+// }
+// fs/kernfs/file.c:kernfs_fop_open()
+// * Both paths of the branch look the same. They're supposed to
+// * look that way and give @of->mutex different static lockdep keys.
+// */
+// if (has_mmap)
+// mutex_init(&of->mutex);
+// else
+// mutex_init(&of->mutex);
+//
+// All other cases look like bugs or at least lack of documentation
+//
+// Confidence: Moderate
+// Copyright: (C) 2016 Nicholas Mc Guire, OSADL. GPLv2.
+// Comments:
+// Options: --no-includes --include-headers
+
+virtual org
+virtual report
+
+@cond@
+statement S1;
+position p;
+@@
+
+* if@p (...) S1 else S1
+
+@script:python depends on org@
+p << cond.p;
+@@
+
+cocci.print_main("WARNING: possible condition with no effect (if == else)",p)
+
+@script:python depends on report@
+p << cond.p;
+@@
+
+coccilib.report.print_report(p[0],"WARNING: possible condition with no effect (if == else)")
diff --git a/tools/coccinelle/ctype_cast.cocci b/tools/coccinelle/ctype_cast.cocci
new file mode 100644
index 0000000..b0b0095
--- /dev/null
+++ b/tools/coccinelle/ctype_cast.cocci
@@ -0,0 +1,11 @@
+
+@@
+identifier func =~ "^(to|is)(alnum|cntrl|print|xdigit|alpha|digit|punct|ascii|graph|space|blank|lower|upper)$";
+expression e;
+@@
+
+ func(
+- (int)
++ (unsigned char)
+ e)
+
diff --git a/tools/coccinelle/deref_null.cocci b/tools/coccinelle/deref_null.cocci
new file mode 100644
index 0000000..cbc6184
--- /dev/null
+++ b/tools/coccinelle/deref_null.cocci
@@ -0,0 +1,282 @@
+///
+/// A variable is dereferenced under a NULL test.
+/// Even though it is known to be NULL.
+///
+// Confidence: Moderate
+// Copyright: (C) 2010 Nicolas Palix, DIKU. GPLv2.
+// Copyright: (C) 2010 Julia Lawall, DIKU. GPLv2.
+// Copyright: (C) 2010 Gilles Muller, INRIA/LiP6. GPLv2.
+// URL: http://coccinelle.lip6.fr/
+// Comments: -I ... -all_includes can give more complete results
+// Options:
+
+virtual context
+virtual org
+virtual report
+
+// The following two rules are separate, because both can match a single
+// expression in different ways
+@pr1 expression@
+expression E;
+identifier f;
+position p1;
+@@
+
+ (E != NULL && ...) ? <+...E->f@p1...+> : ...
+
+@pr2 expression@
+expression E;
+identifier f;
+position p2;
+@@
+
+(
+ (E != NULL) && ... && <+...E->f@p2...+>
+|
+ (E == NULL) || ... || <+...E->f@p2...+>
+|
+ sizeof(<+...E->f@p2...+>)
+)
+
+@ifm@
+expression *E;
+statement S1,S2;
+position p1;
+@@
+
+if@p1 ((E == NULL && ...) || ...) S1 else S2
+
+// For org and report modes
+
+@r depends on !context && (org || report) exists@
+expression subE <= ifm.E;
+expression *ifm.E;
+expression E1,E2;
+identifier f;
+statement S1,S2,S3,S4;
+iterator iter;
+position p!={pr1.p1,pr2.p2};
+position ifm.p1;
+@@
+
+if@p1 ((E == NULL && ...) || ...)
+{
+ ... when != if (...) S1 else S2
+(
+ iter(subE,...) S4 // no use
+|
+ list_remove_head(E2,subE,...)
+|
+ subE = E1
+|
+ for(subE = E1;...;...) S4
+|
+ subE++
+|
+ ++subE
+|
+ --subE
+|
+ subE--
+|
+ &subE
+|
+ E->f@p // bad use
+)
+ ... when any
+ return ...;
+}
+else S3
+
+@script:python depends on !context && !org && report@
+p << r.p;
+p1 << ifm.p1;
+x << ifm.E;
+@@
+
+msg="ERROR: %s is NULL but dereferenced." % (x)
+coccilib.report.print_report(p[0], msg)
+cocci.include_match(False)
+
+@script:python depends on !context && org && !report@
+p << r.p;
+p1 << ifm.p1;
+x << ifm.E;
+@@
+
+msg="ERROR: %s is NULL but dereferenced." % (x)
+msg_safe=msg.replace("[","@(").replace("]",")")
+cocci.print_main(msg_safe,p)
+cocci.include_match(False)
+
+@s depends on !context && (org || report) exists@
+expression subE <= ifm.E;
+expression *ifm.E;
+expression E1,E2;
+identifier f;
+statement S1,S2,S3,S4;
+iterator iter;
+position p!={pr1.p1,pr2.p2};
+position ifm.p1;
+@@
+
+if@p1 ((E == NULL && ...) || ...)
+{
+ ... when != if (...) S1 else S2
+(
+ iter(subE,...) S4 // no use
+|
+ list_remove_head(E2,subE,...)
+|
+ subE = E1
+|
+ for(subE = E1;...;...) S4
+|
+ subE++
+|
+ ++subE
+|
+ --subE
+|
+ subE--
+|
+ &subE
+|
+ E->f@p // bad use
+)
+ ... when any
+}
+else S3
+
+@script:python depends on !context && !org && report@
+p << s.p;
+p1 << ifm.p1;
+x << ifm.E;
+@@
+
+msg="ERROR: %s is NULL but dereferenced." % (x)
+coccilib.report.print_report(p[0], msg)
+
+@script:python depends on !context && org && !report@
+p << s.p;
+p1 << ifm.p1;
+x << ifm.E;
+@@
+
+msg="ERROR: %s is NULL but dereferenced." % (x)
+msg_safe=msg.replace("[","@(").replace("]",")")
+cocci.print_main(msg_safe,p)
+
+// For context mode
+
+@depends on context && !org && !report exists@
+expression subE <= ifm.E;
+expression *ifm.E;
+expression E1,E2;
+identifier f;
+statement S1,S2,S3,S4;
+iterator iter;
+position p!={pr1.p1,pr2.p2};
+position ifm.p1;
+@@
+
+if@p1 ((E == NULL && ...) || ...)
+{
+ ... when != if (...) S1 else S2
+(
+ iter(subE,...) S4 // no use
+|
+ list_remove_head(E2,subE,...)
+|
+ subE = E1
+|
+ for(subE = E1;...;...) S4
+|
+ subE++
+|
+ ++subE
+|
+ --subE
+|
+ subE--
+|
+ &subE
+|
+* E->f@p // bad use
+)
+ ... when any
+ return ...;
+}
+else S3
+
+// The following three rules are duplicates of ifm, pr1 and pr2 respectively.
+// It is need because the previous rule as already made a "change".
+
+@pr11 depends on context && !org && !report expression@
+expression E;
+identifier f;
+position p1;
+@@
+
+ (E != NULL && ...) ? <+...E->f@p1...+> : ...
+
+@pr12 depends on context && !org && !report expression@
+expression E;
+identifier f;
+position p2;
+@@
+
+(
+ (E != NULL) && ... && <+...E->f@p2...+>
+|
+ (E == NULL) || ... || <+...E->f@p2...+>
+|
+ sizeof(<+...E->f@p2...+>)
+)
+
+@ifm1 depends on context && !org && !report@
+expression *E;
+statement S1,S2;
+position p1;
+@@
+
+if@p1 ((E == NULL && ...) || ...) S1 else S2
+
+@depends on context && !org && !report exists@
+expression subE <= ifm1.E;
+expression *ifm1.E;
+expression E1,E2;
+identifier f;
+statement S1,S2,S3,S4;
+iterator iter;
+position p!={pr11.p1,pr12.p2};
+position ifm1.p1;
+@@
+
+if@p1 ((E == NULL && ...) || ...)
+{
+ ... when != if (...) S1 else S2
+(
+ iter(subE,...) S4 // no use
+|
+ list_remove_head(E2,subE,...)
+|
+ subE = E1
+|
+ for(subE = E1;...;...) S4
+|
+ subE++
+|
+ ++subE
+|
+ --subE
+|
+ subE--
+|
+ &subE
+|
+* E->f@p // bad use
+)
+ ... when any
+}
+else S3
diff --git a/tools/coccinelle/double_lock.cocci b/tools/coccinelle/double_lock.cocci
new file mode 100644
index 0000000..002752f
--- /dev/null
+++ b/tools/coccinelle/double_lock.cocci
@@ -0,0 +1,92 @@
+/// Find double locks. False positives may occur when some paths cannot
+/// occur at execution, due to the values of variables, and when there is
+/// an intervening function call that releases the lock.
+///
+// Confidence: Moderate
+// Copyright: (C) 2010 Nicolas Palix, DIKU. GPLv2.
+// Copyright: (C) 2010 Julia Lawall, DIKU. GPLv2.
+// Copyright: (C) 2010 Gilles Muller, INRIA/LiP6. GPLv2.
+// URL: http://coccinelle.lip6.fr/
+// Comments:
+// Options: --no-includes --include-headers
+
+virtual org
+virtual report
+
+@locked@
+position p1;
+expression E1;
+position p;
+@@
+
+(
+mutex_lock@p1
+|
+mutex_trylock@p1
+|
+spin_lock@p1
+|
+spin_trylock@p1
+|
+read_lock@p1
+|
+read_trylock@p1
+|
+write_lock@p1
+|
+write_trylock@p1
+) (E1@p,...);
+
+@balanced@
+position p1 != locked.p1;
+position locked.p;
+identifier lock,unlock;
+expression x <= locked.E1;
+expression E,locked.E1;
+expression E2;
+@@
+
+if (E) {
+ <+... when != E1
+ lock(E1@p,...)
+ ...+>
+}
+... when != E1
+ when != \(x = E2\|&x\)
+ when forall
+if (E) {
+ <+... when != E1
+ unlock@p1(E1,...)
+ ...+>
+}
+
+@r depends on !balanced exists@
+expression x <= locked.E1;
+expression locked.E1;
+expression E2;
+identifier lock;
+position locked.p,p1,p2;
+@@
+
+lock@p1 (E1@p,...);
+... when != E1
+ when != \(x = E2\|&x\)
+lock@p2 (E1,...);
+
+@script:python depends on org@
+p1 << r.p1;
+p2 << r.p2;
+lock << r.lock;
+@@
+
+cocci.print_main(lock,p1)
+cocci.print_secs("second lock",p2)
+
+@script:python depends on report@
+p1 << r.p1;
+p2 << r.p2;
+lock << r.lock;
+@@
+
+msg = "second lock on line %s" % (p2[0].line)
+coccilib.report.print_report(p1[0],msg)
diff --git a/tools/coccinelle/doublebitand.cocci b/tools/coccinelle/doublebitand.cocci
new file mode 100644
index 0000000..72f1572
--- /dev/null
+++ b/tools/coccinelle/doublebitand.cocci
@@ -0,0 +1,54 @@
+/// Find bit operations that include the same argument more than once
+//# One source of false positives is when the argument performs a side
+//# effect. Another source of false positives is when a neutral value
+//# such as 0 for | is used to indicate no information, to maintain the
+//# same structure as other similar expressions
+///
+// Confidence: Moderate
+// Copyright: (C) 2010 Nicolas Palix, DIKU. GPLv2.
+// Copyright: (C) 2010 Julia Lawall, DIKU. GPLv2.
+// Copyright: (C) 2010 Gilles Muller, INRIA/LiP6. GPLv2.
+// URL: http://coccinelle.lip6.fr/
+// Comments:
+// Options: --no-includes --include-headers
+
+virtual context
+virtual org
+virtual report
+
+@r expression@
+expression E;
+position p;
+@@
+
+(
+* E@p
+ & ... & E
+|
+* E@p
+ | ... | E
+|
+* E@p
+ & ... & !E
+|
+* E@p
+ | ... | !E
+|
+* !E@p
+ & ... & E
+|
+* !E@p
+ | ... | E
+)
+
+@script:python depends on org@
+p << r.p;
+@@
+
+cocci.print_main("duplicated argument to & or |",p)
+
+@script:python depends on report@
+p << r.p;
+@@
+
+coccilib.report.print_report(p[0],"duplicated argument to & or |")
diff --git a/tools/coccinelle/doubleinit.cocci b/tools/coccinelle/doubleinit.cocci
new file mode 100644
index 0000000..c0c3371
--- /dev/null
+++ b/tools/coccinelle/doubleinit.cocci
@@ -0,0 +1,53 @@
+/// Find duplicate field initializations. This has a high rate of false
+/// positives due to #ifdefs, which Coccinelle is not aware of in a structure
+/// initialization.
+///
+// Confidence: Low
+// Copyright: (C) 2010-2012 Nicolas Palix. GPLv2.
+// Copyright: (C) 2010-2012 Julia Lawall, INRIA/LIP6. GPLv2.
+// Copyright: (C) 2010-2012 Gilles Muller, INRIA/LiP6. GPLv2.
+// URL: http://coccinelle.lip6.fr/
+// Comments: requires at least Coccinelle 0.2.4, lex or parse error otherwise
+// Options: --no-includes --include-headers
+
+virtual org
+virtual report
+
+@r@
+identifier I, s, fld;
+position p0,p;
+expression E;
+@@
+
+struct I s =@p0 { ..., .fld@p = E, ...};
+
+@s@
+identifier I, s, r.fld;
+position r.p0,p;
+expression E;
+@@
+
+struct I s =@p0 { ..., .fld@p = E, ...};
+
+@script:python depends on org@
+p0 << r.p0;
+fld << r.fld;
+ps << s.p;
+pr << r.p;
+@@
+
+if int(ps[0].line) < int(pr[0].line) or (int(ps[0].line) == int(pr[0].line) and int(ps[0].column) < int(pr[0].column)):
+ cocci.print_main(fld,p0)
+ cocci.print_secs("s",ps)
+ cocci.print_secs("r",pr)
+
+@script:python depends on report@
+p0 << r.p0;
+fld << r.fld;
+ps << s.p;
+pr << r.p;
+@@
+
+if int(ps[0].line) < int(pr[0].line) or (int(ps[0].line) == int(pr[0].line) and int(ps[0].column) < int(pr[0].column)):
+ msg = "%s: first occurrence line %s, second occurrence line %s" % (fld,ps[0].line,pr[0].line)
+ coccilib.report.print_report(p0[0],msg)
diff --git a/tools/coccinelle/doubletest.cocci b/tools/coccinelle/doubletest.cocci
new file mode 100644
index 0000000..7af2ce7
--- /dev/null
+++ b/tools/coccinelle/doubletest.cocci
@@ -0,0 +1,58 @@
+/// Find &&/|| operations that include the same argument more than once
+//# A common source of false positives is when the expression, or
+//# another expresssion in the same && or || operation, performs a
+//# side effect.
+///
+// Confidence: Moderate
+// Copyright: (C) 2010 Nicolas Palix, DIKU. GPLv2.
+// Copyright: (C) 2010 Julia Lawall, DIKU. GPLv2.
+// Copyright: (C) 2010 Gilles Muller, INRIA/LiP6. GPLv2.
+// URL: http://coccinelle.lip6.fr/
+// Comments:
+// Options: --no-includes --include-headers
+
+virtual context
+virtual org
+virtual report
+
+@r expression@
+expression E;
+position p;
+@@
+
+(
+ E@p || ... || E
+|
+ E@p && ... && E
+)
+
+@bad@
+expression r.E,e1,e2,fn;
+position r.p;
+assignment operator op;
+@@
+
+(
+E@p
+&
+ <+... \(fn(...)\|e1 op e2\|e1++\|e1--\|++e1\|--e1\) ...+>
+)
+
+@depends on context && !bad@
+expression r.E;
+position r.p;
+@@
+
+*E@p
+
+@script:python depends on org && !bad@
+p << r.p;
+@@
+
+cocci.print_main("duplicated argument to && or ||",p)
+
+@script:python depends on report && !bad@
+p << r.p;
+@@
+
+coccilib.report.print_report(p[0],"duplicated argument to && or ||")
diff --git a/tools/coccinelle/frr_with_mutex.cocci b/tools/coccinelle/frr_with_mutex.cocci
new file mode 100644
index 0000000..ec8b739
--- /dev/null
+++ b/tools/coccinelle/frr_with_mutex.cocci
@@ -0,0 +1,23 @@
+@@
+expression E;
+iterator name frr_with_mutex;
+@@
+
+- pthread_mutex_lock(E);
++ frr_with_mutex(E) {
+- {
+ ...
+- }
+- pthread_mutex_unlock(E);
++ }
+
+
+@@
+expression E;
+@@
+
+- pthread_mutex_lock(E);
++ frr_with_mutex(E) {
+ ...
+- pthread_mutex_unlock(E);
++ }
diff --git a/tools/coccinelle/hash_compare_null_values_check.cocci b/tools/coccinelle/hash_compare_null_values_check.cocci
new file mode 100644
index 0000000..38649a2
--- /dev/null
+++ b/tools/coccinelle/hash_compare_null_values_check.cocci
@@ -0,0 +1,20 @@
+// There is no need to test for null values in the hash compare
+// function as that we are guaranteed to send in data in
+// the hash compare functions.
+@@
+identifier fn =~ "_hash_cmp";
+type T;
+identifier p1;
+identifier p2;
+@@
+
+?static
+T fn(...)
+{
+...
+- if (p1 == NULL && p2 == NULL)
+- return ...;
+- if (p1 == NULL || p2 == NULL)
+- return ...;
+...
+}
diff --git a/tools/coccinelle/hash_const.cocci b/tools/coccinelle/hash_const.cocci
new file mode 100644
index 0000000..9c53cb0
--- /dev/null
+++ b/tools/coccinelle/hash_const.cocci
@@ -0,0 +1,76 @@
+//
+// Transition hash key signatures to take their argument as const.
+// Does not handle headers or weirdly named hash functions.
+//
+@noconst disable optional_qualifier@
+identifier A;
+identifier func =~ ".*key$|.*key_make$|.*hash_make$|.*hash_keymake$|.*hash_key$|.*hash_key.*";
+@@
+
+- func (void *A)
++ func (const void *A)
+ { ... }
+
+@ depends on noconst disable optional_qualifier @
+identifier noconst.A;
+identifier noconst.func;
+identifier b;
+type T;
+@@
+
+func( ... ) {
+<...
+- T b = A;
++ const T b = A;
+...>
+ }
+
+@ depends on noconst disable optional_qualifier @
+identifier noconst.A;
+identifier noconst.func;
+identifier b;
+type T;
+@@
+
+func(...)
+ {
+<...
+- T b = (T) A;
++ const T b = A;
+...>
+ }
+
+@ depends on noconst disable optional_qualifier @
+identifier noconst.A;
+identifier noconst.func;
+identifier b;
+type T;
+@@
+
+func(...)
+ {
+<...
+- T b;
++ const T b;
+...
+ b = A;
+...>
+ }
+
+@ depends on noconst disable optional_qualifier @
+identifier noconst.A;
+identifier noconst.func;
+identifier b;
+type T;
+@@
+
+func(...)
+ {
+<...
+- T b;
++ const T b;
+...
+- b = (T) A;
++ b = A;
+...>
+ }
diff --git a/tools/coccinelle/ifaddr.cocci b/tools/coccinelle/ifaddr.cocci
new file mode 100644
index 0000000..c2663c6
--- /dev/null
+++ b/tools/coccinelle/ifaddr.cocci
@@ -0,0 +1,34 @@
+/// The address of a variable or field is likely always to be non-zero.
+///
+// Confidence: High
+// Copyright: (C) 2012 Julia Lawall, INRIA/LIP6. GPLv2.
+// Copyright: (C) 2012 Gilles Muller, INRIA/LiP6. GPLv2.
+// URL: http://coccinelle.lip6.fr/
+// Comments:
+// Options: --no-includes --include-headers
+
+virtual org
+virtual report
+virtual context
+
+@r@
+expression x;
+statement S1,S2;
+position p;
+@@
+
+*if@p (&x)
+ S1 else S2
+
+@script:python depends on org@
+p << r.p;
+@@
+
+cocci.print_main("test of a variable/field address",p)
+
+@script:python depends on report@
+p << r.p;
+@@
+
+msg = "ERROR: test of a variable/field address"
+coccilib.report.print_report(p[0],msg)
diff --git a/tools/coccinelle/ifnullxfree.cocci b/tools/coccinelle/ifnullxfree.cocci
new file mode 100644
index 0000000..85fc23e
--- /dev/null
+++ b/tools/coccinelle/ifnullxfree.cocci
@@ -0,0 +1,15 @@
+/// NULL check before some freeing functions is not needed.
+///
+// Copyright: (C) 2014 Fabian Frederick. GPLv2.
+// Copyright: (C) 2019 Quentin Young. GPLv2.
+// Comments: -
+// Options: --no-includes --include-headers
+
+virtual patch
+
+@r2 depends on patch@
+expression E;
+expression Y;
+@@
+- if (E != NULL)
+XFREE(Y, E);
diff --git a/tools/coccinelle/int_to_bool_function.cocci b/tools/coccinelle/int_to_bool_function.cocci
new file mode 100644
index 0000000..f86fe70
--- /dev/null
+++ b/tools/coccinelle/int_to_bool_function.cocci
@@ -0,0 +1,24 @@
+@@
+identifier fn;
+typedef bool;
+symbol false;
+symbol true;
+identifier I;
+struct thread *thread;
+@@
+
+- int
++ bool
+fn (...)
+{
+... when strict
+ when != I = THREAD_ARG(thread);
+(
+- return 0;
++ return false;
+|
+- return 1;
++ return true;
+)
+?...
+}
diff --git a/tools/coccinelle/itnull.cocci b/tools/coccinelle/itnull.cocci
new file mode 100644
index 0000000..f58732b
--- /dev/null
+++ b/tools/coccinelle/itnull.cocci
@@ -0,0 +1,94 @@
+/// Many iterators have the property that the first argument is always bound
+/// to a real list element, never NULL.
+//# False positives arise for some iterators that do not have this property,
+//# or in cases when the loop cursor is reassigned. The latter should only
+//# happen when the matched code is on the way to a loop exit (break, goto,
+//# or return).
+///
+// Confidence: Moderate
+// Copyright: (C) 2010-2012 Nicolas Palix. GPLv2.
+// Copyright: (C) 2010-2012 Julia Lawall, INRIA/LIP6. GPLv2.
+// Copyright: (C) 2010-2012 Gilles Muller, INRIA/LiP6. GPLv2.
+// URL: http://coccinelle.lip6.fr/
+// Comments:
+// Options: --no-includes --include-headers
+
+virtual patch
+virtual context
+virtual org
+virtual report
+
+@depends on patch@
+iterator I;
+expression x,E,E1,E2;
+statement S,S1,S2;
+@@
+
+I(x,...) { <...
+(
+- if (x == NULL && ...) S
+|
+- if (x != NULL || ...)
+ S
+|
+- (x == NULL) ||
+ E
+|
+- (x != NULL) &&
+ E
+|
+- (x == NULL && ...) ? E1 :
+ E2
+|
+- (x != NULL || ...) ?
+ E1
+- : E2
+|
+- if (x == NULL && ...) S1 else
+ S2
+|
+- if (x != NULL || ...)
+ S1
+- else S2
+|
++ BAD(
+ x == NULL
++ )
+|
++ BAD(
+ x != NULL
++ )
+)
+ ...> }
+
+@r depends on !patch exists@
+iterator I;
+expression x,E;
+position p1,p2;
+@@
+
+*I@p1(x,...)
+{ ... when != x = E
+(
+* x@p2 == NULL
+|
+* x@p2 != NULL
+)
+ ... when any
+}
+
+@script:python depends on org@
+p1 << r.p1;
+p2 << r.p2;
+@@
+
+cocci.print_main("iterator-bound variable",p1)
+cocci.print_secs("useless NULL test",p2)
+
+@script:python depends on report@
+p1 << r.p1;
+p2 << r.p2;
+@@
+
+msg = "ERROR: iterator variable bound on line %s cannot be NULL" % (p1[0].line)
+coccilib.report.print_report(p2[0], msg)
diff --git a/tools/coccinelle/json_object_add_camel_case.cocci b/tools/coccinelle/json_object_add_camel_case.cocci
new file mode 100644
index 0000000..279ba21
--- /dev/null
+++ b/tools/coccinelle/json_object_add_camel_case.cocci
@@ -0,0 +1,19 @@
+// Catch whitespaces in JSON keys
+
+@r@
+identifier json;
+constant key;
+identifier func =~ "json_object_";
+position p;
+@@
+
+func(json, key, ...)@p
+
+@script:python@
+fmt << r.key;
+p << r.p;
+@@
+if " " in str(fmt):
+ print("Whitespace detected in JSON keys %s:%s:%s:%s" % (p[0].file, p[0].line, p[0].column, fmt))
+if str(fmt)[1].isupper():
+ print("Capital first detected in JSON keys %s:%s:%s:%s" % (p[0].file, p[0].line, p[0].column, fmt))
diff --git a/tools/coccinelle/json_object_string_addf_inet_ntop.cocci b/tools/coccinelle/json_object_string_addf_inet_ntop.cocci
new file mode 100644
index 0000000..d9f92e5
--- /dev/null
+++ b/tools/coccinelle/json_object_string_addf_inet_ntop.cocci
@@ -0,0 +1,19 @@
+@@
+identifier json;
+expression family, buf, value;
+constant key, buflen;
+@@
+
+(
+-json_object_string_add(json, key, inet_ntop(AF_INET, &value, buf, sizeof(buf)));
++json_object_string_addf(json, key, "%pI4", &value);
+|
+-json_object_string_add(json, key, inet_ntop(AF_INET, &value, buf, buflen));
++json_object_string_addf(json, key, "%pI4", &value);
+|
+-json_object_string_add(json, key, inet_ntop(AF_INET6, &value, buf, sizeof(buf)));
++json_object_string_addf(json, key, "%pI6", &value);
+|
+-json_object_string_add(json, key, inet_ntop(AF_INET6, &value, buf, buflen));
++json_object_string_addf(json, key, "%pI6", &value);
+)
diff --git a/tools/coccinelle/json_object_string_addf_prefix2str.cocci b/tools/coccinelle/json_object_string_addf_prefix2str.cocci
new file mode 100644
index 0000000..ae012b9
--- /dev/null
+++ b/tools/coccinelle/json_object_string_addf_prefix2str.cocci
@@ -0,0 +1,16 @@
+@@
+identifier json;
+expression family, value;
+expression prefix;
+constant key;
+@@
+
+(
+-prefix2str(prefix, value, ...);
+...
+-json_object_string_add(json, key, value);
++json_object_string_addf(json, key, "%pFX", prefix);
+|
+-json_object_string_add(json, key, prefix2str(prefix, value, ...));
++json_object_string_addf(json, key, "%pFX", prefix);
+)
diff --git a/tools/coccinelle/memset.cocci b/tools/coccinelle/memset.cocci
new file mode 100644
index 0000000..0da576c
--- /dev/null
+++ b/tools/coccinelle/memset.cocci
@@ -0,0 +1,21 @@
+//
+
+@@
+identifier src, dst;
+identifier str, len;
+type t =~ "struct";
+
+@@
+
+(
+- memset(&dst, 0, sizeof(t));
++ memset(&dst, 0, sizeof(dst));
+|
+- memcpy(&dst, &src, sizeof(t));
++ memcpy(&dst, &src, sizeof(dst));
+|
+- char str[...];
+...
+- memset(&str, 0, ...);
++ memset(&str, 0, sizeof(str));
+)
diff --git a/tools/coccinelle/mini_lock.cocci b/tools/coccinelle/mini_lock.cocci
new file mode 100644
index 0000000..19c6ee5
--- /dev/null
+++ b/tools/coccinelle/mini_lock.cocci
@@ -0,0 +1,98 @@
+/// Find missing unlocks. This semantic match considers the specific case
+/// where the unlock is missing from an if branch, and there is a lock
+/// before the if and an unlock after the if. False positives are due to
+/// cases where the if branch represents a case where the function is
+/// supposed to exit with the lock held, or where there is some preceding
+/// function call that releases the lock.
+///
+// Confidence: Moderate
+// Copyright: (C) 2010-2012 Nicolas Palix. GPLv2.
+// Copyright: (C) 2010-2012 Julia Lawall, INRIA/LIP6. GPLv2.
+// Copyright: (C) 2010-2012 Gilles Muller, INRIA/LiP6. GPLv2.
+// URL: http://coccinelle.lip6.fr/
+// Comments:
+// Options: --no-includes --include-headers
+
+virtual context
+virtual org
+virtual report
+
+@prelocked@
+position p1,p;
+expression E1;
+@@
+
+(
+mutex_lock@p1
+|
+mutex_trylock@p1
+|
+spin_lock@p1
+|
+spin_trylock@p1
+|
+read_lock@p1
+|
+read_trylock@p1
+|
+write_lock@p1
+|
+write_trylock@p1
+|
+read_lock_irq@p1
+|
+write_lock_irq@p1
+|
+read_lock_irqsave@p1
+|
+write_lock_irqsave@p1
+|
+spin_lock_irq@p1
+|
+spin_lock_irqsave@p1
+) (E1@p,...);
+
+@looped@
+position r;
+@@
+
+for(...;...;...) { <+... return@r ...; ...+> }
+
+@err exists@
+expression E1;
+position prelocked.p;
+position up != prelocked.p1;
+position r!=looped.r;
+identifier lock,unlock;
+@@
+
+*lock(E1@p,...);
+... when != E1
+ when any
+if (...) {
+ ... when != E1
+* return@r ...;
+}
+... when != E1
+ when any
+*unlock@up(E1,...);
+
+@script:python depends on org@
+p << prelocked.p1;
+lock << err.lock;
+unlock << err.unlock;
+p2 << err.r;
+@@
+
+cocci.print_main(lock,p)
+cocci.print_secs(unlock,p2)
+
+@script:python depends on report@
+p << prelocked.p1;
+lock << err.lock;
+unlock << err.unlock;
+p2 << err.r;
+@@
+
+msg = "preceding lock on line %s" % (p[0].line)
+coccilib.report.print_report(p2[0],msg)
diff --git a/tools/coccinelle/nb-cbs.cocci b/tools/coccinelle/nb-cbs.cocci
new file mode 100644
index 0000000..6e4d32e
--- /dev/null
+++ b/tools/coccinelle/nb-cbs.cocci
@@ -0,0 +1,298 @@
+@@
+identifier func =~ ".*_create$";
+identifier event, dnode, resource;
+@@
+
+int
+- func(enum nb_event event, const struct lyd_node *dnode, union nb_resource *resource)
++ func(struct nb_cb_create_args *args)
+ {
+<...
+(
+- event
++ args->event
+|
+- dnode
++ args->dnode
+|
+- resource
++ args->resource
+)
+...>
+ }
+
+@@
+identifier func =~ ".*_modify$";
+identifier event, dnode, resource;
+@@
+
+int
+- func(enum nb_event event, const struct lyd_node *dnode, union nb_resource *resource)
++ func(struct nb_cb_modify_args *args)
+ {
+<...
+(
+- event
++ args->event
+|
+- dnode
++ args->dnode
+|
+- resource
++ args->resource
+)
+...>
+ }
+
+@@
+identifier func =~ ".*_destroy$";
+identifier event, dnode;
+@@
+
+int
+- func(enum nb_event event, const struct lyd_node *dnode)
++ func(struct nb_cb_destroy_args *args)
+ {
+<...
+(
+- dnode
++ args->dnode
+|
+- event
++ args->event
+)
+...>
+ }
+
+@@
+identifier func =~ ".*_pre_validate$";
+identifier dnode;
+@@
+
+int
+- func(const struct lyd_node dnode)
++ func(struct nb_cb_pre_validate_args *args)
+ {
+<...
+- dnode
++ args->dnode
+...>
+ }
+
+@@
+identifier func =~ ".*_apply_finish$";
+identifier dnode;
+@@
+
+void
+- func(const struct lyd_node *dnode)
++ func(struct nb_cb_apply_finish_args *args)
+ {
+<...
+- dnode
++ args->dnode
+...>
+ }
+
+@@
+identifier func =~ ".*_get_elem$";
+identifier xpath, list_entry;
+@@
+
+struct yang_data *
+- func(const char *xpath, const void *list_entry)
++ func(struct nb_cb_get_elem_args *args)
+ {
+<...
+(
+- xpath
++ args->xpath
+|
+- list_entry
++ args->list_entry
+)
+...>
+ }
+
+@@
+identifier func =~ ".*_get_next$";
+identifier parent_list_entry, list_entry;
+@@
+
+const void *
+- func(const void *parent_list_entry, const void *list_entry)
++ func(struct nb_cb_get_next_args *args)
+ {
+<...
+(
+- parent_list_entry
++ args->parent_list_entry
+|
+- list_entry
++ args->list_entry
+)
+...>
+ }
+
+@@
+identifier func =~ ".*_get_keys$";
+identifier list_entry, keys;
+@@
+
+int
+- func(const void *list_entry, struct yang_list_keys *keys)
++ func(struct nb_cb_get_keys_args *args)
+ {
+<...
+(
+- list_entry
++ args->list_entry
+|
+- keys
++ args->keys
+)
+...>
+ }
+
+@@
+identifier func =~ ".*_lookup_entry$";
+identifier parent_list_entry, keys;
+@@
+
+const void *
+- func(const void *parent_list_entry, const struct yang_list_keys *keys)
++ func(struct nb_cb_lookup_entry_args *args)
+ {
+<...
+(
+- parent_list_entry
++ args->parent_list_entry
+|
+- keys
++ args->keys
+)
+...>
+ }
+
+@@
+identifier func =~ ".*_rpc$";
+identifier xpath, input, output;
+@@
+
+int
+- func(const char *xpath, const struct list *input, struct list *output)
++ func(struct nb_cb_rpc_args *args)
+ {
+<...
+(
+- xpath
++ args->xpath
+|
+- input
++ args->input
+|
+- output
++ args->output
+)
+...>
+ }
+
+@@
+identifier func =~ ".*_create$";
+identifier event, dnode, resource;
+@@
+
+int
+- func(enum nb_event event, const struct lyd_node *dnode, union nb_resource *resource)
++ func(struct nb_cb_create_args *args)
+;
+
+@@
+identifier func =~ ".*_modify$";
+identifier event, dnode, resource;
+@@
+
+int
+- func(enum nb_event event, const struct lyd_node *dnode, union nb_resource *resource)
++ func(struct nb_cb_modify_args *args)
+;
+
+@@
+identifier func =~ ".*_destroy$";
+identifier event, dnode;
+@@
+
+int
+- func(enum nb_event event, const struct lyd_node *dnode)
++ func(struct nb_cb_destroy_args *args)
+;
+
+@@
+identifier func =~ ".*_pre_validate$";
+identifier dnode;
+@@
+
+int
+- func(const struct lyd_node dnode)
++ func(struct nb_cb_pre_validate_args *args)
+;
+
+@@
+identifier func =~ ".*_apply_finish$";
+identifier dnode;
+@@
+
+void
+- func(const struct lyd_node *dnode)
++ func(struct nb_cb_apply_finish_args *args)
+;
+
+@@
+identifier func =~ ".*_get_elem$";
+identifier xpath, list_entry;
+@@
+
+struct yang_data *
+- func(const char *xpath, const void *list_entry)
++ func(struct nb_cb_get_elem_args *args)
+;
+
+@@
+identifier func =~ ".*_get_next$";
+identifier parent_list_entry, list_entry;
+@@
+
+const void *
+- func(const void *parent_list_entry, const void *list_entry)
++ func(struct nb_cb_get_next_args *args)
+;
+
+@@
+identifier func =~ ".*_get_keys$";
+identifier list_entry, keys;
+@@
+
+int
+- func(const void *list_entry, struct yang_list_keys *keys)
++ func(struct nb_cb_get_keys_args *args)
+;
+
+@@
+identifier func =~ ".*_lookup_entry$";
+identifier parent_list_entry, keys;
+@@
+
+const void *
+- func(const void *parent_list_entry, const struct yang_list_keys *keys)
++ func(struct nb_cb_lookup_entry_args *args)
+;
+
+@@
+identifier func =~ ".*_rpc$";
+identifier xpath, input, output;
+@@
+
+int
+- func(const char *xpath, const struct list *input, struct list *output)
++ func(struct nb_cb_rpc_args *args)
+;
diff --git a/tools/coccinelle/noderef.cocci b/tools/coccinelle/noderef.cocci
new file mode 100644
index 0000000..ca289d5
--- /dev/null
+++ b/tools/coccinelle/noderef.cocci
@@ -0,0 +1,81 @@
+/// sizeof when applied to a pointer typed expression gives the size of
+/// the pointer
+///
+// Confidence: High
+// Copyright: (C) 2012 Julia Lawall, INRIA/LIP6. GPLv2.
+// Copyright: (C) 2012 Gilles Muller, INRIA/LiP6. GPLv2.
+// URL: http://coccinelle.lip6.fr/
+// Comments:
+// Options: --no-includes --include-headers
+
+virtual org
+virtual report
+virtual context
+virtual patch
+
+@depends on patch@
+expression *x;
+expression f;
+expression i;
+type T;
+@@
+
+(
+x = <+... sizeof(
+- x
++ *x
+ ) ...+>
+|
+f(...,(T)(x),...,sizeof(
+- x
++ *x
+ ),...)
+|
+f(...,sizeof(
+- x
++ *x
+ ),...,(T)(x),...)
+|
+f(...,(T)(x),...,i*sizeof(
+- x
++ *x
+ ),...)
+|
+f(...,i*sizeof(
+- x
++ *x
+ ),...,(T)(x),...)
+)
+
+@r depends on !patch@
+expression *x;
+expression f;
+expression i;
+position p;
+type T;
+@@
+
+(
+*x = <+... sizeof@p(x) ...+>
+|
+*f(...,(T)(x),...,sizeof@p(x),...)
+|
+*f(...,sizeof@p(x),...,(T)(x),...)
+|
+*f(...,(T)(x),...,i*sizeof@p(x),...)
+|
+*f(...,i*sizeof@p(x),...,(T)(x),...)
+)
+
+@script:python depends on org@
+p << r.p;
+@@
+
+cocci.print_main("application of sizeof to pointer",p)
+
+@script:python depends on report@
+p << r.p;
+@@
+
+msg = "ERROR: application of sizeof to pointer"
+coccilib.report.print_report(p[0],msg)<Paste>
diff --git a/tools/coccinelle/replace-strncpy.cocci b/tools/coccinelle/replace-strncpy.cocci
new file mode 100644
index 0000000..18ff131
--- /dev/null
+++ b/tools/coccinelle/replace-strncpy.cocci
@@ -0,0 +1,8 @@
+@@
+type T;
+T[] E;
+expression buf, srclen;
+@@
+
+- strncpy(E, src, srclen)
++ strlcpy(E, src, sizeof(E))
diff --git a/tools/coccinelle/replace_bgp_flag_functions.cocci b/tools/coccinelle/replace_bgp_flag_functions.cocci
new file mode 100644
index 0000000..3064fc0
--- /dev/null
+++ b/tools/coccinelle/replace_bgp_flag_functions.cocci
@@ -0,0 +1,14 @@
+@@
+expression e1, e2;
+@@
+
+(
+- bgp_flag_check(e1, e2)
++ CHECK_FLAG(e1->flags, e2)
+|
+- bgp_flag_set(e1, e2)
++ SET_FLAG(e1->flags, e2)
+|
+- bgp_flag_unset(e1, e2)
++ UNSET_FLAG(e1->flags, e2)
+)
diff --git a/tools/coccinelle/return_without_parenthesis.cocci b/tools/coccinelle/return_without_parenthesis.cocci
new file mode 100644
index 0000000..7097e87
--- /dev/null
+++ b/tools/coccinelle/return_without_parenthesis.cocci
@@ -0,0 +1,9 @@
+// Do not apply only for ldpd daemon since it uses the BSD coding style,
+// where parentheses on return is expected.
+
+@@
+constant c;
+@@
+
+- return (c);
++ return c;
diff --git a/tools/coccinelle/returnvar.cocci b/tools/coccinelle/returnvar.cocci
new file mode 100644
index 0000000..d8286ef
--- /dev/null
+++ b/tools/coccinelle/returnvar.cocci
@@ -0,0 +1,66 @@
+///
+/// Remove unneeded variable used to store return value.
+///
+// Confidence: Moderate
+// Copyright: (C) 2012 Peter Senna Tschudin, INRIA/LIP6. GPLv2.
+// URL: http://coccinelle.lip6.fr/
+// Comments: Comments on code can be deleted if near code that is removed.
+// "when strict" can be removed to get more hits, but adds false
+// positives
+// Options: --no-includes --include-headers
+
+virtual patch
+virtual report
+virtual context
+virtual org
+
+@depends on patch@
+type T;
+constant C;
+identifier ret;
+@@
+- T ret = C;
+... when != ret
+ when strict
+return
+- ret
++ C
+;
+
+@depends on context@
+type T;
+constant C;
+identifier ret;
+@@
+* T ret = C;
+... when != ret
+ when strict
+* return ret;
+
+@r1 depends on report || org@
+type T;
+constant C;
+identifier ret;
+position p1, p2;
+@@
+T ret@p1 = C;
+... when != ret
+ when strict
+return ret@p2;
+
+@script:python depends on report@
+p1 << r1.p1;
+p2 << r1.p2;
+C << r1.C;
+ret << r1.ret;
+@@
+coccilib.report.print_report(p1[0], "Unneeded variable: \"" + ret + "\". Return \"" + C + "\" on line " + p2[0].line)
+
+@script:python depends on org@
+p1 << r1.p1;
+p2 << r1.p2;
+C << r1.C;
+ret << r1.ret;
+@@
+cocci.print_main("unneeded \"" + ret + "\" variable", p1)
+cocci.print_sec("return " + C + " here", p2)
diff --git a/tools/coccinelle/route_map_apply.cocci b/tools/coccinelle/route_map_apply.cocci
new file mode 100644
index 0000000..ccca619
--- /dev/null
+++ b/tools/coccinelle/route_map_apply.cocci
@@ -0,0 +1,15 @@
+@rmap@
+identifier ret;
+position p;
+@@
+
+int ret@p;
+...
+* ret = route_map_apply(...);
+
+@script:python@
+p << rmap.p;
+@@
+
+msg = "ERROR: Invalid type of return value variable for route_map_apply_ext()"
+coccilib.report.print_report(p[0], msg)
diff --git a/tools/coccinelle/s_addr_0_to_INADDR_ANY.cocci b/tools/coccinelle/s_addr_0_to_INADDR_ANY.cocci
new file mode 100644
index 0000000..bd7f4af
--- /dev/null
+++ b/tools/coccinelle/s_addr_0_to_INADDR_ANY.cocci
@@ -0,0 +1,14 @@
+@@
+expression e;
+@@
+
+(
+- e.s_addr == 0
++ e.s_addr == INADDR_ANY
+|
+- e.s_addr != 0
++ e.s_addr != INADDR_ANY
+|
+- e.s_addr = 0
++ e.s_addr = INADDR_ANY
+)
diff --git a/tools/coccinelle/same_type_casting.cocci b/tools/coccinelle/same_type_casting.cocci
new file mode 100644
index 0000000..58fd756
--- /dev/null
+++ b/tools/coccinelle/same_type_casting.cocci
@@ -0,0 +1,7 @@
+@@
+type T;
+T *ptr;
+@@
+
+- (T *)ptr
++ ptr
diff --git a/tools/coccinelle/semicolon.cocci b/tools/coccinelle/semicolon.cocci
new file mode 100644
index 0000000..6740c65
--- /dev/null
+++ b/tools/coccinelle/semicolon.cocci
@@ -0,0 +1,83 @@
+///
+/// Remove unneeded semicolon.
+///
+// Confidence: Moderate
+// Copyright: (C) 2012 Peter Senna Tschudin, INRIA/LIP6. GPLv2.
+// URL: http://coccinelle.lip6.fr/
+// Comments: Some false positives on empty default cases in switch statements.
+// Options: --no-includes --include-headers
+
+virtual patch
+virtual report
+virtual context
+virtual org
+
+@r_default@
+position p;
+@@
+switch (...)
+{
+default: ...;@p
+}
+
+@r_case@
+position p;
+@@
+(
+switch (...)
+{
+case ...:;@p
+}
+|
+switch (...)
+{
+case ...:...
+case ...:;@p
+}
+|
+switch (...)
+{
+case ...:...
+case ...:
+case ...:;@p
+}
+)
+
+@r1@
+statement S;
+position p1;
+position p != {r_default.p, r_case.p};
+identifier label;
+@@
+(
+label:;
+|
+S@p1;@p
+)
+
+@script:python@
+p << r1.p;
+p1 << r1.p1;
+@@
+if p[0].line != p1[0].line_end:
+ cocci.include_match(False)
+
+@depends on patch@
+position r1.p;
+@@
+-;@p
+
+@script:python depends on report@
+p << r1.p;
+@@
+coccilib.report.print_report(p[0],"Unneeded semicolon")
+
+@depends on context@
+position r1.p;
+@@
+*;@p
+
+@script:python depends on org@
+p << r1.p;
+@@
+cocci.print_main("Unneeded semicolon",p)
diff --git a/tools/coccinelle/shorthand_operator.cocci b/tools/coccinelle/shorthand_operator.cocci
new file mode 100644
index 0000000..f7019d4
--- /dev/null
+++ b/tools/coccinelle/shorthand_operator.cocci
@@ -0,0 +1,12 @@
+@@
+identifier data;
+constant x;
+@@
+
+(
+- data = data + x
++ data += x
+|
+- data = data - x
++ data -= x
+)
diff --git a/tools/coccinelle/strncpy_truncation.cocci b/tools/coccinelle/strncpy_truncation.cocci
new file mode 100644
index 0000000..28b5c2a
--- /dev/null
+++ b/tools/coccinelle/strncpy_truncation.cocci
@@ -0,0 +1,41 @@
+/// Use strlcpy rather than strncpy(dest,..,sz) + dest[sz-1] = '\0'
+///
+// Confidence: High
+// Comments:
+// Options: --no-includes --include-headers
+
+virtual patch
+virtual context
+virtual report
+virtual org
+
+@r@
+expression dest, src, sz;
+position p;
+@@
+
+strncpy@p(dest, src, sz);
+dest[sz - 1] = '\0';
+
+@script:python depends on org@
+p << r.p;
+@@
+
+cocci.print_main("strncpy followed by truncation can be strlcpy",p)
+
+@script:python depends on report@
+p << r.p;
+@@
+
+msg = "SUGGESTION: strncpy followed by truncation can be strlcpy"
+coccilib.report.print_report(p[0],msg)
+
+@ok depends on patch@
+expression r.dest, r.src, r.sz;
+position r.p;
+@@
+
+-strncpy@p(
++strlcpy(
+ dest, src, sz);
+-dest[sz - 1] = '\0';
diff --git a/tools/coccinelle/struct_thread_double_pointer.cocci b/tools/coccinelle/struct_thread_double_pointer.cocci
new file mode 100644
index 0000000..a08e672
--- /dev/null
+++ b/tools/coccinelle/struct_thread_double_pointer.cocci
@@ -0,0 +1,35 @@
+@r1@
+identifier fn, m, f, a, v, t;
+identifier func =~ "thread_add_";
+type T1, T2;
+position p;
+@@
+
+?static
+T1 fn(T2 *t)
+{
+...
+func(m,f,a,v,&t)@p
+...
+}
+
+@r2@
+identifier m, f, a, v, t;
+identifier func =~ "thread_add_";
+type T1;
+position p;
+@@
+
+T1 *t;
+...
+func(m,f,a,v,&t)@p
+
+@script:python@
+p << r1.p;
+@@
+coccilib.report.print_report(p[0],"Passed double 'struct thread' pointer")
+
+@script:python@
+p << r2.p;
+@@
+coccilib.report.print_report(p[0],"Passed double 'struct thread' pointer")
diff --git a/tools/coccinelle/struct_thread_null.cocci b/tools/coccinelle/struct_thread_null.cocci
new file mode 100644
index 0000000..4867b44
--- /dev/null
+++ b/tools/coccinelle/struct_thread_null.cocci
@@ -0,0 +1,9 @@
+@@
+identifier I;
+identifier func =~ "thread_add_";
+struct thread *thread;
+@@
+
+*thread = NULL;
+...
+func
diff --git a/tools/coccinelle/test_after_assert.cocci b/tools/coccinelle/test_after_assert.cocci
new file mode 100644
index 0000000..30596a8
--- /dev/null
+++ b/tools/coccinelle/test_after_assert.cocci
@@ -0,0 +1,7 @@
+@@
+identifier i;
+@@
+
+assert(i);
+- if (!i)
+- return ...;
diff --git a/tools/coccinelle/thread_cancel_api.cocci b/tools/coccinelle/thread_cancel_api.cocci
new file mode 100644
index 0000000..cc34f93
--- /dev/null
+++ b/tools/coccinelle/thread_cancel_api.cocci
@@ -0,0 +1,68 @@
+@ptrupdate@
+expression E;
+@@
+- thread_cancel(E);
++ thread_cancel(&E);
+
+@nullcheckremove depends on ptrupdate@
+expression E;
+@@
+
+thread_cancel(&E);
+- E = NULL;
+
+@cancelguardremove depends on nullcheckremove@
+expression E;
+@@
+- if (E)
+- {
+ thread_cancel(&E);
+- }
+
+@cancelguardremove2 depends on nullcheckremove@
+expression E;
+@@
+- if (E != NULL)
+- {
+ thread_cancel(&E);
+- }
+
+@cancelguardremove3 depends on nullcheckremove@
+expression E;
+@@
+- if (E)
+ thread_cancel(&E);
+
+@cancelguardremove4 depends on nullcheckremove@
+expression E;
+@@
+- if (E != NULL)
+ thread_cancel(&E);
+
+@replacetimeroff@
+expression E;
+@@
+
+- THREAD_TIMER_OFF(E);
++ thread_cancel(&E);
+
+@replacewriteoff@
+expression E;
+@@
+
+- THREAD_WRITE_OFF(E);
++ thread_cancel(&E);
+
+@replacereadoff@
+expression E;
+@@
+
+- THREAD_READ_OFF(E);
++ thread_cancel(&E);
+
+@replacethreadoff@
+expression E;
+@@
+
+- THREAD_OFF(E);
++ thread_cancel(&E); \ No newline at end of file
diff --git a/tools/coccinelle/unsigned_lesser_than_zero.cocci b/tools/coccinelle/unsigned_lesser_than_zero.cocci
new file mode 100644
index 0000000..8fa5a3c
--- /dev/null
+++ b/tools/coccinelle/unsigned_lesser_than_zero.cocci
@@ -0,0 +1,75 @@
+/// Unsigned expressions cannot be lesser than zero. Presence of
+/// comparisons 'unsigned (<|<=|>|>=) 0' often indicates a bug,
+/// usually wrong type of variable.
+///
+/// To reduce number of false positives following tests have been added:
+/// - parts of range checks are skipped, eg. "if (u < 0 || u > 15) ...",
+/// developers prefer to keep such code,
+/// - comparisons "<= 0" and "> 0" are performed only on results of
+/// signed functions/macros,
+/// - hardcoded list of signed functions/macros with always non-negative
+/// result is used to avoid false positives difficult to detect by other ways
+///
+// Confidence: Average
+// Copyright: (C) 2015 Andrzej Hajda, Samsung Electronics Co., Ltd. GPLv2.
+// URL: http://coccinelle.lip6.fr/
+// Options: --all-includes
+
+virtual context
+virtual org
+virtual report
+
+@r_cmp@
+position p;
+typedef bool, u8, u16, u32, u64;
+{unsigned char, unsigned short, unsigned int, unsigned long, unsigned long long,
+ size_t, bool, u8, u16, u32, u64} v;
+expression e;
+@@
+
+ \( v = e \| &v \)
+ ...
+ (\( v@p < 0 \| v@p <= 0 \| v@p >= 0 \| v@p > 0 \))
+
+@r@
+position r_cmp.p;
+typedef s8, s16, s32, s64;
+{char, short, int, long, long long, ssize_t, s8, s16, s32, s64} vs;
+expression c, e, v;
+identifier f !~ "^(ata_id_queue_depth|btrfs_copy_from_user|dma_map_sg|dma_map_sg_attrs|fls|fls64|gameport_time|get_write_extents|nla_len|ntoh24|of_flat_dt_match|of_get_child_count|uart_circ_chars_pending|[A-Z0-9_]+)$";
+@@
+
+(
+ v = f(...)@vs;
+ ... when != v = e;
+* (\( v@p <=@e 0 \| v@p >@e 0 \))
+ ... when any
+|
+(
+ (\( v@p < 0 \| v@p <= 0 \)) || ... || (\( v >= c \| v > c \))
+|
+ (\( v >= c \| v > c \)) || ... || (\( v@p < 0 \| v@p <= 0 \))
+|
+ (\( v@p >= 0 \| v@p > 0 \)) && ... && (\( v < c \| v <= c \))
+|
+ ((\( v < c \| v <= c \) && ... && \( v@p >= 0 \| v@p > 0 \)))
+|
+* (\( v@p <@e 0 \| v@p >=@e 0 \))
+)
+)
+
+@script:python depends on org@
+p << r_cmp.p;
+e << r.e;
+@@
+
+msg = "WARNING: Unsigned expression compared with zero: %s" % (e)
+coccilib.org.print_todo(p[0], msg)
+
+@script:python depends on report@
+p << r_cmp.p;
+e << r.e;
+@@
+
+msg = "WARNING: Unsigned expression compared with zero: %s" % (e)
+coccilib.report.print_report(p[0], msg)
diff --git a/tools/coccinelle/void_no_return.cocci b/tools/coccinelle/void_no_return.cocci
new file mode 100644
index 0000000..7da9e73
--- /dev/null
+++ b/tools/coccinelle/void_no_return.cocci
@@ -0,0 +1,9 @@
+@@
+identifier f;
+expression e;
+@@
+void f(...) {
+ ...
+- return
+ e;
+}
diff --git a/tools/coccinelle/vty_check.cocci b/tools/coccinelle/vty_check.cocci
new file mode 100644
index 0000000..7e5fcc4
--- /dev/null
+++ b/tools/coccinelle/vty_check.cocci
@@ -0,0 +1,22 @@
+/*
+ * VTY_DECLVAR_CONTEXT contains a built-in "if (!var) return;"
+ */
+@@
+identifier var, typ;
+statement S;
+@@
+
+ {
+ ...
+ \(
+ VTY_DECLVAR_CONTEXT(typ, var);
+ \|
+ VTY_DECLVAR_CONTEXT_SUB(typ, var);
+ \)
+ ...
+- if (
+- \( !var \| var == NULL \)
+- )
+- S
+ ...
+ }
diff --git a/tools/coccinelle/vty_index.cocci b/tools/coccinelle/vty_index.cocci
new file mode 100644
index 0000000..eabbaa1
--- /dev/null
+++ b/tools/coccinelle/vty_index.cocci
@@ -0,0 +1,244 @@
+/*
+ * prep: strip off casts, they cause things to fail matching later.
+ */
+
+@@
+identifier casttarget;
+symbol vty;
+@@
+
+- (struct casttarget *)vty->index
++ vty->index
+
+/*
+ * variant 1: local variable assigned from vty->index
+ */
+
+@@
+identifier sn, nn;
+identifier fn;
+@@
+
+ int fn(...)
+ {
++ VTY_DECLVAR_CONTEXT (sn, nn);
+ ...
+ \(
+- struct sn *nn;
+ ...
+- nn = vty->index;
+ \|
+- struct sn *nn = vty->index;
+ \|
+- struct sn *nn = vty->index;
+ ...
+- nn = vty->index;
+ \)
+ ...
+ }
+
+@@
+identifier sn, nn;
+identifier fn;
+type Tr;
+@@
+
+ Tr *fn(...)
+ {
++ struct sn *nn = VTY_GET_CONTEXT(sn);
+ ...
+ \(
+- struct sn *nn;
+ ...
+- nn = vty->index;
++ if (!nn) {
++ return NULL;
++ }
+ \|
+- struct sn *nn = vty->index;
++ if (!nn) {
++ return NULL;
++ }
+ \|
+- struct sn *nn = vty->index;
+ ...
+- nn = vty->index;
++ if (!nn) {
++ return NULL;
++ }
+ \)
+ ...
+ }
+
+/*
+ * variant 2: vty wrapper func with (vty, vty->index, ...) signature
+ */
+
+/* find calls of this pattern first; arg will be dropped in rule3 */
+@rule1@
+identifier fn !~ "generic_(set|match)_";
+expression arg;
+@@
+
+ fn(arg, arg->index, ...)
+
+@ script:python @
+fn << rule1.fn;
+arg << rule1.arg;
+@@
+print "R01 removing vty-index argument on %s(%s, ...)" % (fn, arg)
+
+#/* strip arg on the vty wrapper func, add local handling */
+@ rule2 @
+identifier rule1.fn;
+identifier arg;
+identifier T;
+@@
+
+ static int fn (struct vty *vty,
+- struct T * arg,
+ ...)
+ {
++ VTY_DECLVAR_CONTEXT (T, arg);
+ ...
+ }
+
+/* drop argument on call sites identified earlier */
+@ rule3 @
+identifier rule1.fn;
+expression arg;
+@@
+
+ fn(arg,
+- arg->index,
+ ...)
+
+
+/*
+ * variant 3: function calls with "vty->index" argument (but no vty)
+ *
+ * a bit more complicated since we need to find the type from the header.
+ */
+
+/* find call sites first
+ * remember function name for later declvar insertion
+ */
+@ rule11 exists@
+identifier fn;
+identifier fparent;
+type Tr;
+@@
+
+ Tr fparent (...)
+ {
+ ...
+ fn(vty->index, ...)
+ ...
+ }
+
+@ script:python @
+fn << rule11.fn;
+@@
+print "R11 removing vty-index argument on %s(...)" % (fn)
+
+#/* find type of the argument - note args are mostly unnamed in FRR :( */
+@ rule12 @
+identifier rule11.fn;
+identifier T, argname;
+type Tr;
+@@
+
+(
+ Tr fn(struct T *, ...);
+|
+ Tr fn(struct T * argname, ...);
+)
+
+@ script:python @
+fn << rule11.fn;
+T << rule12.T;
+@@
+print "R12 removing vty-index type is %s for %s(...)" % (T, fn)
+
+#/* add declvar
+# * this is split from rule14 so we support multiple calls in one func */
+@ rule13a @
+identifier rule11.fparent;
+identifier rule12.T;
+@@
+
+ int fparent (...)
+ {
++ VTY_DECLVAR_CONTEXT(T, T);
+ ...
+ }
+
+@ rule13b @
+identifier rule11.fparent;
+identifier rule12.T;
+type Tr;
+@@
+
+ Tr *fparent (...)
+ {
++ struct T *T = VTY_GET_CONTEXT(T);
++ if (!T) {
++ return NULL;
++ }
+ ...
+ }
+
+/* now replace the argument in the call */
+@ rule14 exists @
+identifier rule11.fn;
+identifier rule12.T;
+@@
+
+ {
+ ...
+ \(
+ fn(
+- vty->index,
++ T,
+ ...)
+ \|
+ fn(
+- vty->index
++ T
+ )
+ \)
+ ...
+ }
+
+/* special case ... */
+@rule30@
+identifier fn =~ "generic_(set|match)_";
+expression arg;
+@@
+
+ fn(arg,
+- arg->index,
++ VTY_GET_CONTEXT(route_map_index),
+ ...)
+
+/* and finally - PUSH_CONTEXT */
+@ rule99a exists @
+identifier tnode;
+identifier vexpr =~ "NULL";
+@@
+
+- vty->node = tnode;
+ ...
+- vty->index = vexpr;
++ VTY_PUSH_CONTEXT_NULL(tnode);
+
+@ rule99b exists @
+identifier tnode;
+expression vexpr;
+@@
+
+- vty->node = tnode;
+ ...
+- vty->index = vexpr;
++ VTY_PUSH_CONTEXT(tnode, vexpr);
+
diff --git a/tools/coccinelle/vty_json.cocci b/tools/coccinelle/vty_json.cocci
new file mode 100644
index 0000000..2f2f321
--- /dev/null
+++ b/tools/coccinelle/vty_json.cocci
@@ -0,0 +1,10 @@
+@@
+identifier vty;
+identifier json;
+constant fmt;
+@@
+
+-vty_out(vty, fmt, json_object_to_json_string_ext(json, ...));
+...
+-json_object_free(json);
++vty_json(vty, json);
diff --git a/tools/coccinelle/xcalloc-simple.cocci b/tools/coccinelle/xcalloc-simple.cocci
new file mode 100644
index 0000000..5be4daf
--- /dev/null
+++ b/tools/coccinelle/xcalloc-simple.cocci
@@ -0,0 +1,52 @@
+///
+/// Use zeroing allocator rather than allocator followed by memset with 0
+///
+/// This considers some simple cases that are common and easy to validate
+/// Note in particular that there are no ...s in the rule, so all of the
+/// matched code has to be contiguous
+///
+// Confidence: High
+// Copyright: (C) 2009-2010 Julia Lawall, Nicolas Palix, DIKU. GPLv2.
+// Copyright: (C) 2009-2010 Gilles Muller, INRIA/LiP6. GPLv2.
+// Copyright: (C) 2017 Himanshu Jha GPLv2.
+// Copyright: (C) 2019 Quentin Young. GPLv2.
+// URL: http://coccinelle.lip6.fr/rules/kzalloc.html
+// Options: --no-includes --include-headers
+//
+// Keywords: XMALLOC, XCALLOC
+// Version min: < 2.6.12 kmalloc
+// Version min: 2.6.14 kzalloc
+//
+
+virtual context
+virtual patch
+
+//----------------------------------------------------------
+// For context mode
+//----------------------------------------------------------
+
+@depends on context@
+type T, T2;
+expression x;
+expression E1;
+expression t;
+@@
+
+* x = (T)XMALLOC(t, E1);
+* memset((T2)x,0,E1);
+
+//----------------------------------------------------------
+// For patch mode
+//----------------------------------------------------------
+
+@depends on patch@
+type T, T2;
+expression x;
+expression E1;
+expression t;
+@@
+
+- x = (T)XMALLOC(t, E1);
++ x = (T)XCALLOC(t, E1);
+- memset((T2)x,0,E1);
+
diff --git a/tools/coccinelle/xcalloc-xmalloc.cocci b/tools/coccinelle/xcalloc-xmalloc.cocci
new file mode 100644
index 0000000..2a091d4
--- /dev/null
+++ b/tools/coccinelle/xcalloc-xmalloc.cocci
@@ -0,0 +1,17 @@
+// No need checking against NULL for XMALLOC/XCALLOC.
+// If that happens, we have more problems with memory.
+
+@@
+type T;
+T *ptr;
+@@
+
+ptr =
+(
+XCALLOC(...)
+|
+XMALLOC(...)
+)
+...
+- if (ptr == NULL)
+- return ...;
diff --git a/tools/coccinelle/xfree.cocci b/tools/coccinelle/xfree.cocci
new file mode 100644
index 0000000..eb38f0d
--- /dev/null
+++ b/tools/coccinelle/xfree.cocci
@@ -0,0 +1,122 @@
+/// Find a use after free.
+//# Values of variables may imply that some
+//# execution paths are not possible, resulting in false positives.
+//# Another source of false positives are macros such as
+//# SCTP_DBG_OBJCNT_DEC that do not actually evaluate their argument
+///
+// Confidence: Moderate
+// Copyright: (C) 2010-2012 Nicolas Palix. GPLv2.
+// Copyright: (C) 2010-2012 Julia Lawall, INRIA/LIP6. GPLv2.
+// Copyright: (C) 2010-2012 Gilles Muller, INRIA/LiP6. GPLv2.
+// Copyright: (C) 2019 Quentin Young. GPLv2.
+// URL: http://coccinelle.lip6.fr/
+// Comments:
+// Options: --no-includes --include-headers
+
+virtual org
+virtual report
+
+@free@
+expression E, t;
+position p1;
+@@
+
+* XFREE@p1(t, E)
+
+@print expression@
+constant char [] c;
+expression free.E,E2;
+type T;
+position p;
+identifier f;
+@@
+
+(
+ f(...,c,...,(T)E@p,...)
+|
+ E@p == E2
+|
+ E@p != E2
+|
+ E2 == E@p
+|
+ E2 != E@p
+|
+ !E@p
+|
+ E@p || ...
+)
+
+@sz@
+expression free.E;
+position p;
+@@
+
+ sizeof(<+...E@p...+>)
+
+@loop exists@
+expression E, t;
+identifier l;
+position ok;
+@@
+
+while (1) { ...
+* XFREE@ok(t, E)
+ ... when != break;
+ when != goto l;
+ when forall
+}
+
+@r exists@
+expression free.E, subE<=free.E, E2;
+expression E1;
+iterator iter;
+statement S;
+position free.p1!=loop.ok,p2!={print.p,sz.p};
+@@
+
+* XFREE@p1(t, E)
+...
+(
+ iter(...,subE,...) S // no use
+|
+ list_remove_head(E1,subE,...)
+|
+ subE = E2
+|
+ subE++
+|
+ ++subE
+|
+ --subE
+|
+ subE--
+|
+ &subE
+|
+ BUG(...)
+|
+ BUG_ON(...)
+|
+ return_VALUE(...)
+|
+ return_ACPI_STATUS(...)
+|
+ E@p2 // bad use
+)
+
+@script:python depends on org@
+p1 << free.p1;
+p2 << r.p2;
+@@
+
+cocci.print_main("kfree",p1)
+cocci.print_secs("ref",p2)
+
+@script:python depends on report@
+p1 << free.p1;
+p2 << r.p2;
+@@
+
+msg = "ERROR: reference preceded by free on line %s" % (p1[0].line)
+coccilib.report.print_report(p2[0],msg)
diff --git a/tools/coccinelle/xfreeaddr.cocci b/tools/coccinelle/xfreeaddr.cocci
new file mode 100644
index 0000000..c99c7ac
--- /dev/null
+++ b/tools/coccinelle/xfreeaddr.cocci
@@ -0,0 +1,33 @@
+/// Free of a structure field
+///
+// Confidence: High
+// Copyright: (C) 2013 Julia Lawall, INRIA/LIP6. GPLv2.
+// Copyright: (C) 2019 Quentin Young. GPLv2.
+// URL: http://coccinelle.lip6.fr/
+// Comments:
+// Options: --no-includes --include-headers
+
+virtual org
+virtual report
+virtual context
+
+@r depends on context || report || org @
+expression e, t;
+identifier f;
+position p;
+@@
+
+* XFREE@p(t, &e->f)
+
+@script:python depends on org@
+p << r.p;
+@@
+
+cocci.print_main("XFREE",p)
+
+@script:python depends on report@
+p << r.p;
+@@
+
+msg = "ERROR: invalid free of structure field"
+coccilib.report.print_report(p[0],msg)
diff --git a/tools/coccinelle/xmalloc_returnval.cocci b/tools/coccinelle/xmalloc_returnval.cocci
new file mode 100644
index 0000000..8e0ad10
--- /dev/null
+++ b/tools/coccinelle/xmalloc_returnval.cocci
@@ -0,0 +1,37 @@
+/// XMALLOC, XCALLOC etc either return non-null, or abort the program.
+/// Never nullcheck these.
+//
+// Copyright: (C) 2019 Quentin Young. GPLv2.
+
+virtual patch
+
+//----------------------------------------------------------
+// For patch mode
+//----------------------------------------------------------
+
+@depends on patch@
+identifier alloc;
+@@
+
+alloc = XMALLOC(...);
+
+...
+
+- if (alloc == NULL)
+- {
+- ...
+- }
+
+@depends on patch@
+identifier alloc;
+@@
+
+alloc = XCALLOC(...);
+
+...
+
+- if (alloc == NULL)
+- {
+- ...
+- }
+
diff --git a/tools/coccinelle/zlog_no_newline.cocci b/tools/coccinelle/zlog_no_newline.cocci
new file mode 100644
index 0000000..20cf9d2
--- /dev/null
+++ b/tools/coccinelle/zlog_no_newline.cocci
@@ -0,0 +1,20 @@
+// zlog_* should not have \n or \r at the end usually.
+// spatch --sp-file tools/coccinelle/zlog_no_newline.cocci --macro-file tools/cocci.h ./ 2>/dev/null
+
+@r@
+expression fmt;
+identifier func =~ "zlog_";
+position p;
+@@
+(
+ func(fmt)@p
+|
+ func(fmt, ...)@p
+)
+
+@script:python@
+fmt << r.fmt;
+p << r.p;
+@@
+if "\\n" in str(fmt) or "\\r" in str(fmt):
+ print("Newline in logging function detected %s:%s:%s:%s" % (p[0].file, p[0].line, p[0].column, fmt))
diff --git a/tools/coccinelle/zprivs.cocci b/tools/coccinelle/zprivs.cocci
new file mode 100644
index 0000000..11628a7
--- /dev/null
+++ b/tools/coccinelle/zprivs.cocci
@@ -0,0 +1,76 @@
+@@
+identifier change;
+identifier end;
+expression E, f, g;
+iterator name frr_with_privs;
+@@
+
+- if (E.change(ZPRIVS_RAISE))
+- f;
++ frr_with_privs(&E) {
+ <+...
+- goto end;
++ break;
+ ...+>
+- end:
+- if (E.change(ZPRIVS_LOWER))
+- g;
++ }
+
+@@
+identifier change, errno, safe_strerror, exit;
+expression E, f1, f2, f3, ret, fn;
+iterator name frr_with_privs;
+@@
+
+ if (E.change(ZPRIVS_RAISE))
+ f1;
+ ...
+ if (...) {
+- int save_errno = errno;
+ ...
+- if (E.change(ZPRIVS_LOWER))
+- f2;
+ ...
+- safe_strerror(save_errno)
++ safe_strerror(errno)
+ ...
+ \( return ret; \| exit(ret); \)
+ }
+ ...
+ if (E.change(ZPRIVS_LOWER))
+ f3;
+
+@@
+identifier change;
+expression E, f1, f2, f3, ret;
+iterator name frr_with_privs;
+@@
+
+ if (E.change(ZPRIVS_RAISE))
+ f1;
+ ...
+ if (...) {
+ ...
+- if (E.change(ZPRIVS_LOWER))
+- f2;
+ ...
+ return ret;
+ }
+ ...
+ if (E.change(ZPRIVS_LOWER))
+ f3;
+
+@@
+identifier change;
+expression E, f, g;
+iterator name frr_with_privs;
+@@
+
+- if (E.change(ZPRIVS_RAISE))
+- f;
++ frr_with_privs(&E) {
+ ...
+- if (E.change(ZPRIVS_LOWER))
+- g;
++ }
diff --git a/tools/convert-fixedwidth.sh b/tools/convert-fixedwidth.sh
new file mode 100755
index 0000000..bb6011e
--- /dev/null
+++ b/tools/convert-fixedwidth.sh
@@ -0,0 +1,44 @@
+#!/bin/bash
+# This script converts nonstandard fixed-width integer types found in FRR to
+# C99 standard types.
+USAGE="./$(basename "$0")"
+USAGE+=$' <src-path> -- convert nonstandard fixed-width integer types found in FRR to C99 standard types\n'
+USAGE+=$'<src-path> - a directory containing C source, or a C source file\n'
+if [ $# -eq 0 ]; then
+ printf "%s" "$USAGE"
+ exit 1
+fi
+
+FRRTREE=$1
+
+if [[ -d $FRRTREE ]]; then
+ SOURCES=$(find $FRRTREE -type f -name '*.[ch]')
+elif [[ -f $FRRTREE ]]; then
+ SOURCES="$FRRTREE"
+ SOURCES+=$'\n'
+else
+ printf "%s" "$USAGE"
+ exit 1
+fi
+
+printf "%s" "$SOURCES" | while read line ; do
+ printf "Processing $line "
+ sed -i -e 's/u_int\([0-9]\{1,3\}\)_t/uint\1_t/g' $line
+ printf "."
+ sed -i -e 's/\([^a-z_]\)u_char\([^a-z_]\|$\)/\1uint8_t\2/g' $line
+ printf "."
+ sed -i -e 's/\([^a-z_]\)u_short\([^a-z_]\|$\)/\1unsigned short\2/g' $line
+ printf "."
+ sed -i -e 's/\([^a-z_]\)u_int\([^a-z_]\|$\)/\1unsigned int\2/g' $line
+ printf "."
+ sed -i -e 's/\([^a-z_]\)u_long\([^a-z_]\|$\)/\1unsigned long\2/g' $line
+ printf "."
+ sed -i -e 's/^u_char /uint8_t /g' $line
+ printf "."
+ sed -i -e 's/^u_short /unsigned short /g' $line
+ printf "."
+ sed -i -e 's/^u_int /unsigned int /g' $line
+ printf "."
+ sed -i -e 's/^u_long /unsigned long /g' $line
+ printf ".\n"
+done
diff --git a/tools/emacs.dir-locals.el b/tools/emacs.dir-locals.el
new file mode 100644
index 0000000..b2d7cf3
--- /dev/null
+++ b/tools/emacs.dir-locals.el
@@ -0,0 +1,8 @@
+;;; Directory Local Variables
+;;; For more information see (info "(emacs) Directory Variables")
+;;; Match project coding conventions
+
+((c-mode . ((indent-tabs-mode . t)
+ (show-trailing-whitespace . t)
+ (c-basic-offset . 8)))
+ (json-mode . ((js-indent-level 4))))
diff --git a/tools/etc/frr/daemons b/tools/etc/frr/daemons
new file mode 100644
index 0000000..c487e7e
--- /dev/null
+++ b/tools/etc/frr/daemons
@@ -0,0 +1,126 @@
+# This file tells the frr package which daemons to start.
+#
+# Sample configurations for these daemons can be found in
+# /usr/share/doc/frr/examples/.
+#
+# ATTENTION:
+#
+# When activating a daemon for the first time, a config file, even if it is
+# empty, has to be present *and* be owned by the user and group "frr", else
+# the daemon will not be started by /etc/init.d/frr. The permissions should
+# be u=rw,g=r,o=.
+# When using "vtysh" such a config file is also needed. It should be owned by
+# group "frrvty" and set to ug=rw,o= though. Check /etc/pam.d/frr, too.
+#
+# The watchfrr, zebra and staticd daemons are always started.
+#
+bgpd=no
+ospfd=no
+ospf6d=no
+ripd=no
+ripngd=no
+isisd=no
+pimd=no
+pim6d=no
+ldpd=no
+nhrpd=no
+eigrpd=no
+babeld=no
+sharpd=no
+pbrd=no
+bfdd=no
+fabricd=no
+vrrpd=no
+pathd=no
+
+#
+# If this option is set the /etc/init.d/frr script automatically loads
+# the config via "vtysh -b" when the servers are started.
+# Check /etc/pam.d/frr if you intend to use "vtysh"!
+#
+vtysh_enable=yes
+zebra_options=" -A 127.0.0.1 -s 90000000"
+mgmtd_options=" -A 127.0.0.1"
+bgpd_options=" -A 127.0.0.1"
+ospfd_options=" -A 127.0.0.1"
+ospf6d_options=" -A ::1"
+ripd_options=" -A 127.0.0.1"
+ripngd_options=" -A ::1"
+isisd_options=" -A 127.0.0.1"
+pimd_options=" -A 127.0.0.1"
+pim6d_options=" -A ::1"
+ldpd_options=" -A 127.0.0.1"
+nhrpd_options=" -A 127.0.0.1"
+eigrpd_options=" -A 127.0.0.1"
+babeld_options=" -A 127.0.0.1"
+sharpd_options=" -A 127.0.0.1"
+pbrd_options=" -A 127.0.0.1"
+staticd_options="-A 127.0.0.1"
+bfdd_options=" -A 127.0.0.1"
+fabricd_options="-A 127.0.0.1"
+vrrpd_options=" -A 127.0.0.1"
+pathd_options=" -A 127.0.0.1"
+
+
+# If you want to pass a common option to all daemons, you can use the
+# "frr_global_options" variable.
+#
+#frr_global_options=""
+
+
+# The list of daemons to watch is automatically generated by the init script.
+# This variable can be used to pass options to watchfrr that will be passed
+# prior to the daemon list.
+#
+# To make watchfrr create/join the specified netns, add the the "--netns"
+# option here. It will only have an effect in /etc/frr/<somename>/daemons, and
+# you need to start FRR with "/usr/lib/frr/frrinit.sh start <somename>".
+#
+#watchfrr_options=""
+
+
+# configuration profile
+#
+#frr_profile="traditional"
+#frr_profile="datacenter"
+
+
+# This is the maximum number of FD's that will be available. Upon startup this
+# is read by the control files and ulimit is called. Uncomment and use a
+# reasonable value for your setup if you are expecting a large number of peers
+# in say BGP.
+#
+#MAX_FDS=1024
+
+# Uncomment this option if you want to run FRR as a non-root user. Note that
+# you should know what you are doing since most of the daemons need root
+# to work. This could be useful if you want to run FRR in a container
+# for instance.
+# FRR_NO_ROOT="yes"
+
+# For any daemon, you can specify a "wrap" command to start instead of starting
+# the daemon directly. This will simply be prepended to the daemon invocation.
+# These variables have the form daemon_wrap, where 'daemon' is the name of the
+# daemon (the same pattern as the daemon_options variables).
+#
+# Note that when daemons are started, they are told to daemonize with the `-d`
+# option. This has several implications. For one, the init script expects that
+# when it invokes a daemon, the invocation returns immediately. If you add a
+# wrap command here, it must comply with this expectation and daemonize as
+# well, or the init script will never return. Furthermore, because daemons are
+# themselves daemonized with -d, you must ensure that your wrapper command is
+# capable of following child processes after a fork() if you need it to do so.
+#
+# If your desired wrapper does not support daemonization, you can wrap it with
+# a utility program that daemonizes programs, such as 'daemonize'. An example
+# of this might look like:
+#
+# bgpd_wrap="/usr/bin/daemonize /usr/bin/mywrapper"
+#
+# This is particularly useful for programs which record processes but lack
+# daemonization options, such as perf and rr.
+#
+# If you wish to wrap all daemons in the same way, you may set the "all_wrap"
+# variable.
+#
+#all_wrap=""
diff --git a/tools/etc/frr/daemons.conf b/tools/etc/frr/daemons.conf
new file mode 100644
index 0000000..70b96a9
--- /dev/null
+++ b/tools/etc/frr/daemons.conf
@@ -0,0 +1 @@
+# this file is deprecated, please use "daemons" instead.
diff --git a/tools/etc/frr/frr.conf b/tools/etc/frr/frr.conf
new file mode 100644
index 0000000..5ee6d15
--- /dev/null
+++ b/tools/etc/frr/frr.conf
@@ -0,0 +1,10 @@
+# default to using syslog. /etc/rsyslog.d/45-frr.conf places the log in
+# /var/log/frr/frr.log
+#
+# Note:
+# FRR's configuration shell, vtysh, dynamically edits the live, in-memory
+# configuration while FRR is running. When instructed, vtysh will persist the
+# live configuration to this file, overwriting its contents. If you want to
+# avoid this, you can edit this file manually before starting FRR, or instruct
+# vtysh to write configuration to a different file.
+log syslog informational
diff --git a/tools/etc/frr/support_bundle_commands.conf b/tools/etc/frr/support_bundle_commands.conf
new file mode 100644
index 0000000..b7a1708
--- /dev/null
+++ b/tools/etc/frr/support_bundle_commands.conf
@@ -0,0 +1,259 @@
+# FRR Support Bundle Command List
+# Do Not modify the lines that start with
+# PROC_NAME, CMD_LIST_START and CMD_LIST_END
+# Add the new command for each process between
+# CMD_LIST_START and CMD_LIST_END lines
+
+# BGP Support Bundle Command List
+PROC_NAME:bgp
+CMD_LIST_START
+show bgp summary
+show bgp ipv4 uni
+show bgp ipv4 neighbors
+show bgp ipv4 summary
+show bgp ipv4 statistics
+
+show bgp ipv4 update-groups advertise-queue
+show bgp ipv4 update-groups advertised-routes
+show bgp ipv4 update-groups packet-queue
+show bgp ipv4 update-groups statistics
+show bgp peer-group
+show bgp memory
+
+show bgp ipv6
+show bgp ipv6 neighbors
+show bgp ipv6 summary
+show bgp ipv6 update-groups advertise-queue
+show bgp ipv6 update-groups advertised-routes
+show bgp ipv6 update-groups packet-queue
+show bgp ipv6 update-groups statistics
+show bgp ipv6 statistics
+show bgp martian next-hop
+show bgp nexthop
+
+show bgp vrf all summary
+show bgp vrf all ipv4
+show bgp vrf all ipv6
+show bgp vrf all neighbors
+
+show bgp evpn route
+show bgp l2vpn evpn route vni all
+show bgp l2vpn evpn vni
+show bgp l2vpn evpn import-rt
+show bgp l2vpn evpn vrf-import-rt
+show bgp l2vpn evpn all overlay
+show bgp l2vpn evpn summary
+show bgp l2vpn evpn route detail
+show bgp l2vpn evpn vni remote-ip-hash
+show bgp l2vpn evpn vni-svi-hash
+
+show evpn
+show evpn arp-cache vni all detail
+show evpn mac vni all detail
+show evpn next-hops vni all
+show evpn rmac vni all
+show evpn vni detail
+
+CMD_LIST_END
+
+# Zebra Support Bundle Command List
+PROC_NAME:zebra
+CMD_LIST_START
+show zebra
+show zebra client summary
+show zebra router table summary
+show ip zebra route dump
+show ipv6 zebra route dump
+show ip nht vrf all
+show ipv6 nht vrf all
+show ip route vrf all
+show ipv6 route vrf all
+show ip fib
+show ipv6 fib
+show nexthop-group rib
+show route-map
+show memory
+show interface vrf all
+show vrf
+show work-queues
+show debugging hashtable
+show running-config
+show thread cpu
+show thread poll
+show thread timers
+show daemons
+show version
+CMD_LIST_END
+
+# OSPF Support Bundle Command List
+PROC_NAME:ospf
+CMD_LIST_START
+show ip ospf vrf all
+show ip ospf vrfs
+
+show ip ospf vrf all interface
+show ip ospf vrf all interface traffic
+show ip ospf vrf all neighbor
+show ip ospf vrf all neighbor detail all
+show ip ospf vrf all graceful-restart helper detail
+
+show ip ospf vrf all border-routers
+show ip ospf vrf all summary-address detail
+
+show ip ospf vrf all database
+show ip ospf vrf all database router
+show ip ospf vrf all database network
+show ip ospf vrf all database summary
+show ip ospf vrf all database asbr-summary
+show ip ospf vrf all database external
+show ip ospf vrf all database opaque-area
+show ip ospf vrf all database opaque-as
+show ip ospf vrf all database opaque-link
+show ip ospf vrf all database nssa-external
+show ip ospf database segment-routing
+show ip ospf vrf all database max-age
+show ip ospf vrf all database self-originate
+show ip ospf vrf all route
+
+show ip ospf mpls ldp-sync
+show ip ospf mpls ldp-sync interface all
+
+show ip ospf vrf all mpls-te interface
+show ip ospf mpls-te database verbose
+show ip ospf mpls-te router
+
+show ip ospf router-info
+show ip ospf router-info pce
+CMD_LIST_END
+
+# RIP Support Bundle Command List
+# PROC_NAME:rip
+# CMD_LIST_START
+# CMD_LIST_END
+
+# ISIS Support Bundle Command List
+# PROC_NAME:isis
+# CMD_LIST_START
+# CMD_LIST_END
+
+# BFD Support Bundle Command List
+# PROC_NAME:bfd
+# CMD_LIST_START
+# CMD_LIST_END
+
+# STATIC Support Bundle Command List
+# PROC_NAME:static
+# CMD_LIST_START
+# CMD_LIST_END
+
+# PIM Support Bundle Command List
+PROC_NAME:pim
+CMD_LIST_START
+show ip multicast
+show ip pim interface
+show ip pim interface traffic
+show ip pim nexthop
+show ip pim neighbor
+show ip pim bsr
+show ip pim bsrp-info
+show ip pim bsm-database
+show ip pim rp-info
+show ip igmp groups
+show ip igmp interface
+show ip igmp join
+show ip igmp sources
+show ip igmp statistics
+show ip pim upstream
+show ip mroute
+show ip pim join
+show ip pim state
+show ip pim statistics
+show ip pim rpf
+CMD_LIST_END
+
+# OSPFv3 Support Bundle Command List
+PROC_NAME:ospf6
+CMD_LIST_START
+show ipv6 ospf6 vrf all
+show ipv6 ospf6 vrfs
+show ipv6 ospf6 vrf all border-routers
+show ipv6 ospf6 vrf all border-routers detail
+show ipv6 ospf6 vrf all database
+show ipv6 ospf6 vrf all database detail
+show ipv6 ospf6 vrf all database dump
+show ipv6 ospf6 vrf all database internal
+show ipv6 ospf6 vrf all database router detail
+show ipv6 ospf6 vrf all database network detail
+show ipv6 ospf6 vrf all database inter-prefix detail
+show ipv6 ospf6 vrf all database inter-router detail
+show ipv6 ospf6 vrf all database intra-prefix detail
+show ipv6 ospf6 vrf all database link detail
+show ipv6 ospf6 vrf all database as-external detail
+show ipv6 ospf6 vrf all database self-originate detail
+show ipv6 ospf6 vrf all database type-7 detail
+show ipv6 ospf6 vrf all interface
+show ipv6 ospf6 vrf all interface prefix
+show ipv6 ospf6 vrf all interface traffic
+show ipv6 ospf6 vrf all linkstate detail
+show ipv6 ospf6 vrf all neighbor
+show ipv6 ospf6 vrf all neighbor drchoice
+show ipv6 ospf6 vrf all neighbor detail
+show ipv6 ospf6 vrf all redistribute
+show ipv6 ospf6 vrf all route
+show ipv6 ospf6 vrf all route external-1
+show ipv6 ospf6 vrf all route external-2
+show ipv6 ospf6 vrf all route inter-area
+show ipv6 ospf6 vrf all route intra-area
+show ipv6 ospf6 vrf all route detail
+show ipv6 ospf6 vrf all route summary
+show ipv6 ospf6 vrf all spf tree
+show ipv6 ospf6 vrf all summary-address detail
+show ipv6 ospf6 zebra
+CMD_LIST_END
+
+#PIMv6 Support Bundle Command List
+PROC_NAME:pim6
+CMD_LIST_START
+show ipv6 pim channel
+show ipv6 pim interface
+show ipv6 pim interface traffic
+show ipv6 pim join
+show ipv6 jp-agg
+show ipv6 pim nexthop
+show ipv6 pim nexthop-lookup
+show ipv6 pim neighbor
+show ipv6 pim local-membership
+show ipv6 pim rp-info
+show ipv6 pim rpf
+show ipv6 pim secondary
+show ipv6 pim state
+show ipv6 pim statistics
+show ipv6 pim upstream
+show ipv6 pim upstream-join-desired
+show ipv6 pim upstream-rpf
+show ipv6 mld interface
+show ipv6 mld statistics
+show ipv6 mld joins
+show ipv6 mld groups
+show ipv6 multicast
+show ipv6 mroute
+show ipv6 pim bsr
+show ipv6 pim bsrp-info
+show ipv6 pim bsm-databases
+CMD_LIST_END
+
+#MGMT Support Bundle Command List
+PROC_NAME:mgmt
+CMD_LIST_START
+show mgmt backend-adapter all
+show mgmt backend-yang-xpath-registry
+show mgmt commit-history
+show mgmt datastore all
+show mgmt datastore-contents candidate json
+show mgmt datastore-contents running json
+show mgmt frontend-adapter all detail
+show mgmt get-config candidate /
+show mgmt get-config running /
+show mgmt transaction all
+show mgmt yang-xpath-subscription /
+CMD_LIST_END
diff --git a/tools/etc/frr/vtysh.conf b/tools/etc/frr/vtysh.conf
new file mode 100644
index 0000000..e0ab9cb
--- /dev/null
+++ b/tools/etc/frr/vtysh.conf
@@ -0,0 +1 @@
+service integrated-vtysh-config
diff --git a/tools/etc/iproute2/rt_protos.d/frr.conf b/tools/etc/iproute2/rt_protos.d/frr.conf
new file mode 100644
index 0000000..bbb358f
--- /dev/null
+++ b/tools/etc/iproute2/rt_protos.d/frr.conf
@@ -0,0 +1,14 @@
+# Additional protocol strings defined by frr for each of its daemons
+
+186 bgp
+187 isis
+188 ospf
+189 rip
+190 ripng
+191 nhrp
+192 eigrp
+193 ldp
+194 sharp
+195 pbr
+196 static
+197 openfabric
diff --git a/tools/etc/logrotate.d/frr b/tools/etc/logrotate.d/frr
new file mode 100644
index 0000000..735af65
--- /dev/null
+++ b/tools/etc/logrotate.d/frr
@@ -0,0 +1,27 @@
+/var/log/frr/*.log {
+ size 500k
+ sharedscripts
+ missingok
+ compress
+ rotate 14
+ create 0640 frr frr
+
+ postrotate
+ pid=$(lsof -t -a -c /syslog/ /var/log/frr/* 2>/dev/null)
+ if [ -n "$pid" ]
+ then # using syslog
+ kill -HUP $pid
+ fi
+ # in case using file logging; if switching back and forth
+ # between file and syslog, rsyslogd might still have file
+ # open, as well as the daemons, so always signal the daemons.
+ # It's safe, a NOP if (only) syslog is being used.
+ for i in babeld bgpd eigrpd isisd ldpd nhrpd ospf6d ospfd sharpd \
+ pimd pim6d ripd ripngd zebra pathd pbrd staticd bfdd fabricd vrrpd; do
+ if [ -e /var/run/frr/$i.pid ] ; then
+ pids="$pids $(cat /var/run/frr/$i.pid)"
+ fi
+ done
+ [ -n "$pids" ] && kill -USR1 $pids || true
+ endscript
+}
diff --git a/tools/etc/rsyslog.d/45-frr.conf b/tools/etc/rsyslog.d/45-frr.conf
new file mode 100644
index 0000000..75b20d7
--- /dev/null
+++ b/tools/etc/rsyslog.d/45-frr.conf
@@ -0,0 +1,48 @@
+# The lines below cause all FRR daemons and process to go
+# to /var/log/frr/frr.log, then drops the message so it does
+# not also go to /var/log/syslog, so the messages are not duplicated
+
+$outchannel frr_log,/var/log/frr/frr.log
+if $programname == 'babeld' or
+ $programname == 'bgpd' or
+ $programname == 'bfdd' or
+ $programname == 'eigrpd' or
+ $programname == 'frr' or
+ $programname == 'isisd' or
+ $programname == 'fabricd' or
+ $programname == 'ldpd' or
+ $programname == 'nhrpd' or
+ $programname == 'ospf6d' or
+ $programname == 'ospfd' or
+ $programname == 'pimd' or
+ $programname == 'pim6d' or
+ $programname == 'pathd' or
+ $programname == 'pbrd' or
+ $programname == 'ripd' or
+ $programname == 'ripngd' or
+ $programname == 'vrrpd' or
+ $programname == 'watchfrr' or
+ $programname == 'zebra'
+ then :omfile:$frr_log
+
+if $programname == 'babeld' or
+ $programname == 'bgpd' or
+ $programname == 'bfdd' or
+ $programname == 'eigrpd' or
+ $programname == 'frr' or
+ $programname == 'isisd' or
+ $programname == 'fabricd' or
+ $programname == 'ldpd' or
+ $programname == 'nhrpd' or
+ $programname == 'ospf6d' or
+ $programname == 'ospfd' or
+ $programname == 'pimd' or
+ $programname == 'pim6d' or
+ $programname == 'pathd' or
+ $programname == 'pbrd' or
+ $programname == 'ripd' or
+ $programname == 'ripngd' or
+ $programname == 'vrrpd' or
+ $programname == 'watchfrr' or
+ $programname == 'zebra'
+ then stop
diff --git a/tools/fixup-deprecated.py b/tools/fixup-deprecated.py
new file mode 100755
index 0000000..ad3ae54
--- /dev/null
+++ b/tools/fixup-deprecated.py
@@ -0,0 +1,82 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+# SPDX-License-Identifier: NONE
+#
+# Script used to replace deprecated quagga/frr mactors/types/etc.
+#
+# loosly based on indent.py, 2017 by David Lamparter
+# 2018 by Lou Berger, placed in public domain
+
+import sys, re, subprocess, os
+
+
+class replaceEntry:
+ compiled = None # compiled regex
+ repl = None # regex
+
+ def __init__(self, c, r):
+ self.compiled = c
+ self.repl = r
+
+
+rList = [
+ # old #define VNL, VTYNL, VTY_NEWLINE
+ replaceEntry(re.compile(r"(VNL|VTYNL|VTY_NEWLINE)"), r'"\\n"'),
+ # old #define VTY_GET_INTEGER(desc, v, str)
+ # old #define VTY_GET_INTEGER_RANGE(desc, v, str, min, max)
+ # old #define VTY_GET_ULONG(desc, v, str)
+ replaceEntry(
+ re.compile(
+ r"(VTY_GET_INTEGER(_RANGE|)|VTY_GET_ULONG)[\s\(]*(.*?)\s*,\s*(.*?)\s*,\s*(.*?)(\s*|)(\)|,).*?;",
+ re.M | re.S,
+ ),
+ r"(\4) = strtoul((\5), NULL, 10);\t/* \3 */",
+ ),
+ # old #define VTY_GET_ULL(desc, v, str)
+ replaceEntry(
+ re.compile(
+ r"VTY_GET_ULL[\s\(]*(.*?)\s*,\s*(.*?)\s*,\s*(.*?)(\s*|)(\)|,).*?;",
+ re.M | re.S,
+ ),
+ r"(\2) = strtoull((\3), NULL, 10);\t/* \1 */",
+ ),
+ # old #define VTY_GET_IPV4_ADDRESS(desc, v, str)
+ replaceEntry(
+ re.compile(
+ r"VTY_GET_IPV4_ADDRESS[\s\(]*(.*?)\s*,\s*(.*?)\s*,\s*(.*?)(\s*|)(\)|,).*?;",
+ re.M | re.S,
+ ),
+ r"inet_aton((\3), &(\2));\t/* \1 */",
+ ),
+ # old #define VTY_GET_IPV4_PREFIX(desc, v, str)
+ replaceEntry(
+ re.compile(
+ r"VTY_GET_IPV4_PREFIX[\s\(]*(.*?)\s*,\s*(.*?)\s*,\s*(.*?)(\s*|)(\)|,).*?;",
+ re.M | re.S,
+ ),
+ r"str2prefix_ipv4((\3), &(\2));\t/* \1 */",
+ ),
+ # old #define vty_outln(vty, str, ...)
+ replaceEntry(
+ re.compile(r'vty_outln[\s\(]*(.*?)\s*,\s*(".*?"|.*?)\s*(\)|,)', re.M | re.S),
+ r'vty_out(\1, \2 "\\n"\3',
+ ),
+]
+
+
+def fixup_file(fn):
+ with open(fn, "r") as fd:
+ text = fd.read()
+
+ for re in rList:
+ text = re.compiled.sub(re.repl, text)
+
+ tmpname = fn + ".fixup"
+ with open(tmpname, "w") as ofd:
+ ofd.write(text)
+ os.rename(tmpname, fn)
+
+
+if __name__ == "__main__":
+ for fn in sys.argv[1:]:
+ fixup_file(fn)
diff --git a/tools/frr-llvm-cg.c b/tools/frr-llvm-cg.c
new file mode 100644
index 0000000..3a7222e
--- /dev/null
+++ b/tools/frr-llvm-cg.c
@@ -0,0 +1,950 @@
+// SPDX-License-Identifier: Unlicense
+/* based on example code: https://github.com/sheredom/llvm_bc_parsing_example
+ * which came under the above (un-)license. does not depend on any FRR
+ * pieces, so no reason to change the license.
+ *
+ * please note that while included in the FRR sources, this tool is in no way
+ * supported or maintained by the FRR community. it is provided as a
+ * "convenience"; while it worked at some point (using LLVM 8 / 9), it may
+ * easily break with a future LLVM version or any other factors.
+ *
+ * 2020-05-04, David Lamparter
+ */
+
+#include <string.h>
+#include <strings.h>
+#include <stdbool.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <errno.h>
+#include <assert.h>
+
+#include <llvm-c/BitReader.h>
+#include <llvm-c/BitWriter.h>
+#include <llvm-c/Core.h>
+
+#include <json-c/json.h>
+
+#include "frr-llvm-debuginfo.h"
+
+/* if you want to use this without the special FRRouting defines,
+ * remove the following #define
+ */
+#define FRR_SPECIFIC
+
+static struct dbginfo *dbginfo;
+
+static void dbgloc_add(struct json_object *jsobj, LLVMValueRef obj)
+{
+ unsigned file_len = 0;
+ const char *file = LLVMGetDebugLocFilename(obj, &file_len);
+ unsigned line = LLVMGetDebugLocLine(obj);
+
+ if (!file)
+ file = "???", file_len = 3;
+ else if (file[0] == '.' && file[1] == '/')
+ file += 2, file_len -= 2;
+
+ json_object_object_add(jsobj, "filename",
+ json_object_new_string_len(file, file_len));
+ json_object_object_add(jsobj, "line", json_object_new_int64(line));
+}
+
+static struct json_object *js_get_or_make(struct json_object *parent,
+ const char *key,
+ struct json_object *(*maker)(void))
+{
+ struct json_object *ret;
+
+ ret = json_object_object_get(parent, key);
+ if (ret)
+ return ret;
+ ret = maker();
+ json_object_object_add(parent, key, ret);
+ return ret;
+}
+
+static bool try_struct_fptr(struct json_object *js_call, LLVMValueRef gep,
+ const char *prefix)
+{
+ unsigned long long val = 0;
+ bool ret = false;
+ LLVMTypeRef ptrtype = LLVMTypeOf(LLVMGetOperand(gep, 0));
+ LLVMValueRef idx;
+
+ /* middle steps like struct a -> struct b a_member; -> fptr */
+ for (int i = 1; ptrtype && i < LLVMGetNumOperands(gep) - 1; i++) {
+ if (LLVMGetTypeKind(ptrtype) == LLVMPointerTypeKind
+ || LLVMGetTypeKind(ptrtype) == LLVMArrayTypeKind
+ || LLVMGetTypeKind(ptrtype) == LLVMVectorTypeKind) {
+ ptrtype = LLVMGetElementType(ptrtype);
+ continue;
+ }
+
+ if (LLVMGetTypeKind(ptrtype) != LLVMStructTypeKind)
+ return false;
+
+ idx = LLVMGetOperand(gep, i);
+ if (!LLVMIsConstant(idx))
+ return false;
+ val = LLVMConstIntGetZExtValue(idx);
+
+ unsigned n = LLVMGetNumContainedTypes(ptrtype);
+ LLVMTypeRef arr[n];
+
+ if (val > n)
+ return false;
+
+ LLVMGetSubtypes(ptrtype, arr);
+ ptrtype = arr[val];
+ }
+
+ if (!ptrtype)
+ return false;
+
+ idx = LLVMGetOperand(gep, LLVMGetNumOperands(gep) - 1);
+ if (!LLVMIsConstant(idx))
+ return false;
+
+ val = LLVMConstIntGetZExtValue(idx);
+
+ char *sname = NULL, *mname = NULL;
+
+ if (dbginfo_struct_member(dbginfo, ptrtype, val, &sname, &mname)) {
+ fprintf(stderr, "%s: call to struct %s->%s\n", prefix, sname,
+ mname);
+
+ json_object_object_add(js_call, "type",
+ json_object_new_string("struct_memb"));
+ json_object_object_add(js_call, "struct",
+ json_object_new_string(sname));
+ json_object_object_add(js_call, "member",
+ json_object_new_string(mname));
+ ret = true;
+ }
+ free(sname);
+ free(mname);
+
+ return ret;
+}
+
+static bool details_fptr_vars = false;
+static bool details_fptr_consts = true;
+
+enum called_fn {
+ FN_GENERIC = 0,
+ FN_NONAME,
+ FN_INSTALL_ELEMENT,
+ FN_THREAD_ADD,
+};
+
+static void walk_const_fptrs(struct json_object *js_call, LLVMValueRef value,
+ const char *prefix, bool *hdr_written)
+{
+ LLVMTypeRef type;
+ LLVMValueKind kind;
+
+ if (LLVMIsAGlobalVariable(value)) {
+ type = LLVMGlobalGetValueType(value);
+ value = LLVMGetInitializer(value);
+ } else {
+ type = LLVMTypeOf(value);
+ }
+
+ if (LLVMIsAFunction(value)) {
+ struct json_object *js_fptrs;
+
+ js_fptrs = js_get_or_make(js_call, "funcptrs",
+ json_object_new_array);
+
+ size_t fn_len;
+ const char *fn_name = LLVMGetValueName2(value, &fn_len);
+
+ size_t curlen = json_object_array_length(js_fptrs);
+ struct json_object *jsobj;
+ const char *s;
+
+ for (size_t i = 0; i < curlen; i++) {
+ jsobj = json_object_array_get_idx(js_fptrs, i);
+ s = json_object_get_string(jsobj);
+
+ if (s && !strcmp(s, fn_name))
+ return;
+ }
+
+ if (details_fptr_consts && !*hdr_written) {
+ fprintf(stderr,
+ "%s: calls function pointer from constant or global data\n",
+ prefix);
+ *hdr_written = true;
+ }
+ if (details_fptr_consts)
+ fprintf(stderr, "%s- constant: %.*s()\n",
+ prefix, (int)fn_len, fn_name);
+
+ json_object_array_add(js_fptrs,
+ json_object_new_string_len(fn_name,
+ fn_len));
+ return;
+ }
+
+ kind = LLVMGetValueKind(value);
+
+ unsigned len;
+ char *dump;
+
+ switch (kind) {
+ case LLVMUndefValueValueKind:
+ case LLVMConstantAggregateZeroValueKind:
+ case LLVMConstantPointerNullValueKind:
+ /* null pointer / array - ignore */
+ break;
+
+ case LLVMConstantIntValueKind:
+ /* integer - ignore */
+ break;
+
+ case LLVMConstantStructValueKind:
+ len = LLVMCountStructElementTypes(type);
+ for (unsigned i = 0; i < len; i++)
+ walk_const_fptrs(js_call, LLVMGetOperand(value, i),
+ prefix, hdr_written);
+ break;
+
+ case LLVMConstantArrayValueKind:
+ len = LLVMGetArrayLength(type);
+ for (unsigned i = 0; i < len; i++)
+ walk_const_fptrs(js_call, LLVMGetOperand(value, i),
+ prefix, hdr_written);
+ return;
+
+ case LLVMConstantExprValueKind:
+ switch (LLVMGetConstOpcode(value)) {
+ case LLVMGetElementPtr:
+ if (try_struct_fptr(js_call, value, prefix)) {
+ *hdr_written = true;
+ return;
+ }
+
+ fprintf(stderr,
+ "%s: calls function pointer from unhandled const GEP\n",
+ prefix);
+ *hdr_written = true;
+ /* fallthru */
+ default:
+ /* to help the user / development */
+ if (!*hdr_written) {
+ fprintf(stderr,
+ "%s: calls function pointer from constexpr\n",
+ prefix);
+ *hdr_written = true;
+ }
+ dump = LLVMPrintValueToString(value);
+ fprintf(stderr, "%s- [opcode=%d] %s\n", prefix,
+ LLVMGetConstOpcode(value), dump);
+ LLVMDisposeMessage(dump);
+ }
+ return;
+
+ default:
+ /* to help the user / development */
+ if (!*hdr_written) {
+ fprintf(stderr,
+ "%s: calls function pointer from constant or global data\n",
+ prefix);
+ *hdr_written = true;
+ }
+ dump = LLVMPrintValueToString(value);
+ fprintf(stderr,
+ "%s- value could not be processed:\n"
+ "%s- [kind=%d] %s\n",
+ prefix, prefix, kind, dump);
+ LLVMDisposeMessage(dump);
+ return;
+ }
+ return;
+}
+
+#ifdef FRR_SPECIFIC
+static bool is_thread_sched(const char *name, size_t len)
+{
+#define thread_prefix "_"
+ static const char *const names[] = {
+ thread_prefix "event_add_read_write",
+ thread_prefix "event_add_timer",
+ thread_prefix "event_add_timer_msec",
+ thread_prefix "event_add_timer_tv",
+ thread_prefix "event_add_event",
+ thread_prefix "event_execute",
+ };
+ size_t i;
+
+ for (i = 0; i < sizeof(names) / sizeof(names[0]); i++) {
+ if (strlen(names[i]) != len)
+ continue;
+ if (!memcmp(names[i], name, len))
+ return true;
+ }
+ return false;
+}
+#endif
+
+static bool _check_val(bool cond, const char *text, LLVMValueRef dumpval)
+{
+ if (cond)
+ return true;
+
+ char *dump = LLVMPrintValueToString(dumpval);
+ fprintf(stderr, "check failed: %s\ndump:\n\t%s\n", text, dump);
+ LLVMDisposeMessage(dump);
+ return false;
+}
+
+#define check_val(cond, dump) \
+ if (!_check_val(cond, #cond, dump)) \
+ return;
+
+static char *get_string(LLVMValueRef value)
+{
+ if (!LLVMIsAConstant(value))
+ return strdup("!NOT-A-CONST");
+
+ if (LLVMGetValueKind(value) == LLVMConstantExprValueKind
+ && LLVMGetConstOpcode(value) == LLVMGetElementPtr) {
+ value = LLVMGetOperand(value, 0);
+
+ if (!LLVMIsAConstant(value))
+ return strdup("!NOT-A-CONST-2");
+ }
+
+ if (LLVMIsAGlobalVariable(value))
+ value = LLVMGetInitializer(value);
+
+ size_t len = 0;
+ const char *sval = LLVMGetAsString(value, &len);
+
+ return strndup(sval, len);
+}
+
+static void handle_yang_module(struct json_object *js_special,
+ LLVMValueRef yang_mod)
+{
+ check_val(LLVMIsAGlobalVariable(yang_mod), yang_mod);
+
+ LLVMValueRef value;
+
+ value = LLVMGetInitializer(yang_mod);
+ LLVMValueKind kind = LLVMGetValueKind(value);
+
+ check_val(kind == LLVMConstantStructValueKind, value);
+
+ size_t var_len = 0;
+ const char *var_name = LLVMGetValueName2(yang_mod, &var_len);
+ char buf_name[var_len + 1];
+
+ memcpy(buf_name, var_name, var_len);
+ buf_name[var_len] = '\0';
+
+ struct json_object *js_yang, *js_yangmod, *js_items;
+
+ js_yang = js_get_or_make(js_special, "yang", json_object_new_object);
+ js_yangmod = js_get_or_make(js_yang, buf_name, json_object_new_object);
+ js_items = js_get_or_make(js_yangmod, "items", json_object_new_array);
+
+ char *mod_name = get_string(LLVMGetOperand(value, 0));
+ json_object_object_add(js_yangmod, "name",
+ json_object_new_string(mod_name));
+ free(mod_name);
+
+ value = LLVMGetOperand(value, 1);
+ kind = LLVMGetValueKind(value);
+ check_val(kind == LLVMConstantArrayValueKind, value);
+
+ unsigned len = LLVMGetArrayLength(LLVMTypeOf(value));
+
+ for (unsigned i = 0; i < len - 1; i++) {
+ struct json_object *js_item, *js_cbs;
+ LLVMValueRef item = LLVMGetOperand(value, i);
+ char *xpath = get_string(LLVMGetOperand(item, 0));
+
+ js_item = json_object_new_object();
+ json_object_array_add(js_items, js_item);
+
+ json_object_object_add(js_item, "xpath",
+ json_object_new_string(xpath));
+ js_cbs = js_get_or_make(js_item, "cbs", json_object_new_object);
+
+ free(xpath);
+
+ LLVMValueRef cbs = LLVMGetOperand(item, 1);
+
+ check_val(LLVMGetValueKind(cbs) == LLVMConstantStructValueKind,
+ value);
+
+ LLVMTypeRef cbs_type = LLVMTypeOf(cbs);
+ unsigned cblen = LLVMCountStructElementTypes(cbs_type);
+
+ for (unsigned i = 0; i < cblen; i++) {
+ LLVMValueRef cb = LLVMGetOperand(cbs, i);
+
+ char *sname = NULL;
+ char *mname = NULL;
+
+ if (dbginfo_struct_member(dbginfo, cbs_type, i, &sname,
+ &mname)) {
+ (void)0;
+ }
+
+ if (LLVMIsAFunction(cb)) {
+ size_t fn_len;
+ const char *fn_name;
+
+ fn_name = LLVMGetValueName2(cb, &fn_len);
+
+ json_object_object_add(
+ js_cbs, mname,
+ json_object_new_string_len(fn_name,
+ fn_len));
+ }
+
+ free(sname);
+ free(mname);
+ }
+ }
+}
+
+static void handle_daemoninfo(struct json_object *js_special,
+ LLVMValueRef daemoninfo)
+{
+ check_val(LLVMIsAGlobalVariable(daemoninfo), daemoninfo);
+
+ LLVMTypeRef type;
+ LLVMValueRef value;
+ unsigned len;
+
+ type = LLVMGlobalGetValueType(daemoninfo);
+ value = LLVMGetInitializer(daemoninfo);
+ LLVMValueKind kind = LLVMGetValueKind(value);
+
+ check_val(kind == LLVMConstantStructValueKind, value);
+
+ int yang_idx = -1;
+
+ len = LLVMCountStructElementTypes(type);
+
+ LLVMTypeRef fieldtypes[len];
+ LLVMGetSubtypes(type, fieldtypes);
+
+ for (unsigned i = 0; i < len; i++) {
+ LLVMTypeRef t = fieldtypes[i];
+
+ if (LLVMGetTypeKind(t) != LLVMPointerTypeKind)
+ continue;
+ t = LLVMGetElementType(t);
+ if (LLVMGetTypeKind(t) != LLVMPointerTypeKind)
+ continue;
+ t = LLVMGetElementType(t);
+ if (LLVMGetTypeKind(t) != LLVMStructTypeKind)
+ continue;
+
+ const char *name = LLVMGetStructName(t);
+ if (!strcmp(name, "struct.frr_yang_module_info"))
+ yang_idx = i;
+ }
+
+ if (yang_idx == -1)
+ return;
+
+ LLVMValueRef yang_mods = LLVMGetOperand(value, yang_idx);
+ LLVMValueRef yang_size = LLVMGetOperand(value, yang_idx + 1);
+
+ check_val(LLVMIsConstant(yang_size), yang_size);
+
+ unsigned long long ival = LLVMConstIntGetZExtValue(yang_size);
+
+ check_val(LLVMGetValueKind(yang_mods) == LLVMConstantExprValueKind
+ && LLVMGetConstOpcode(yang_mods) == LLVMGetElementPtr,
+ yang_mods);
+
+ yang_mods = LLVMGetOperand(yang_mods, 0);
+
+ check_val(LLVMIsAGlobalVariable(yang_mods), yang_mods);
+
+ yang_mods = LLVMGetInitializer(yang_mods);
+
+ check_val(LLVMGetValueKind(yang_mods) == LLVMConstantArrayValueKind,
+ yang_mods);
+
+ len = LLVMGetArrayLength(LLVMTypeOf(yang_mods));
+
+ if (len != ival)
+ fprintf(stderr, "length mismatch - %llu vs. %u\n", ival, len);
+
+ for (unsigned i = 0; i < len; i++) {
+ char *dump;
+
+ LLVMValueRef item = LLVMGetOperand(yang_mods, i);
+ LLVMValueKind kind = LLVMGetValueKind(item);
+
+ check_val(kind == LLVMGlobalVariableValueKind
+ || kind == LLVMConstantExprValueKind,
+ item);
+
+ if (kind == LLVMGlobalVariableValueKind)
+ continue;
+
+ LLVMOpcode opcode = LLVMGetConstOpcode(item);
+ switch (opcode) {
+ case LLVMBitCast:
+ item = LLVMGetOperand(item, 0);
+ handle_yang_module(js_special, item);
+ break;
+
+ default:
+ dump = LLVMPrintValueToString(item);
+ printf("[%u] = [opcode=%u] %s\n", i, opcode, dump);
+ LLVMDisposeMessage(dump);
+ }
+ }
+}
+
+static void process_call(struct json_object *js_calls,
+ struct json_object *js_special,
+ LLVMValueRef instr,
+ LLVMValueRef function)
+{
+ struct json_object *js_call, *js_fptrs = NULL;
+
+ LLVMValueRef called = LLVMGetCalledValue(instr);
+
+ if (LLVMIsAInlineAsm(called))
+ return;
+
+ if (LLVMIsAConstantExpr(called)) {
+ LLVMOpcode opcode = LLVMGetConstOpcode(called);
+
+ if (opcode == LLVMBitCast) {
+ LLVMValueRef op0 = LLVMGetOperand(called, 0);
+
+ if (LLVMIsAFunction(op0))
+ called = op0;
+ }
+ }
+
+ size_t called_len = 0;
+ const char *called_name = LLVMGetValueName2(called, &called_len);
+ unsigned n_args = LLVMGetNumArgOperands(instr);
+
+ bool is_external = LLVMIsDeclaration(called);
+
+ js_call = json_object_new_object();
+ json_object_array_add(js_calls, js_call);
+ dbgloc_add(js_call, instr);
+ json_object_object_add(js_call, "is_external",
+ json_object_new_boolean(is_external));
+
+ if (!called_name || called_len == 0) {
+ json_object_object_add(js_call, "type",
+ json_object_new_string("indirect"));
+
+ LLVMValueRef last = called;
+
+ size_t name_len = 0;
+ const char *name_c = LLVMGetValueName2(function, &name_len);
+
+#ifdef FRR_SPECIFIC
+ /* information for FRR hooks is dumped for the registration
+ * in _hook_typecheck; we can safely ignore the funcptr here
+ */
+ if (strncmp(name_c, "hook_call_", 10) == 0)
+ return;
+#endif
+
+ unsigned file_len = 0;
+ const char *file = LLVMGetDebugLocFilename(instr, &file_len);
+ unsigned line = LLVMGetDebugLocLine(instr);
+
+ char prefix[256];
+ snprintf(prefix, sizeof(prefix), "%.*s:%d:%.*s()",
+ (int)file_len, file, line, (int)name_len, name_c);
+
+ if (LLVMIsALoadInst(called)
+ && LLVMIsAGetElementPtrInst(LLVMGetOperand(called, 0))
+ && try_struct_fptr(js_call, LLVMGetOperand(called, 0),
+ prefix))
+ goto out_struct_fptr;
+
+ while (LLVMIsALoadInst(last) || LLVMIsAGetElementPtrInst(last))
+ /* skipping over details for GEP here, but meh. */
+ last = LLVMGetOperand(last, 0);
+
+ if (LLVMIsAAllocaInst(last)) {
+ /* "alloca" is just generically all variables on the
+ * stack, this does not refer to C alloca() calls
+ *
+ * looking at the control flow in the function can
+ * give better results here, it's just not implemented
+ * (yet?)
+ */
+ fprintf(stderr,
+ "%s: call to a function pointer variable\n",
+ prefix);
+
+ if (details_fptr_vars) {
+ char *dump = LLVMPrintValueToString(called);
+ printf("%s- %s\n", prefix, dump);
+ LLVMDisposeMessage(dump);
+ }
+
+ json_object_object_add(
+ js_call, "type",
+ json_object_new_string("stack_fptr"));
+ } else if (LLVMIsACallInst(last)) {
+ /* calling the a function pointer returned from
+ * another function.
+ */
+ struct json_object *js_indirect;
+
+ js_indirect = js_get_or_make(js_call, "return_of",
+ json_object_new_array);
+
+ process_call(js_indirect, js_special, last, function);
+ } else if (LLVMIsAConstant(last)) {
+ /* function pointer is a constant (includes loading
+ * from complicated constants like structs or arrays.)
+ */
+ bool hdr_written = false;
+ walk_const_fptrs(js_call, last, prefix, &hdr_written);
+ if (details_fptr_consts && !hdr_written)
+ fprintf(stderr,
+ "%s: calls function pointer from constant or global data, but no non-NULL function pointers found\n",
+ prefix);
+ } else {
+ char *dump = LLVMPrintValueToString(called);
+ fprintf(stderr, "%s: ??? %s\n", prefix, dump);
+ LLVMDisposeMessage(dump);
+ }
+#ifdef FRR_SPECIFIC
+ } else if (!strcmp(called_name, "_install_element")) {
+ LLVMValueRef param0 = LLVMGetOperand(instr, 0);
+ if (!LLVMIsAConstantInt(param0))
+ goto out_nonconst;
+
+ long long vty_node = LLVMConstIntGetSExtValue(param0);
+ json_object_object_add(js_call, "vty_node",
+ json_object_new_int64(vty_node));
+
+ LLVMValueRef param1 = LLVMGetOperand(instr, 1);
+ if (!LLVMIsAGlobalVariable(param1))
+ goto out_nonconst;
+
+ LLVMValueRef intlz = LLVMGetInitializer(param1);
+ assert(intlz && LLVMIsConstant(intlz));
+
+ LLVMValueKind intlzkind = LLVMGetValueKind(intlz);
+ assert(intlzkind == LLVMConstantStructValueKind);
+
+ LLVMValueRef funcptr = LLVMGetOperand(intlz, 4);
+ assert(LLVMIsAFunction(funcptr));
+
+ size_t target_len = 0;
+ const char *target;
+ target = LLVMGetValueName2(funcptr, &target_len);
+
+ json_object_object_add(
+ js_call, "type",
+ json_object_new_string("install_element"));
+ json_object_object_add(
+ js_call, "target",
+ json_object_new_string_len(target, target_len));
+ return;
+
+ out_nonconst:
+ json_object_object_add(
+ js_call, "target",
+ json_object_new_string("install_element"));
+ return;
+ } else if (is_thread_sched(called_name, called_len)) {
+ json_object_object_add(js_call, "type",
+ json_object_new_string("thread_sched"));
+ json_object_object_add(
+ js_call, "subtype",
+ json_object_new_string_len(called_name, called_len));
+
+ LLVMValueRef fparam;
+ fparam = LLVMGetOperand(instr, 2);
+ assert(fparam);
+
+ size_t target_len = 0;
+ const char *target;
+ target = LLVMGetValueName2(fparam, &target_len);
+
+ json_object_object_add(js_call, "target",
+ !target_len ? NULL :
+ json_object_new_string_len(target, target_len));
+ if (!LLVMIsAFunction(fparam))
+ json_object_object_add(js_call, "target_unresolved",
+ json_object_new_boolean(true));
+ return;
+ } else if (!strncmp(called_name, "_hook_typecheck_",
+ strlen("_hook_typecheck_"))) {
+ struct json_object *js_hook, *js_this;
+ const char *hook_name;
+
+ hook_name = called_name + strlen("_hook_typecheck_");
+
+ json_object_object_add(js_call, "type",
+ json_object_new_string("hook"));
+
+ LLVMValueRef param0 = LLVMGetOperand(instr, 0);
+ if (!LLVMIsAFunction(param0))
+ return;
+
+ size_t target_len = 0;
+ const char *target;
+ target = LLVMGetValueName2(param0, &target_len);
+
+ js_hook = js_get_or_make(js_special, "hooks",
+ json_object_new_object);
+ js_hook = js_get_or_make(js_hook, hook_name,
+ json_object_new_array);
+
+ js_this = json_object_new_object();
+ json_object_array_add(js_hook, js_this);
+
+ dbgloc_add(js_this, instr);
+ json_object_object_add(
+ js_this, "target",
+ json_object_new_string_len(target, target_len));
+ return;
+
+ /* TODO (FRR specifics):
+ * - workqueues - not sure we can do much there
+ * - zclient->* ?
+ */
+#endif /* FRR_SPECIFIC */
+ } else if (!strcmp(called_name, "frr_preinit")) {
+ LLVMValueRef daemoninfo = LLVMGetOperand(instr, 0);
+
+ handle_daemoninfo(js_special, daemoninfo);
+
+ json_object_object_add(
+ js_call, "target",
+ json_object_new_string_len(called_name, called_len));
+ } else {
+ json_object_object_add(
+ js_call, "target",
+ json_object_new_string_len(called_name, called_len));
+ }
+
+out_struct_fptr:
+ for (unsigned argno = 0; argno < n_args; argno++) {
+ LLVMValueRef param = LLVMGetOperand(instr, argno);
+ size_t target_len;
+ const char *target_name;
+
+ if (LLVMIsAFunction(param)) {
+ js_fptrs = js_get_or_make(js_call, "funcptrs",
+ json_object_new_array);
+
+ target_name = LLVMGetValueName2(param, &target_len);
+
+ json_object_array_add(js_fptrs,
+ json_object_new_string_len(
+ target_name, target_len));
+ }
+ }
+}
+
+static void process_fn(struct json_object *funcs,
+ struct json_object *js_special,
+ LLVMValueRef function)
+{
+ struct json_object *js_func, *js_calls;
+
+ size_t name_len = 0;
+ const char *name_c = LLVMGetValueName2(function, &name_len);
+ char *name;
+
+ name = strndup(name_c, name_len);
+
+ js_func = json_object_object_get(funcs, name);
+ if (js_func) {
+ unsigned file_len = 0;
+ const char *file = LLVMGetDebugLocFilename(function, &file_len);
+ unsigned line = LLVMGetDebugLocLine(function);
+
+ fprintf(stderr, "%.*s:%d:%s(): duplicate definition!\n",
+ (int)file_len, file, line, name);
+ free(name);
+ return;
+ }
+
+ js_func = json_object_new_object();
+ json_object_object_add(funcs, name, js_func);
+ free(name);
+
+ js_calls = json_object_new_array();
+ json_object_object_add(js_func, "calls", js_calls);
+
+ dbgloc_add(js_func, function);
+
+ for (LLVMBasicBlockRef basicBlock = LLVMGetFirstBasicBlock(function);
+ basicBlock; basicBlock = LLVMGetNextBasicBlock(basicBlock)) {
+
+ for (LLVMValueRef instr = LLVMGetFirstInstruction(basicBlock);
+ instr; instr = LLVMGetNextInstruction(instr)) {
+
+ if (LLVMIsAIntrinsicInst(instr))
+ continue;
+
+ if (LLVMIsACallInst(instr) || LLVMIsAInvokeInst(instr))
+ process_call(js_calls, js_special, instr,
+ function);
+ }
+ }
+}
+
+static void help(int retcode)
+{
+ fprintf(stderr,
+ "FRR LLVM bitcode to callgraph analyzer\n"
+ "\n"
+ "usage: frr-llvm-cg [-q|-v] [-o <JSONOUTPUT>] BITCODEINPUT\n"
+ "\n"
+ "\t-o FILENAME\twrite JSON output to file instead of stdout\n"
+ "\t-v\t\tbe more verbose\n"
+ "\t-q\t\tbe quiet\n"
+ "\n"
+ "BITCODEINPUT must be a LLVM binary bitcode file (not text\n"
+ "representation.) Use - to read from stdin.\n"
+ "\n"
+ "Note it may be necessary to build this binary tool against\n"
+ "the specific LLVM version that created the bitcode file.\n");
+ exit(retcode);
+}
+
+int main(int argc, char **argv)
+{
+ int opt;
+ const char *out = NULL;
+ const char *inp = NULL;
+ char v_or_q = '\0';
+
+ while ((opt = getopt(argc, argv, "hvqo:")) != -1) {
+ switch (opt) {
+ case 'o':
+ if (out)
+ help(1);
+ out = optarg;
+ break;
+ case 'v':
+ if (v_or_q && v_or_q != 'v')
+ help(1);
+ details_fptr_vars = true;
+ details_fptr_consts = true;
+ v_or_q = 'v';
+ break;
+ case 'q':
+ if (v_or_q && v_or_q != 'q')
+ help(1);
+ details_fptr_vars = false;
+ details_fptr_consts = false;
+ v_or_q = 'q';
+ break;
+ case 'h':
+ help(0);
+ return 0;
+ default:
+ help(1);
+ }
+ }
+
+ if (optind != argc - 1)
+ help(1);
+
+ inp = argv[optind];
+
+ LLVMMemoryBufferRef memoryBuffer;
+ char *message;
+ int ret;
+
+ // check if we are to read our input file from stdin
+ if (!strcmp(inp, "-")) {
+ inp = "<stdin>";
+ ret = LLVMCreateMemoryBufferWithSTDIN(&memoryBuffer, &message);
+ } else {
+ ret = LLVMCreateMemoryBufferWithContentsOfFile(
+ inp, &memoryBuffer, &message);
+ }
+
+ if (ret) {
+ fprintf(stderr, "failed to open %s: %s\n", inp, message);
+ free(message);
+ return 1;
+ }
+
+ // now create our module using the memorybuffer
+ LLVMModuleRef module;
+ if (LLVMParseBitcode2(memoryBuffer, &module)) {
+ fprintf(stderr, "%s: invalid bitcode\n", inp);
+ LLVMDisposeMemoryBuffer(memoryBuffer);
+ return 1;
+ }
+
+ // done with the memory buffer now, so dispose of it
+ LLVMDisposeMemoryBuffer(memoryBuffer);
+
+ dbginfo = dbginfo_load(module);
+
+ struct json_object *js_root, *js_funcs, *js_special;
+
+ js_root = json_object_new_object();
+ js_funcs = json_object_new_object();
+ json_object_object_add(js_root, "functions", js_funcs);
+ js_special = json_object_new_object();
+ json_object_object_add(js_root, "special", js_special);
+
+ // loop through all the functions in the module
+ for (LLVMValueRef function = LLVMGetFirstFunction(module); function;
+ function = LLVMGetNextFunction(function)) {
+ if (LLVMIsDeclaration(function))
+ continue;
+
+ process_fn(js_funcs, js_special, function);
+ }
+
+ if (out) {
+ char tmpout[strlen(out) + 5];
+
+ snprintf(tmpout, sizeof(tmpout), "%s.tmp", out);
+ ret = json_object_to_file_ext(tmpout, js_root,
+ JSON_C_TO_STRING_PRETTY |
+ JSON_C_TO_STRING_PRETTY_TAB |
+ JSON_C_TO_STRING_NOSLASHESCAPE);
+ if (ret < 0) {
+ fprintf(stderr, "could not write JSON to file\n");
+ return 1;
+ }
+ if (rename(tmpout, out)) {
+ fprintf(stderr, "could not rename JSON output: %s\n",
+ strerror(errno));
+ unlink(tmpout);
+ return 1;
+ }
+ } else {
+ ret = json_object_to_fd(1, js_root,
+ JSON_C_TO_STRING_PRETTY |
+ JSON_C_TO_STRING_PRETTY_TAB |
+ JSON_C_TO_STRING_NOSLASHESCAPE);
+ if (ret < 0) {
+ fprintf(stderr, "could not write JSON to stdout\n");
+ return 1;
+ }
+ }
+
+ LLVMDisposeModule(module);
+
+ return 0;
+}
diff --git a/tools/frr-llvm-debuginfo.cpp b/tools/frr-llvm-debuginfo.cpp
new file mode 100644
index 0000000..16ad18b
--- /dev/null
+++ b/tools/frr-llvm-debuginfo.cpp
@@ -0,0 +1,89 @@
+// SPDX-License-Identifier: Unlicense
+
+#include <llvm-c/BitReader.h>
+#include <llvm-c/BitWriter.h>
+#include <llvm-c/Core.h>
+#include <llvm-c/DebugInfo.h>
+
+#include <llvm/IR/Module.h>
+#include <llvm/IR/Value.h>
+#include <llvm/IR/Type.h>
+#include <llvm/IR/DebugInfo.h>
+#include <llvm/IR/DebugInfoMetadata.h>
+#include <llvm/Support/raw_ostream.h>
+
+#include <map>
+
+#include "frr-llvm-debuginfo.h"
+
+/* llvm::DebugInfoFinder is unfortunately not exposed in the llvm-c API... */
+
+struct dbginfo {
+ llvm::DebugInfoFinder finder;
+ std::map<std::string, llvm::DICompositeType *> tab;
+};
+
+struct dbginfo *dbginfo_load(LLVMModuleRef _mod)
+{
+ llvm::Module *mod = llvm::unwrap(_mod);
+ struct dbginfo *info = new dbginfo();
+
+ info->finder.processModule(*mod);
+
+ for (auto ty : info->finder.types()) {
+ if (ty->getMetadataID() != llvm::Metadata::DICompositeTypeKind)
+ continue;
+
+ llvm::DICompositeType *cty = (llvm::DICompositeType *)ty;
+ /* empty forward declarations aka "struct foobar;" */
+ if (cty->getElements().size() == 0)
+ continue;
+
+ info->tab.emplace(std::move(ty->getName().str()), cty);
+ }
+
+ return info;
+}
+
+bool dbginfo_struct_member(struct dbginfo *info, LLVMTypeRef _typ,
+ unsigned long long idx, char **struct_name,
+ char **member_name)
+{
+ *struct_name = NULL;
+ *member_name = NULL;
+
+ llvm::Type *typ = llvm::unwrap(_typ);
+
+ if (!typ->isStructTy())
+ return false;
+
+ llvm::StructType *styp = (llvm::StructType *)typ;
+ auto sname = styp->getStructName();
+
+ if (!sname.startswith("struct."))
+ return false;
+ sname = sname.drop_front(7);
+
+ size_t dot = sname.find_last_of(".");
+ if (dot != sname.npos)
+ sname = sname.take_front(dot);
+
+ auto item = info->tab.find(sname.str());
+ if (item == info->tab.end())
+ return false;
+
+ auto elements = item->second->getElements();
+ if (idx >= elements.size())
+ return false;
+
+ auto elem = elements[idx];
+
+ if (elem->getMetadataID() != llvm::Metadata::DIDerivedTypeKind)
+ return false;
+
+ llvm::DIDerivedType *dtyp = (llvm::DIDerivedType *)elem;
+
+ *struct_name = strdup(sname.str().c_str());
+ *member_name = strdup(dtyp->getName().str().c_str());
+ return true;
+}
diff --git a/tools/frr-llvm-debuginfo.h b/tools/frr-llvm-debuginfo.h
new file mode 100644
index 0000000..4d2985c
--- /dev/null
+++ b/tools/frr-llvm-debuginfo.h
@@ -0,0 +1,24 @@
+// SPDX-License-Identifier: Unlicense
+
+#ifndef _FRR_LLVM_DEBUGINFO_H
+#define _FRR_LLVM_DEBUGINFO_H
+
+#include <stdbool.h>
+#include <llvm-c/Core.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+struct dbginfo;
+
+extern struct dbginfo *dbginfo_load(LLVMModuleRef mod);
+extern bool dbginfo_struct_member(struct dbginfo *di, LLVMTypeRef typ,
+ unsigned long long idx, char **struct_name,
+ char **member_name);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _FRR_LLVM_DEBUGINFO_H */
diff --git a/tools/frr-reload b/tools/frr-reload
new file mode 100755
index 0000000..75b31d0
--- /dev/null
+++ b/tools/frr-reload
@@ -0,0 +1,7 @@
+#!/bin/sh
+
+if test -e /usr/lib/frr/frr-reload.py; then
+ exec /usr/lib/frr/frr-reload.py --reload /etc/frr/frr.conf
+fi
+>&2 echo "Please install frr-pythontools package. Required for reload"
+exit 1
diff --git a/tools/frr-reload.py b/tools/frr-reload.py
new file mode 100755
index 0000000..3a478f6
--- /dev/null
+++ b/tools/frr-reload.py
@@ -0,0 +1,2275 @@
+#!/usr/bin/env python3
+# SPDX-License-Identifier: GPL-2.0-or-later
+# Frr Reloader
+# Copyright (C) 2014 Cumulus Networks, Inc.
+#
+"""
+This program
+- reads a frr configuration text file
+- reads frr's current running configuration via "vtysh -c 'show running'"
+- compares the two configs and determines what commands to execute to
+ synchronize frr's running configuration with the configuation in the
+ text file
+"""
+
+from __future__ import print_function, unicode_literals
+import argparse
+import logging
+import os, os.path
+import random
+import re
+import string
+import subprocess
+import sys
+from collections import OrderedDict
+from ipaddress import IPv6Address, ip_network
+from pprint import pformat
+
+# Python 3
+def iteritems(d):
+ return iter(d.items())
+
+
+log = logging.getLogger(__name__)
+
+
+class VtyshException(Exception):
+ pass
+
+
+class Vtysh(object):
+ def __init__(self, bindir=None, confdir=None, sockdir=None, pathspace=None):
+ self.bindir = bindir
+ self.confdir = confdir
+ self.pathspace = pathspace
+ self.common_args = [os.path.join(bindir or "", "vtysh")]
+ if confdir:
+ self.common_args.extend(["--config_dir", confdir])
+ if sockdir:
+ self.common_args.extend(["--vty_socket", sockdir])
+ if pathspace:
+ self.common_args.extend(["-N", pathspace])
+
+ def _call(self, args, stdin=None, stdout=None, stderr=None):
+ kwargs = {}
+ if stdin is not None:
+ kwargs["stdin"] = stdin
+ if stdout is not None:
+ kwargs["stdout"] = stdout
+ if stderr is not None:
+ kwargs["stderr"] = stderr
+ return subprocess.Popen(self.common_args + args, **kwargs)
+
+ def _call_cmd(self, command, stdin=None, stdout=None, stderr=None):
+ if isinstance(command, list):
+ args = [item for sub in command for item in ["-c", sub]]
+ else:
+ args = ["-c", command]
+ return self._call(args, stdin, stdout, stderr)
+
+ def __call__(self, command, stdouts=None):
+ """
+ Call a CLI command (e.g. "show running-config")
+
+ Output text is automatically redirected, decoded and returned.
+ Multiple commands may be passed as list.
+ """
+ proc = self._call_cmd(command, stdout=subprocess.PIPE)
+ stdout, stderr = proc.communicate()
+ if proc.wait() != 0:
+ if stdouts is not None:
+ stdouts.append(stdout.decode("UTF-8"))
+ raise VtyshException(
+ 'vtysh returned status %d for command "%s"' % (proc.returncode, command)
+ )
+ return stdout.decode("UTF-8")
+
+ def is_config_available(self):
+ """
+ Return False if no frr daemon is running or some other vtysh session is
+ in 'configuration terminal' mode which will prevent us from making any
+ configuration changes.
+ """
+
+ output = self("configure")
+
+ if "VTY configuration is locked by other VTY" in output:
+ log.error("vtysh 'configure' returned\n%s\n" % (output))
+ return False
+
+ return True
+
+ def exec_file(self, filename):
+ child = self._call(["-f", filename])
+ if child.wait() != 0:
+ raise VtyshException(
+ "vtysh (exec file) exited with status %d" % (child.returncode)
+ )
+
+ def mark_file(self, filename, stdin=None):
+ child = self._call(
+ ["-m", "-f", filename],
+ stdout=subprocess.PIPE,
+ stdin=subprocess.PIPE,
+ stderr=subprocess.PIPE,
+ )
+ try:
+ stdout, stderr = child.communicate()
+ except subprocess.TimeoutExpired:
+ child.kill()
+ stdout, stderr = child.communicate()
+ raise VtyshException("vtysh call timed out!")
+
+ if child.wait() != 0:
+ raise VtyshException(
+ "vtysh (mark file) exited with status %d:\n%s"
+ % (child.returncode, stderr)
+ )
+
+ return stdout.decode("UTF-8")
+
+ def mark_show_run(self, daemon=None):
+ cmd = "show running-config"
+ if daemon:
+ cmd += " %s" % daemon
+ cmd += " no-header"
+ show_run = self._call_cmd(cmd, stdout=subprocess.PIPE)
+ mark = self._call(
+ ["-m", "-f", "-"], stdin=show_run.stdout, stdout=subprocess.PIPE
+ )
+
+ show_run.wait()
+ stdout, stderr = mark.communicate()
+ mark.wait()
+
+ if show_run.returncode != 0:
+ raise VtyshException(
+ "vtysh (show running-config) exited with status %d:"
+ % (show_run.returncode)
+ )
+ if mark.returncode != 0:
+ raise VtyshException(
+ "vtysh (mark running-config) exited with status %d" % (mark.returncode)
+ )
+
+ return stdout.decode("UTF-8")
+
+
+class Context(object):
+ """
+ A Context object represents a section of frr configuration such as:
+ !
+ interface swp3
+ description swp3 -> r8's swp1
+ ipv6 nd suppress-ra
+ link-detect
+ !
+
+ or a single line context object such as this:
+
+ ip forwarding
+
+ """
+
+ def __init__(self, keys, lines):
+ self.keys = keys
+ self.lines = lines
+
+ # Keep a dictionary of the lines, this is to make it easy to tell if a
+ # line exists in this Context
+ self.dlines = OrderedDict()
+
+ for ligne in lines:
+ self.dlines[ligne] = True
+
+ def __str__(self):
+ return str(self.keys) + " : " + str(self.lines)
+
+ def add_lines(self, lines):
+ """
+ Add lines to specified context
+ """
+
+ self.lines.extend(lines)
+
+ for ligne in lines:
+ self.dlines[ligne] = True
+
+
+def get_normalized_es_id(line):
+ """
+ The es-id or es-sys-mac need to be converted to lower case
+ """
+ sub_strs = ["evpn mh es-id", "evpn mh es-sys-mac"]
+ for sub_str in sub_strs:
+ obj = re.match(sub_str + " (?P<esi>\S*)", line)
+ if obj:
+ line = "%s %s" % (sub_str, obj.group("esi").lower())
+ break
+ return line
+
+
+def get_normalized_mac_ip_line(line):
+ if line.startswith("evpn mh es"):
+ return get_normalized_es_id(line)
+
+ if not "ipv6 add" in line:
+ return get_normalized_ipv6_line(line)
+
+ return line
+
+
+# This dictionary contains a tree of all commands that we know start a
+# new multi-line context. All other commands are treated either as
+# commands inside a multi-line context or as single-line contexts. This
+# dictionary should be updated whenever a new node is added to FRR.
+ctx_keywords = {
+ "router bgp ": {
+ "address-family ": {
+ "vni ": {},
+ },
+ "vnc defaults": {},
+ "vnc nve-group ": {},
+ "vnc l2-group ": {},
+ "vrf-policy ": {},
+ "bmp targets ": {},
+ "segment-routing srv6": {},
+ },
+ "router rip": {},
+ "router ripng": {},
+ "router isis ": {},
+ "router openfabric ": {},
+ "router ospf": {},
+ "router ospf6": {},
+ "router eigrp ": {},
+ "router babel": {},
+ "mpls ldp": {"address-family ": {"interface ": {}}},
+ "l2vpn ": {"member pseudowire ": {}},
+ "key chain ": {"key ": {}},
+ "vrf ": {},
+ "interface ": {"link-params": {}},
+ "pseudowire ": {},
+ "segment-routing": {
+ "traffic-eng": {
+ "segment-list ": {},
+ "policy ": {"candidate-path ": {}},
+ "pcep": {"pcc": {}, "pce ": {}, "pce-config ": {}},
+ },
+ "srv6": {"locators": {"locator ": {}}},
+ },
+ "nexthop-group ": {},
+ "route-map ": {},
+ "pbr-map ": {},
+ "rpki": {},
+ "bfd": {"peer ": {}, "profile ": {}},
+ "line vty": {},
+}
+
+
+class Config(object):
+ """
+ A frr configuration is stored in a Config object. A Config object
+ contains a dictionary of Context objects where the Context keys
+ ('router ospf' for example) are our dictionary key.
+ """
+
+ def __init__(self, vtysh):
+ self.lines = []
+ self.contexts = OrderedDict()
+ self.vtysh = vtysh
+
+ def load_from_file(self, filename):
+ """
+ Read configuration from specified file and slurp it into internal memory
+ The internal representation has been marked appropriately by passing it
+ through vtysh with the -m parameter
+ """
+ log.info("Loading Config object from file %s", filename)
+
+ file_output = self.vtysh.mark_file(filename)
+
+ for line in file_output.split("\n"):
+ line = line.strip()
+
+ # Compress duplicate whitespaces
+ line = " ".join(line.split())
+
+ if ":" in line:
+ line = get_normalized_mac_ip_line(line)
+
+ # vrf static routes can be added in two ways. The old way is:
+ #
+ # "ip route x.x.x.x/x y.y.y.y vrf <vrfname>"
+ #
+ # but it's rendered in the configuration as the new way::
+ #
+ # vrf <vrf-name>
+ # ip route x.x.x.x/x y.y.y.y
+ # exit-vrf
+ #
+ # this difference causes frr-reload to not consider them a
+ # match and delete vrf static routes incorrectly.
+ # fix the old way to match new "show running" output so a
+ # proper match is found.
+ if (
+ line.startswith("ip route ") or line.startswith("ipv6 route ")
+ ) and " vrf " in line:
+ newline = line.split(" ")
+ vrf_index = newline.index("vrf")
+ vrf_ctx = newline[vrf_index] + " " + newline[vrf_index + 1]
+ del newline[vrf_index : vrf_index + 2]
+ newline = " ".join(newline)
+ self.lines.append(vrf_ctx)
+ self.lines.append(newline)
+ self.lines.append("exit-vrf")
+ line = "end"
+
+ self.lines.append(line)
+
+ self.load_contexts()
+
+ def load_from_show_running(self, daemon):
+ """
+ Read running configuration and slurp it into internal memory
+ The internal representation has been marked appropriately by passing it
+ through vtysh with the -m parameter
+ """
+ log.info("Loading Config object from vtysh show running")
+
+ config_text = self.vtysh.mark_show_run(daemon)
+
+ for line in config_text.split("\n"):
+ line = line.strip()
+
+ if (
+ line == "Building configuration..."
+ or line == "Current configuration:"
+ or not line
+ ):
+ continue
+
+ self.lines.append(line)
+
+ self.load_contexts()
+
+ def get_lines(self):
+ """
+ Return the lines read in from the configuration
+ """
+ return "\n".join(self.lines)
+
+ def get_contexts(self):
+ """
+ Return the parsed context as strings for display, log etc.
+ """
+ for (_, ctx) in sorted(iteritems(self.contexts)):
+ print(str(ctx))
+
+ def save_contexts(self, key, lines):
+ """
+ Save the provided key and lines as a context
+ """
+ if not key:
+ return
+
+ # IP addresses specified in "network" statements, "ip prefix-lists"
+ # etc. can differ in the host part of the specification the user
+ # provides and what the running config displays. For example, user can
+ # specify 11.1.1.1/24, and the running config displays this as
+ # 11.1.1.0/24. Ensure we don't do a needless operation for such lines.
+ # IS-IS & OSPFv3 have no "network" support.
+ re_key_rt = re.match(r"(ip|ipv6)\s+route\s+([A-Fa-f:.0-9/]+)(.*)$", key[0])
+ if re_key_rt:
+ addr = re_key_rt.group(2)
+ if "/" in addr:
+ try:
+ newaddr = ip_network(addr, strict=False)
+ key[0] = "%s route %s/%s%s" % (
+ re_key_rt.group(1),
+ str(newaddr.network_address),
+ newaddr.prefixlen,
+ re_key_rt.group(3),
+ )
+ except ValueError:
+ pass
+
+ re_key_rt = re.match(
+ r"(ip|ipv6)\s+prefix-list(.*)(permit|deny)\s+([A-Fa-f:.0-9/]+)(.*)$", key[0]
+ )
+ if re_key_rt:
+ addr = re_key_rt.group(4)
+ if "/" in addr:
+ try:
+ network_addr = ip_network(addr, strict=False)
+ newaddr = "%s/%s" % (
+ str(network_addr.network_address),
+ network_addr.prefixlen,
+ )
+ except ValueError:
+ newaddr = addr
+ else:
+ newaddr = addr
+
+ legestr = re_key_rt.group(5)
+ re_lege = re.search(r"(.*)le\s+(\d+)\s+ge\s+(\d+)(.*)", legestr)
+ if re_lege:
+ legestr = "%sge %s le %s%s" % (
+ re_lege.group(1),
+ re_lege.group(3),
+ re_lege.group(2),
+ re_lege.group(4),
+ )
+
+ key[0] = "%s prefix-list%s%s %s%s" % (
+ re_key_rt.group(1),
+ re_key_rt.group(2),
+ re_key_rt.group(3),
+ newaddr,
+ legestr,
+ )
+
+ if lines and key[0].startswith("router bgp"):
+ newlines = []
+ for line in lines:
+ re_net = re.match(r"network\s+([A-Fa-f:.0-9/]+)(.*)$", line)
+ if re_net:
+ addr = re_net.group(1)
+ if "/" not in addr and key[0].startswith("router bgp"):
+ # This is most likely an error because with no
+ # prefixlen, BGP treats the prefixlen as 8
+ addr = addr + "/8"
+
+ try:
+ network_addr = ip_network(addr, strict=False)
+ line = "network %s/%s %s" % (
+ str(network_addr.network_address),
+ network_addr.prefixlen,
+ re_net.group(2),
+ )
+ newlines.append(line)
+ except ValueError:
+ # Really this should be an error. Whats a network
+ # without an IP Address following it ?
+ newlines.append(line)
+ else:
+ newlines.append(line)
+ lines = newlines
+
+ # More fixups in user specification and what running config shows.
+ # "null0" in routes must be replaced by Null0.
+ if (
+ key[0].startswith("ip route")
+ or key[0].startswith("ipv6 route")
+ and "null0" in key[0]
+ ):
+ key[0] = re.sub(r"\s+null0(\s*$)", " Null0", key[0])
+
+ if lines and key[0].startswith("vrf "):
+ newlines = []
+ for line in lines:
+ if line.startswith("ip route ") or line.startswith("ipv6 route "):
+ if "null0" in line:
+ line = re.sub(r"\s+null0(\s*$)", " Null0", line)
+ newlines.append(line)
+ else:
+ newlines.append(line)
+ lines = newlines
+
+ if lines:
+ if tuple(key) not in self.contexts:
+ ctx = Context(tuple(key), lines)
+ self.contexts[tuple(key)] = ctx
+ else:
+ ctx = self.contexts[tuple(key)]
+ ctx.add_lines(lines)
+
+ else:
+ if tuple(key) not in self.contexts:
+ ctx = Context(tuple(key), [])
+ self.contexts[tuple(key)] = ctx
+
+ def load_contexts(self):
+ """
+ Parse the configuration and create contexts for each appropriate block
+
+ The end of a context is flagged via the 'end' keyword:
+
+ !
+ interface swp52
+ ipv6 nd suppress-ra
+ link-detect
+ !
+ end
+ router bgp 10
+ bgp router-id 10.0.0.1
+ bgp log-neighbor-changes
+ no bgp default ipv4-unicast
+ neighbor EBGP peer-group
+ neighbor EBGP advertisement-interval 1
+ neighbor EBGP timers connect 10
+ neighbor 2001:40:1:4::6 remote-as 40
+ neighbor 2001:40:1:8::a remote-as 40
+ !
+ end
+ address-family ipv6
+ neighbor IBGPv6 activate
+ neighbor 2001:10::2 peer-group IBGPv6
+ neighbor 2001:10::3 peer-group IBGPv6
+ exit-address-family
+ !
+ end
+ router ospf
+ ospf router-id 10.0.0.1
+ log-adjacency-changes detail
+ timers throttle spf 0 50 5000
+ !
+ end
+
+ The code assumes that its working on the output from the "vtysh -m"
+ command. That provides the appropriate markers to signify end of
+ a context. This routine uses that to build the contexts for the
+ config.
+
+ There are single line contexts such as "log file /media/node/zebra.log"
+ and multi-line contexts such as "router ospf" and subcontexts
+ within a context such as "address-family" within "router bgp"
+ In each of these cases, the first line of the context becomes the
+ key of the context. So "router bgp 10" is the key for the non-address
+ family part of bgp, "router bgp 10, address-family ipv6 unicast" is
+ the key for the subcontext and so on.
+ """
+ # stack of context keys
+ ctx_keys = []
+ # stack of context keywords
+ cur_ctx_keywords = [ctx_keywords]
+ # list of stored commands
+ cur_ctx_lines = []
+
+ for line in self.lines:
+
+ if not line:
+ continue
+
+ if line.startswith("!") or line.startswith("#"):
+ continue
+
+ if line.startswith("exit"):
+ # ignore on top level
+ if len(ctx_keys) == 0:
+ continue
+
+ # save current context
+ self.save_contexts(ctx_keys, cur_ctx_lines)
+
+ # exit current context
+ log.debug("LINE %-50s: exit context %-50s", line, ctx_keys)
+
+ ctx_keys.pop()
+ cur_ctx_keywords.pop()
+ cur_ctx_lines = []
+
+ continue
+
+ if line.startswith("end"):
+ # exit all contexts
+ while len(ctx_keys) > 0:
+ # save current context
+ self.save_contexts(ctx_keys, cur_ctx_lines)
+
+ # exit current context
+ log.debug("LINE %-50s: exit context %-50s", line, ctx_keys)
+
+ ctx_keys.pop()
+ cur_ctx_keywords.pop()
+ cur_ctx_lines = []
+
+ continue
+
+ new_ctx = False
+
+ # check if the line is a context-entering keyword
+ for k, v in cur_ctx_keywords[-1].items():
+ if line.startswith(k):
+ # candidate-path is a special case. It may be a node and
+ # may be a single-line command. The distinguisher is the
+ # word "dynamic" or "explicit" at the middle of the line.
+ # It was perhaps not the best choice by the pathd authors
+ # but we have what we have.
+ if k == "candidate-path " and "explicit" in line:
+ # this is a single-line command
+ break
+
+ # save current context
+ self.save_contexts(ctx_keys, cur_ctx_lines)
+
+ # enter new context
+ new_ctx = True
+ ctx_keys.append(line)
+ cur_ctx_keywords.append(v)
+ cur_ctx_lines = []
+
+ log.debug("LINE %-50s: enter context %-50s", line, ctx_keys)
+ break
+
+ if new_ctx:
+ continue
+
+ if len(ctx_keys) == 0:
+ log.debug("LINE %-50s: single-line context", line)
+ self.save_contexts([line], [])
+ else:
+ log.debug("LINE %-50s: add to current context %-50s", line, ctx_keys)
+ cur_ctx_lines.append(line)
+
+ # Save the context of the last one
+ if len(ctx_keys) > 0:
+ self.save_contexts(ctx_keys, cur_ctx_lines)
+
+
+def lines_to_config(ctx_keys, line, delete):
+ """
+ Return the command as it would appear in frr.conf
+ """
+ cmd = []
+
+ # If there's no `line` and `ctx_keys` length is 1, then it may be a single-line command.
+ # In this case, we should treat it as a single command in an empty context.
+ if len(ctx_keys) == 1 and not line:
+ single = True
+
+ for k, v in ctx_keywords.items():
+ if ctx_keys[0].startswith(k):
+ single = False
+ break
+
+ if single:
+ line = ctx_keys[0]
+ ctx_keys = []
+
+ if line:
+ for (i, ctx_key) in enumerate(ctx_keys):
+ cmd.append(" " * i + ctx_key)
+
+ line = line.lstrip()
+ indent = len(ctx_keys) * " "
+
+ # There are some commands that are on by default so their "no" form will be
+ # displayed in the config. "no bgp default ipv4-unicast" is one of these.
+ # If we need to remove this line we do so by adding "bgp default ipv4-unicast",
+ # not by doing a "no no bgp default ipv4-unicast"
+ if delete:
+ if line.startswith("no "):
+ cmd.append("%s%s" % (indent, line[3:]))
+ else:
+ cmd.append("%sno %s" % (indent, line))
+
+ else:
+ cmd.append(indent + line)
+
+ for i in reversed(range(len(ctx_keys))):
+ cmd.append(" " * i + "exit")
+
+ # If line is None then we are typically deleting an entire
+ # context ('no router ospf' for example)
+ else:
+ for i, ctx_key in enumerate(ctx_keys[:-1]):
+ cmd.append("%s%s" % (" " * i, ctx_key))
+
+ # Only put the 'no' on the last sub-context
+ if delete:
+ if ctx_keys[-1].startswith("no "):
+ cmd.append("%s%s" % (" " * (len(ctx_keys) - 1), ctx_keys[-1][3:]))
+ else:
+ cmd.append("%sno %s" % (" " * (len(ctx_keys) - 1), ctx_keys[-1]))
+ else:
+ cmd.append("%s%s" % (" " * (len(ctx_keys) - 1), ctx_keys[-1]))
+ cmd.append("%sexit" % (" " * (len(ctx_keys) - 1)))
+
+ for i in reversed(range(len(ctx_keys) - 1)):
+ cmd.append(" " * i + "exit")
+
+ return cmd
+
+
+def get_normalized_ipv6_line(line):
+ """
+ Return a normalized IPv6 line as produced by frr,
+ with all letters in lower case and trailing and leading
+ zeros removed, and only the network portion present if
+ the IPv6 word is a network
+ """
+ norm_line = ""
+ words = line.split(" ")
+ for word in words:
+ if ":" in word:
+ norm_word = None
+ if "/" in word:
+ try:
+ v6word = ip_network(word, strict=False)
+ norm_word = "%s/%s" % (
+ str(v6word.network_address),
+ v6word.prefixlen,
+ )
+ except ValueError:
+ pass
+ if not norm_word:
+ try:
+ norm_word = "%s" % IPv6Address(word)
+ except ValueError:
+ norm_word = word
+ else:
+ norm_word = word
+ norm_line = norm_line + " " + norm_word
+
+ return norm_line.strip()
+
+
+def line_exist(lines, target_ctx_keys, target_line, exact_match=True):
+ for (ctx_keys, line) in lines:
+ if ctx_keys == target_ctx_keys:
+ if exact_match:
+ if line == target_line:
+ return True
+ else:
+ if line.startswith(target_line):
+ return True
+ return False
+
+
+def bgp_delete_inst_move_line(lines_to_del):
+ # Deletion of bgp default inst followed by
+ # bgp vrf inst leads to issue of default
+ # instance can not be removed.
+ # Move the bgp default instance line to end.
+ bgp_defult_inst = False
+ bgp_vrf_inst = False
+
+ for (ctx_keys, line) in lines_to_del:
+ # Find bgp default inst
+ if (
+ ctx_keys[0].startswith("router bgp")
+ and not line
+ and "vrf" not in ctx_keys[0]
+ ):
+ bgp_defult_inst = True
+ # Find bgp vrf inst
+ if ctx_keys[0].startswith("router bgp") and not line and "vrf" in ctx_keys[0]:
+ bgp_vrf_inst = True
+
+ if bgp_defult_inst and bgp_vrf_inst:
+ for (ctx_keys, line) in lines_to_del:
+ # move bgp default inst to end
+ if (
+ ctx_keys[0].startswith("router bgp")
+ and not line
+ and "vrf" not in ctx_keys[0]
+ ):
+ lines_to_del.remove((ctx_keys, line))
+ lines_to_del.append((ctx_keys, line))
+
+
+def bgp_delete_nbr_remote_as_line(lines_to_add):
+ # Handle deletion of neighbor <nbr> remote-as line from
+ # lines_to_add if the nbr is configured with peer-group and
+ # peer-group has remote-as config present.
+ # 'neighbor <nbr> remote-as change on peer is not allowed
+ # if the peer is part of peer-group and peer-group has
+ # remote-as config.
+
+ pg_dict = dict()
+ found_pg_cmd = False
+
+ # Find all peer-group commands; create dict of each peer-group
+ # to store assoicated neighbor as value
+ for ctx_keys, line in lines_to_add:
+ if (
+ ctx_keys[0].startswith("router bgp")
+ and line
+ and line.startswith("neighbor ")
+ ):
+ # {'router bgp 65001': {'PG': [], 'PG1': []},
+ # 'router bgp 65001 vrf vrf1': {'PG': [], 'PG1': []}}
+ if ctx_keys[0] not in pg_dict:
+ pg_dict[ctx_keys[0]] = dict()
+ # find 'neighbor <pg_name> peer-group'
+ re_pg = re.match("neighbor (\S+) peer-group$", line)
+ if re_pg and re_pg.group(1) not in pg_dict[ctx_keys[0]]:
+ pg_dict[ctx_keys[0]][re_pg.group(1)] = {
+ "nbr": list(),
+ "remoteas": False,
+ }
+ found_pg_cmd = True
+
+ # Do nothing if there is no any "peer-group"
+ if found_pg_cmd is False:
+ return
+
+ # Find peer-group with remote-as command, also search neighbor
+ # associated to peer-group and store into peer-group dict
+ for ctx_keys, line in lines_to_add:
+ if (
+ ctx_keys[0].startswith("router bgp")
+ and line
+ and line.startswith("neighbor ")
+ ):
+ if ctx_keys[0] in pg_dict:
+ for pg_key in pg_dict[ctx_keys[0]]:
+ # Find 'neighbor <pg_name> remote-as'
+ pg_rmtas = "neighbor %s remote-as (\S+)" % pg_key
+ re_pg_rmtas = re.search(pg_rmtas, line)
+ if re_pg_rmtas:
+ pg_dict[ctx_keys[0]][pg_key]["remoteas"] = True
+
+ # Find 'neighbor <peer> [interface] peer-group <pg_name>'
+ nb_pg = "neighbor (\S+) peer-group %s$" % pg_key
+ re_nbr_pg = re.search(nb_pg, line)
+ if (
+ re_nbr_pg
+ and re_nbr_pg.group(1) not in pg_dict[ctx_keys[0]][pg_key]
+ ):
+ pg_dict[ctx_keys[0]][pg_key]["nbr"].append(re_nbr_pg.group(1))
+
+ # Find any neighbor <nbr> remote-as config line check if the nbr
+ # is in the peer group's list of nbrs. Remove 'neighbor <nbr> remote-as <>'
+ # from lines_to_add.
+ lines_to_del_from_add = []
+ for ctx_keys, line in lines_to_add:
+ if (
+ ctx_keys[0].startswith("router bgp")
+ and line
+ and line.startswith("neighbor ")
+ ):
+ nbr_rmtas = "neighbor (\S+) remote-as.*"
+ re_nbr_rmtas = re.search(nbr_rmtas, line)
+ if re_nbr_rmtas and ctx_keys[0] in pg_dict:
+ for pg in pg_dict[ctx_keys[0]]:
+ if pg_dict[ctx_keys[0]][pg]["remoteas"] == True:
+ for nbr in pg_dict[ctx_keys[0]][pg]["nbr"]:
+ if re_nbr_rmtas.group(1) == nbr:
+ lines_to_del_from_add.append((ctx_keys, line))
+
+ for ctx_keys, line in lines_to_del_from_add:
+ lines_to_add.remove((ctx_keys, line))
+
+
+def bgp_remove_neighbor_cfg(lines_to_del, del_nbr_dict):
+
+ # This method handles deletion of bgp neighbor configs,
+ # if there is neighbor to peer-group cmd is in delete list.
+ # As 'no neighbor .* peer-group' deletes the neighbor,
+ # subsequent neighbor speciic config line deletion results
+ # in error.
+ lines_to_del_to_del = []
+
+ for (ctx_keys, line) in lines_to_del:
+ if (
+ ctx_keys[0].startswith("router bgp")
+ and line
+ and line.startswith("neighbor ")
+ ):
+ if ctx_keys[0] in del_nbr_dict:
+ for nbr in del_nbr_dict[ctx_keys[0]]:
+ re_nbr_pg = re.search("neighbor (\S+) .*peer-group (\S+)", line)
+ nb_exp = "neighbor %s .*" % nbr
+ if not re_nbr_pg:
+ re_nb = re.search(nb_exp, line)
+ if re_nb:
+ lines_to_del_to_del.append((ctx_keys, line))
+
+ for (ctx_keys, line) in lines_to_del_to_del:
+ lines_to_del.remove((ctx_keys, line))
+
+
+def bgp_delete_move_lines(lines_to_add, lines_to_del):
+ # This method handles deletion of bgp peer group config.
+ # The objective is to delete config lines related to peers
+ # associated with the peer-group and move the peer-group
+ # config line to the end of the lines_to_del list.
+
+ bgp_delete_nbr_remote_as_line(lines_to_add)
+
+ del_dict = dict()
+ del_nbr_dict = dict()
+ # Stores the lines to move to the end of the pending list.
+ lines_to_del_to_del = []
+ # Stores the lines to move to end of the pending list.
+ lines_to_del_to_app = []
+ found_pg_del_cmd = False
+
+ # When "neighbor <pg_name> peer-group" under a bgp instance is removed,
+ # it also deletes the associated peer config. Any config line below no form of
+ # peer-group related to a peer are errored out as the peer no longer exists.
+ # To cleanup peer-group and associated peer(s) configs:
+ # - Remove all the peers config lines from the pending list (lines_to_del list).
+ # - Move peer-group deletion line to the end of the pending list, to allow
+ # removal of any of the peer-group specific configs.
+ #
+ # Create a dictionary of config context (i.e. router bgp vrf x).
+ # Under each context node, create a dictionary of a peer-group name.
+ # Append a peer associated to the peer-group into a list under a peer-group node.
+ # Remove all of the peer associated config lines from the pending list.
+ # Append peer-group deletion line to end of the pending list.
+ #
+ # Example:
+ # neighbor underlay peer-group
+ # neighbor underlay remote-as external
+ # neighbor underlay advertisement-interval 0
+ # neighbor underlay timers 3 9
+ # neighbor underlay timers connect 10
+ # neighbor swp1 interface peer-group underlay
+ # neighbor swp1 advertisement-interval 0
+ # neighbor swp1 timers 3 9
+ # neighbor swp1 timers connect 10
+ # neighbor swp2 interface peer-group underlay
+ # neighbor swp2 advertisement-interval 0
+ # neighbor swp2 timers 3 9
+ # neighbor swp2 timers connect 10
+ # neighbor swp3 interface peer-group underlay
+ # neighbor uplink1 interface remote-as internal
+ # neighbor uplink1 advertisement-interval 0
+ # neighbor uplink1 timers 3 9
+ # neighbor uplink1 timers connect 10
+
+ # New order:
+ # "router bgp 200 no bgp bestpath as-path multipath-relax"
+ # "router bgp 200 no neighbor underlay advertisement-interval 0"
+ # "router bgp 200 no neighbor underlay timers 3 9"
+ # "router bgp 200 no neighbor underlay timers connect 10"
+ # "router bgp 200 no neighbor uplink1 advertisement-interval 0"
+ # "router bgp 200 no neighbor uplink1 timers 3 9"
+ # "router bgp 200 no neighbor uplink1 timers connect 10"
+ # "router bgp 200 no neighbor underlay remote-as external"
+ # "router bgp 200 no neighbor uplink1 interface remote-as internal"
+ # "router bgp 200 no neighbor underlay peer-group"
+
+ for (ctx_keys, line) in lines_to_del:
+ if (
+ ctx_keys[0].startswith("router bgp")
+ and line
+ and line.startswith("neighbor ")
+ ):
+ # When 'neighbor <peer> remote-as <>' is removed it deletes the peer,
+ # there might be a peer associated config which also needs to be removed
+ # prior to peer.
+ # Append the 'neighbor <peer> remote-as <>' to the lines_to_del.
+ # Example:
+ #
+ # neighbor uplink1 interface remote-as internal
+ # neighbor uplink1 advertisement-interval 0
+ # neighbor uplink1 timers 3 9
+ # neighbor uplink1 timers connect 10
+
+ # Move to end:
+ # neighbor uplink1 advertisement-interval 0
+ # neighbor uplink1 timers 3 9
+ # neighbor uplink1 timers connect 10
+ # ...
+ #
+ # neighbor uplink1 interface remote-as internal
+ #
+ # 'no neighbor peer [interface] remote-as <>'
+ nb_remoteas = "neighbor (\S+) .*remote-as (\S+)"
+ re_nb_remoteas = re.search(nb_remoteas, line)
+ if re_nb_remoteas:
+ lines_to_del_to_app.append((ctx_keys, line))
+
+ # 'no neighbor peer [interface] peer-group <>' is in lines_to_del
+ # copy the neighbor and look for all config removal lines associated
+ # to neighbor and delete them from the lines_to_del
+ re_nbr_pg = re.search("neighbor (\S+) .*peer-group (\S+)", line)
+ if re_nbr_pg:
+ if ctx_keys[0] not in del_nbr_dict:
+ del_nbr_dict[ctx_keys[0]] = list()
+ if re_nbr_pg.group(1) not in del_nbr_dict[ctx_keys[0]]:
+ del_nbr_dict[ctx_keys[0]].append(re_nbr_pg.group(1))
+
+ # {'router bgp 65001': {'PG': [], 'PG1': []},
+ # 'router bgp 65001 vrf vrf1': {'PG': [], 'PG1': []}}
+ if ctx_keys[0] not in del_dict:
+ del_dict[ctx_keys[0]] = dict()
+ # find 'no neighbor <pg_name> peer-group'
+ re_pg = re.match("neighbor (\S+) peer-group$", line)
+ if re_pg and re_pg.group(1) not in del_dict[ctx_keys[0]]:
+ del_dict[ctx_keys[0]][re_pg.group(1)] = list()
+ found_pg_del_cmd = True
+
+ if found_pg_del_cmd == False:
+ bgp_delete_inst_move_line(lines_to_del)
+ if del_nbr_dict:
+ bgp_remove_neighbor_cfg(lines_to_del, del_nbr_dict)
+ return (lines_to_add, lines_to_del)
+
+ for (ctx_keys, line) in lines_to_del_to_app:
+ lines_to_del.remove((ctx_keys, line))
+ lines_to_del.append((ctx_keys, line))
+
+ # {'router bgp 65001': {'PG': ['10.1.1.2'], 'PG1': ['10.1.1.21']},
+ # 'router bgp 65001 vrf vrf1': {'PG': ['10.1.1.2'], 'PG1': ['10.1.1.21']}}
+ for (ctx_keys, line) in lines_to_del:
+ if (
+ ctx_keys[0].startswith("router bgp")
+ and line
+ and line.startswith("neighbor ")
+ ):
+ if ctx_keys[0] in del_dict:
+ for pg_key in del_dict[ctx_keys[0]]:
+ # 'neighbor <peer> [interface] peer-group <pg_name>'
+ nb_pg = "neighbor (\S+) .*peer-group %s$" % pg_key
+ re_nbr_pg = re.search(nb_pg, line)
+ if (
+ re_nbr_pg
+ and re_nbr_pg.group(1) not in del_dict[ctx_keys[0]][pg_key]
+ ):
+ del_dict[ctx_keys[0]][pg_key].append(re_nbr_pg.group(1))
+
+ lines_to_del_to_app = []
+ for (ctx_keys, line) in lines_to_del:
+ if (
+ ctx_keys[0].startswith("router bgp")
+ and line
+ and line.startswith("neighbor ")
+ ):
+ if ctx_keys[0] in del_dict:
+ for pg in del_dict[ctx_keys[0]]:
+ for nbr in del_dict[ctx_keys[0]][pg]:
+ nb_exp = "neighbor %s .*" % nbr
+ re_nb = re.search(nb_exp, line)
+ # add peer configs to delete list.
+ if re_nb and line not in lines_to_del_to_del:
+ lines_to_del_to_del.append((ctx_keys, line))
+
+ pg_exp = "neighbor %s peer-group$" % pg
+ re_pg = re.match(pg_exp, line)
+ if re_pg:
+ lines_to_del_to_app.append((ctx_keys, line))
+
+ for (ctx_keys, line) in lines_to_del_to_del:
+ lines_to_del.remove((ctx_keys, line))
+
+ for (ctx_keys, line) in lines_to_del_to_app:
+ lines_to_del.remove((ctx_keys, line))
+ lines_to_del.append((ctx_keys, line))
+
+ bgp_delete_inst_move_line(lines_to_del)
+
+ return (lines_to_add, lines_to_del)
+
+
+def pim_delete_move_lines(lines_to_add, lines_to_del):
+
+ # Under interface context, if 'no ip pim' is present
+ # remove subsequent 'no ip pim <blah>' options as it
+ # they are implicitly deleted by 'no ip pim'.
+ # Remove all such depdendent options from delete
+ # pending list.
+ pim_disable = False
+
+ for (ctx_keys, line) in lines_to_del:
+ if ctx_keys[0].startswith("interface") and line and line == "ip pim":
+ pim_disable = True
+
+ if pim_disable:
+ for (ctx_keys, line) in lines_to_del:
+ if (
+ ctx_keys[0].startswith("interface")
+ and line
+ and line.startswith("ip pim ")
+ ):
+ lines_to_del.remove((ctx_keys, line))
+
+ return (lines_to_add, lines_to_del)
+
+
+def delete_move_lines(lines_to_add, lines_to_del):
+
+ lines_to_add, lines_to_del = bgp_delete_move_lines(lines_to_add, lines_to_del)
+ lines_to_add, lines_to_del = pim_delete_move_lines(lines_to_add, lines_to_del)
+
+ return (lines_to_add, lines_to_del)
+
+
+def ignore_delete_re_add_lines(lines_to_add, lines_to_del):
+
+ # Quite possibly the most confusing (while accurate) variable names in history
+ lines_to_add_to_del = []
+ lines_to_del_to_del = []
+
+ index = 0
+ for (ctx_keys, line) in lines_to_del:
+ deleted = False
+
+ # no form of route-map description command only
+ # accept 'no description', replace 'no description blah'
+ # to just 'no description'.
+ index = index + 1
+ if (
+ ctx_keys[0].startswith("route-map")
+ and line
+ and line.startswith("description ")
+ ):
+ lines_to_del.remove((ctx_keys, line))
+ lines_to_del.insert(index, (ctx_keys, "description"))
+
+ # If there is a change in the segment routing block ranges, do it
+ # in-place, to avoid requesting spurious label chunks which might fail
+ if line and "segment-routing global-block" in line:
+ for (add_key, add_line) in lines_to_add:
+ if (
+ ctx_keys[0] == add_key[0]
+ and add_line
+ and "segment-routing global-block" in add_line
+ ):
+ lines_to_del_to_del.append((ctx_keys, line))
+ break
+ continue
+
+ if ctx_keys[0].startswith("router bgp") and line:
+
+ if line.startswith("neighbor "):
+ # BGP changed how it displays swpX peers that are part of peer-group. Older
+ # versions of frr would display these on separate lines:
+ # neighbor swp1 interface
+ # neighbor swp1 peer-group FOO
+ #
+ # but today we display via a single line
+ # neighbor swp1 interface peer-group FOO
+ #
+ # This change confuses frr-reload.py so check to see if we are deleting
+ # neighbor swp1 interface peer-group FOO
+ #
+ # and adding
+ # neighbor swp1 interface
+ # neighbor swp1 peer-group FOO
+ #
+ # If so then chop the del line and the corresponding add lines
+ re_swpx_int_peergroup = re.search(
+ "neighbor (\S+) interface peer-group (\S+)", line
+ )
+ re_swpx_int_v6only_peergroup = re.search(
+ "neighbor (\S+) interface v6only peer-group (\S+)", line
+ )
+
+ if re_swpx_int_peergroup or re_swpx_int_v6only_peergroup:
+ swpx_interface = None
+ swpx_peergroup = None
+
+ if re_swpx_int_peergroup:
+ swpx = re_swpx_int_peergroup.group(1)
+ peergroup = re_swpx_int_peergroup.group(2)
+ swpx_interface = "neighbor %s interface" % swpx
+ elif re_swpx_int_v6only_peergroup:
+ swpx = re_swpx_int_v6only_peergroup.group(1)
+ peergroup = re_swpx_int_v6only_peergroup.group(2)
+ swpx_interface = "neighbor %s interface v6only" % swpx
+
+ swpx_peergroup = "neighbor %s peer-group %s" % (swpx, peergroup)
+ found_add_swpx_interface = line_exist(
+ lines_to_add, ctx_keys, swpx_interface
+ )
+ found_add_swpx_peergroup = line_exist(
+ lines_to_add, ctx_keys, swpx_peergroup
+ )
+ tmp_ctx_keys = tuple(list(ctx_keys))
+
+ if not found_add_swpx_peergroup:
+ tmp_ctx_keys = list(ctx_keys)
+ tmp_ctx_keys.append("address-family ipv4 unicast")
+ tmp_ctx_keys = tuple(tmp_ctx_keys)
+ found_add_swpx_peergroup = line_exist(
+ lines_to_add, tmp_ctx_keys, swpx_peergroup
+ )
+
+ if not found_add_swpx_peergroup:
+ tmp_ctx_keys = list(ctx_keys)
+ tmp_ctx_keys.append("address-family ipv6 unicast")
+ tmp_ctx_keys = tuple(tmp_ctx_keys)
+ found_add_swpx_peergroup = line_exist(
+ lines_to_add, tmp_ctx_keys, swpx_peergroup
+ )
+
+ if found_add_swpx_interface and found_add_swpx_peergroup:
+ deleted = True
+ lines_to_del_to_del.append((ctx_keys, line))
+ lines_to_add_to_del.append((ctx_keys, swpx_interface))
+ lines_to_add_to_del.append((tmp_ctx_keys, swpx_peergroup))
+
+ # Changing the bfd timers on neighbors is allowed without doing
+ # a delete/add process. Since doing a "no neighbor blah bfd
+ # ..." will cause the peer to bounce unnecessarily, just skip
+ # the delete and just do the add.
+ re_nbr_bfd_timers = re.search(
+ r"neighbor (\S+) bfd (\S+) (\S+) (\S+)", line
+ )
+
+ if re_nbr_bfd_timers:
+ nbr = re_nbr_bfd_timers.group(1)
+ bfd_nbr = "neighbor %s" % nbr
+ bfd_search_string = bfd_nbr + r" bfd (\S+) (\S+) (\S+)"
+
+ for (ctx_keys, add_line) in lines_to_add:
+ if ctx_keys[0].startswith("router bgp"):
+ re_add_nbr_bfd_timers = re.search(
+ bfd_search_string, add_line
+ )
+
+ if re_add_nbr_bfd_timers:
+ found_add_bfd_nbr = line_exist(
+ lines_to_add, ctx_keys, bfd_nbr, False
+ )
+
+ if found_add_bfd_nbr:
+ lines_to_del_to_del.append((ctx_keys, line))
+
+ # Neighbor changes of route-maps need to be accounted for in
+ # that we do not want to do a `no route-map...` `route-map
+ # ....` when changing a route-map. This is bad mojo as that we
+ # will send/receive data we don't want. Additionally we need
+ # to ensure that if we have different afi/safi variants that
+ # they actually match and if we are going from a very old style
+ # command such that the neighbor command is under the `router
+ # bgp ..` node that we need to handle that appropriately
+ re_nbr_rm = re.search("neighbor(.*)route-map(.*)(in|out)$", line)
+ if re_nbr_rm:
+ adjust_for_bgp_node = 0
+ neighbor_name = re_nbr_rm.group(1)
+ rm_name_del = re_nbr_rm.group(2)
+ dir = re_nbr_rm.group(3)
+ search = "neighbor%sroute-map(.*)%s" % (neighbor_name, dir)
+ save_line = "EMPTY"
+ for (ctx_keys_al, add_line) in lines_to_add:
+ if ctx_keys_al[0].startswith("router bgp"):
+ if add_line:
+ rm_match = re.search(search, add_line)
+ if rm_match:
+ rm_name_add = rm_match.group(1)
+ if rm_name_add == rm_name_del:
+ continue
+ if len(ctx_keys_al) == 1:
+ save_line = line
+ adjust_for_bgp_node = 1
+ else:
+ if (
+ len(ctx_keys) > 1
+ and len(ctx_keys_al) > 1
+ and ctx_keys[1] == ctx_keys_al[1]
+ ):
+ lines_to_del_to_del.append((ctx_keys_al, line))
+
+ if adjust_for_bgp_node == 1:
+ for (ctx_keys_dl, dl_line) in lines_to_del:
+ if (
+ ctx_keys_dl[0].startswith("router bgp")
+ and len(ctx_keys_dl) > 1
+ and ctx_keys_dl[1] == "address-family ipv4 unicast"
+ ):
+ if save_line == dl_line:
+ lines_to_del_to_del.append((ctx_keys_dl, save_line))
+
+ # We changed how we display the neighbor interface command. Older
+ # versions of frr would display the following:
+ # neighbor swp1 interface
+ # neighbor swp1 remote-as external
+ # neighbor swp1 capability extended-nexthop
+ #
+ # but today we display via a single line
+ # neighbor swp1 interface remote-as external
+ #
+ # and capability extended-nexthop is no longer needed because we
+ # automatically enable it when the neighbor is of type interface.
+ #
+ # This change confuses frr-reload.py so check to see if we are deleting
+ # neighbor swp1 interface remote-as (external|internal|ASNUM)
+ #
+ # and adding
+ # neighbor swp1 interface
+ # neighbor swp1 remote-as (external|internal|ASNUM)
+ # neighbor swp1 capability extended-nexthop
+ #
+ # If so then chop the del line and the corresponding add lines
+ re_swpx_int_remoteas = re.search(
+ "neighbor (\S+) interface remote-as (\S+)", line
+ )
+ re_swpx_int_v6only_remoteas = re.search(
+ "neighbor (\S+) interface v6only remote-as (\S+)", line
+ )
+
+ if re_swpx_int_remoteas or re_swpx_int_v6only_remoteas:
+ swpx_interface = None
+ swpx_remoteas = None
+
+ if re_swpx_int_remoteas:
+ swpx = re_swpx_int_remoteas.group(1)
+ remoteas = re_swpx_int_remoteas.group(2)
+ swpx_interface = "neighbor %s interface" % swpx
+ elif re_swpx_int_v6only_remoteas:
+ swpx = re_swpx_int_v6only_remoteas.group(1)
+ remoteas = re_swpx_int_v6only_remoteas.group(2)
+ swpx_interface = "neighbor %s interface v6only" % swpx
+
+ swpx_remoteas = "neighbor %s remote-as %s" % (swpx, remoteas)
+ found_add_swpx_interface = line_exist(
+ lines_to_add, ctx_keys, swpx_interface
+ )
+ found_add_swpx_remoteas = line_exist(
+ lines_to_add, ctx_keys, swpx_remoteas
+ )
+ tmp_ctx_keys = tuple(list(ctx_keys))
+
+ if found_add_swpx_interface and found_add_swpx_remoteas:
+ deleted = True
+ lines_to_del_to_del.append((ctx_keys, line))
+ lines_to_add_to_del.append((ctx_keys, swpx_interface))
+ lines_to_add_to_del.append((tmp_ctx_keys, swpx_remoteas))
+
+ # We made the 'bgp bestpath as-path multipath-relax' command
+ # automatically assume 'no-as-set' since the lack of this option
+ # caused weird routing problems. When the running config is shown
+ # in releases with this change, the no-as-set keyword is not shown
+ # as it is the default. This causes frr-reload to unnecessarily
+ # unapply this option only to apply it back again, causing
+ # unnecessary session resets.
+ if "multipath-relax" in line:
+ re_asrelax_new = re.search(
+ "^bgp\s+bestpath\s+as-path\s+multipath-relax$", line
+ )
+ old_asrelax_cmd = "bgp bestpath as-path multipath-relax no-as-set"
+ found_asrelax_old = line_exist(lines_to_add, ctx_keys, old_asrelax_cmd)
+
+ if re_asrelax_new and found_asrelax_old:
+ deleted = True
+ lines_to_del_to_del.append((ctx_keys, line))
+ lines_to_add_to_del.append((ctx_keys, old_asrelax_cmd))
+
+ # If we are modifying the BGP table-map we need to avoid a del/add
+ # and instead modify the table-map in place via an add. This is
+ # needed to avoid installing all routes in the RIB the second the
+ # 'no table-map' is issued.
+ if line.startswith("table-map"):
+ found_table_map = line_exist(lines_to_add, ctx_keys, "table-map", False)
+
+ if found_table_map:
+ lines_to_del_to_del.append((ctx_keys, line))
+
+ # More old-to-new config handling. ip import-table no longer accepts
+ # distance, but we honor the old syntax. But 'show running' shows only
+ # the new syntax. This causes an unnecessary 'no import-table' followed
+ # by the same old 'ip import-table' which causes perturbations in
+ # announced routes leading to traffic blackholes. Fix this issue.
+ re_importtbl = re.search("^ip\s+import-table\s+(\d+)$", ctx_keys[0])
+ if re_importtbl:
+ table_num = re_importtbl.group(1)
+ for ctx in lines_to_add:
+ if ctx[0][0].startswith("ip import-table %s distance" % table_num):
+ lines_to_del_to_del.append(
+ (("ip import-table %s" % table_num,), None)
+ )
+ lines_to_add_to_del.append((ctx[0], None))
+
+ # ip/ipv6 prefix-lists and access-lists can be specified without a seq
+ # number. However, the running config always adds 'seq x', where x is
+ # a number incremented by 5 for every element of the prefix/access
+ # list. So, ignore such lines as well. Sample prefix-list and
+ # acces-list lines:
+ # ip prefix-list PR-TABLE-2 seq 5 permit 20.8.2.0/24 le 32
+ # ip prefix-list PR-TABLE-2 seq 10 permit 20.8.2.0/24 le 32
+ # ipv6 prefix-list vrfdev6-12 permit 2000:9:2::/64 gt 64
+ # access-list FOO seq 5 permit 2.2.2.2/32
+ # ipv6 access-list BAR seq 5 permit 2:2:2::2/128
+ re_acl_pfxlst = re.search(
+ "^(ip |ipv6 |)(prefix-list|access-list)(\s+\S+\s+)(seq \d+\s+)(permit|deny)(.*)$",
+ ctx_keys[0],
+ )
+ if re_acl_pfxlst:
+ found = False
+ tmpline = (
+ re_acl_pfxlst.group(1)
+ + re_acl_pfxlst.group(2)
+ + re_acl_pfxlst.group(3)
+ + re_acl_pfxlst.group(5)
+ + re_acl_pfxlst.group(6)
+ )
+ for ctx in lines_to_add:
+ if ctx[0][0] == tmpline:
+ lines_to_del_to_del.append((ctx_keys, None))
+ lines_to_add_to_del.append(((tmpline,), None))
+ found = True
+ # If prefix-lists or access-lists are being deleted and not added
+ # (see comment above), add command with 'no' to lines_to_add and
+ # remove from lines_to_del to improve scaling performance.
+ if found is False:
+ add_cmd = ("no " + ctx_keys[0],)
+ lines_to_add.append((add_cmd, None))
+ lines_to_del_to_del.append((ctx_keys, None))
+
+ # bgp community-list, large-community-list, extcommunity-list can be
+ # specified without a seq number. However, the running config always
+ # adds `seq X` (sequence number). So, ignore such lines as well.
+ # Examples:
+ # bgp community-list standard clist seq 5 permit 222:213
+ # bgp large-community-list standard llist seq 5 permit 65001:65001:1
+ # bgp extcommunity-list standard elist seq 5 permit soo 123:123
+ re_bgp_lists = re.search(
+ "^(bgp )(community-list|large-community-list|extcommunity-list)(\s+\S+\s+)(\S+\s+)(seq \d+\s+)(permit|deny)(.*)$",
+ ctx_keys[0],
+ )
+ if re_bgp_lists:
+ found = False
+ tmpline = (
+ re_bgp_lists.group(1)
+ + re_bgp_lists.group(2)
+ + re_bgp_lists.group(3)
+ + re_bgp_lists.group(4)
+ + re_bgp_lists.group(6)
+ + re_bgp_lists.group(7)
+ )
+ for ctx in lines_to_add:
+ if ctx[0][0] == tmpline:
+ lines_to_del_to_del.append((ctx_keys, None))
+ lines_to_add_to_del.append(((tmpline,), None))
+ found = True
+ if found is False:
+ add_cmd = ("no " + ctx_keys[0],)
+ lines_to_add.append((add_cmd, None))
+ lines_to_del_to_del.append((ctx_keys, None))
+
+ if (
+ len(ctx_keys) == 3
+ and ctx_keys[0].startswith("router bgp")
+ and ctx_keys[1] == "address-family l2vpn evpn"
+ and ctx_keys[2].startswith("vni")
+ ):
+
+ re_route_target = (
+ re.search("^route-target import (.*)$", line)
+ if line is not None
+ else False
+ )
+
+ if re_route_target:
+ rt = re_route_target.group(1).strip()
+ route_target_import_line = line
+ route_target_export_line = "route-target export %s" % rt
+ route_target_both_line = "route-target both %s" % rt
+
+ found_route_target_export_line = line_exist(
+ lines_to_del, ctx_keys, route_target_export_line
+ )
+ found_route_target_both_line = line_exist(
+ lines_to_add, ctx_keys, route_target_both_line
+ )
+
+ # If the running configs has
+ # route-target import 1:1
+ # route-target export 1:1
+ # and the config we are reloading against has
+ # route-target both 1:1
+ # then we can ignore deleting the import/export and ignore adding the 'both'
+ if found_route_target_export_line and found_route_target_both_line:
+ lines_to_del_to_del.append((ctx_keys, route_target_import_line))
+ lines_to_del_to_del.append((ctx_keys, route_target_export_line))
+ lines_to_add_to_del.append((ctx_keys, route_target_both_line))
+
+ # Deleting static routes under a vrf can lead to time-outs if each is sent
+ # as separate vtysh -c commands. Change them from being in lines_to_del and
+ # put the "no" form in lines_to_add
+ if ctx_keys[0].startswith("vrf ") and line:
+ if line.startswith("ip route") or line.startswith("ipv6 route"):
+ add_cmd = "no " + line
+ lines_to_add.append((ctx_keys, add_cmd))
+ lines_to_del_to_del.append((ctx_keys, line))
+
+ if not deleted:
+ found_add_line = line_exist(lines_to_add, ctx_keys, line)
+
+ if found_add_line:
+ lines_to_del_to_del.append((ctx_keys, line))
+ lines_to_add_to_del.append((ctx_keys, line))
+ else:
+ # We have commands that used to be displayed in the global part
+ # of 'router bgp' that are now displayed under 'address-family ipv4 unicast'
+ #
+ # # old way
+ # router bgp 64900
+ # neighbor ISL advertisement-interval 0
+ #
+ # vs.
+ #
+ # # new way
+ # router bgp 64900
+ # address-family ipv4 unicast
+ # neighbor ISL advertisement-interval 0
+ #
+ # Look to see if we are deleting it in one format just to add it back in the other
+ if (
+ ctx_keys[0].startswith("router bgp")
+ and len(ctx_keys) > 1
+ and ctx_keys[1] == "address-family ipv4 unicast"
+ ):
+ tmp_ctx_keys = list(ctx_keys)[:-1]
+ tmp_ctx_keys = tuple(tmp_ctx_keys)
+
+ found_add_line = line_exist(lines_to_add, tmp_ctx_keys, line)
+
+ if found_add_line:
+ lines_to_del_to_del.append((ctx_keys, line))
+ lines_to_add_to_del.append((tmp_ctx_keys, line))
+
+ for (ctx_keys, line) in lines_to_del_to_del:
+ try:
+ lines_to_del.remove((ctx_keys, line))
+ except ValueError:
+ pass
+
+ for (ctx_keys, line) in lines_to_add_to_del:
+ try:
+ lines_to_add.remove((ctx_keys, line))
+ except ValueError:
+ pass
+
+
+ return (lines_to_add, lines_to_del)
+
+
+def ignore_unconfigurable_lines(lines_to_add, lines_to_del):
+ """
+ There are certain commands that cannot be removed. Remove
+ those commands from lines_to_del.
+ """
+ lines_to_del_to_del = []
+
+ for (ctx_keys, line) in lines_to_del:
+
+ # The integrated-vtysh-config one is technically "no"able but if we did
+ # so frr-reload would stop working so do not let the user shoot
+ # themselves in the foot by removing this.
+ if any(
+ [
+ ctx_keys[0].startswith(x)
+ for x in [
+ "agentx",
+ "frr version",
+ "frr defaults",
+ "username",
+ "password",
+ "line vty",
+ "service integrated-vtysh-config",
+ ]
+ ]
+ ):
+ log.info('"%s" cannot be removed' % (ctx_keys[-1],))
+ lines_to_del_to_del.append((ctx_keys, line))
+
+ for (ctx_keys, line) in lines_to_del_to_del:
+ lines_to_del.remove((ctx_keys, line))
+
+ return (lines_to_add, lines_to_del)
+
+
+def compare_context_objects(newconf, running):
+ """
+ Create a context diff for the two specified contexts
+ """
+
+ # Compare the two Config objects to find the lines that we need to add/del
+ lines_to_add = []
+ lines_to_del = []
+ pollist_to_del = []
+ seglist_to_del = []
+ pceconf_to_del = []
+ pcclist_to_del = []
+ candidates_to_add = []
+ delete_bgpd = False
+ area_stub_no_sum = "area (\S+) stub no-summary"
+ deleted_keychains = []
+
+ # Find contexts that are in newconf but not in running
+ # Find contexts that are in running but not in newconf
+ for (running_ctx_keys, running_ctx) in iteritems(running.contexts):
+
+ if running_ctx_keys in newconf.contexts:
+ newconf_ctx = newconf.contexts[running_ctx_keys]
+
+ for line in running_ctx.lines:
+ # ospf area <> stub no-summary line removal requires
+ # to remoe area <> stub as no form of original
+ # retains the stub form.
+ # lines_to_del will contain:
+ # no area <x> stub no-summary and
+ # no area <x> stub
+ if (
+ running_ctx_keys[0].startswith("router ospf")
+ and line not in newconf_ctx.dlines
+ ):
+ re_area_stub_no_sum = re.search(area_stub_no_sum, line)
+ if re_area_stub_no_sum:
+ new_del_line = "area %s stub" % re_area_stub_no_sum.group(1)
+ lines_to_del.append((running_ctx_keys, new_del_line))
+
+ if running_ctx_keys not in newconf.contexts:
+
+ # We check that the len is 1 here so that we only look at ('router bgp 10')
+ # and not ('router bgp 10', 'address-family ipv4 unicast'). The
+ # latter could cause a false delete_bgpd positive if ipv4 unicast is in
+ # running but not in newconf.
+ if "router bgp" in running_ctx_keys[0] and len(running_ctx_keys) == 1:
+ delete_bgpd = True
+ lines_to_del.append((running_ctx_keys, None))
+
+ # We cannot do 'no interface' or 'no vrf' in FRR, and so deal with it
+ elif running_ctx_keys[0].startswith("interface") or running_ctx_keys[
+ 0
+ ].startswith("vrf"):
+ for line in running_ctx.lines:
+ lines_to_del.append((running_ctx_keys, line))
+
+ # If this is an address-family under 'router bgp' and we are already deleting the
+ # entire 'router bgp' context then ignore this sub-context
+ elif (
+ "router bgp" in running_ctx_keys[0]
+ and len(running_ctx_keys) > 1
+ and delete_bgpd
+ ):
+ continue
+
+ # Check if key chain is being deleted:
+ # - If it is being deleted then avoid deleting its contexts
+ # - Else delete its configuration without removing the root node
+ elif (
+ running_ctx_keys[0].startswith("key chain ")
+ and len(running_ctx_keys) == 1
+ ):
+ deleted_keychains.append(running_ctx_keys[0])
+ lines_to_del.append((running_ctx_keys, None))
+ elif (
+ running_ctx_keys[0].startswith("key chain ")
+ and len(running_ctx_keys) > 1
+ and running_ctx_keys[0] in deleted_keychains
+ ):
+ continue
+
+ # Delete an entire vni sub-context under "address-family l2vpn evpn"
+ elif (
+ "router bgp" in running_ctx_keys[0]
+ and len(running_ctx_keys) > 2
+ and running_ctx_keys[1].startswith("address-family l2vpn evpn")
+ and running_ctx_keys[2].startswith("vni ")
+ ):
+ lines_to_del.append((running_ctx_keys, None))
+
+ elif (
+ "router bgp" in running_ctx_keys[0]
+ and len(running_ctx_keys) > 1
+ and running_ctx_keys[1].startswith("address-family")
+ ):
+ # There's no 'no address-family' support and so we have to
+ # delete each line individually again
+ for line in running_ctx.lines:
+ lines_to_del.append((running_ctx_keys, line))
+
+ # Some commands can happen at higher counts that make
+ # doing vtysh -c inefficient (and can time out.) For
+ # these commands, instead of adding them to lines_to_del,
+ # add the "no " version to lines_to_add.
+ elif running_ctx_keys[0].startswith("ip route") or running_ctx_keys[
+ 0
+ ].startswith("ipv6 route"):
+ add_cmd = ("no " + running_ctx_keys[0],)
+ lines_to_add.append((add_cmd, None))
+
+ # if this an interface sub-subcontext in an address-family block in ldpd and
+ # we are already deleting the whole context, then ignore this
+ elif (
+ len(running_ctx_keys) > 2
+ and running_ctx_keys[0].startswith("mpls ldp")
+ and running_ctx_keys[1].startswith("address-family")
+ and (running_ctx_keys[:2], None) in lines_to_del
+ ):
+ continue
+
+ # same thing for a pseudowire sub-context inside an l2vpn context
+ elif (
+ len(running_ctx_keys) > 1
+ and running_ctx_keys[0].startswith("l2vpn")
+ and running_ctx_keys[1].startswith("member pseudowire")
+ and (running_ctx_keys[:1], None) in lines_to_del
+ ):
+ continue
+
+ # Segment routing and traffic engineering never need to be deleted
+ elif (
+ running_ctx_keys[0].startswith("segment-routing")
+ and len(running_ctx_keys) < 3
+ ):
+ continue
+
+ # Neither the pcep command
+ elif (
+ len(running_ctx_keys) == 3
+ and running_ctx_keys[0].startswith("segment-routing")
+ and running_ctx_keys[2].startswith("pcep")
+ ):
+ continue
+
+ # Segment lists can only be deleted after we removed all the candidate paths that
+ # use them, so add them to a separate array that is going to be appended at the end
+ elif (
+ len(running_ctx_keys) == 3
+ and running_ctx_keys[0].startswith("segment-routing")
+ and running_ctx_keys[2].startswith("segment-list")
+ ):
+ seglist_to_del.append((running_ctx_keys, None))
+
+ # Policies must be deleted after there candidate path, to be sure
+ # we add them to a separate array that is going to be appended at the end
+ elif (
+ len(running_ctx_keys) == 3
+ and running_ctx_keys[0].startswith("segment-routing")
+ and running_ctx_keys[2].startswith("policy")
+ ):
+ pollist_to_del.append((running_ctx_keys, None))
+
+ # pce-config must be deleted after the pce, to be sure we add them
+ # to a separate array that is going to be appended at the end
+ elif (
+ len(running_ctx_keys) >= 4
+ and running_ctx_keys[0].startswith("segment-routing")
+ and running_ctx_keys[3].startswith("pce-config")
+ ):
+ pceconf_to_del.append((running_ctx_keys, None))
+
+ # pcc must be deleted after the pce and pce-config too
+ elif (
+ len(running_ctx_keys) >= 4
+ and running_ctx_keys[0].startswith("segment-routing")
+ and running_ctx_keys[3].startswith("pcc")
+ ):
+ pcclist_to_del.append((running_ctx_keys, None))
+
+ # Non-global context
+ elif running_ctx_keys and not any(
+ "address-family" in key for key in running_ctx_keys
+ ):
+ lines_to_del.append((running_ctx_keys, None))
+
+ elif running_ctx_keys and not any("vni" in key for key in running_ctx_keys):
+ lines_to_del.append((running_ctx_keys, None))
+
+ # Global context
+ else:
+ for line in running_ctx.lines:
+ lines_to_del.append((running_ctx_keys, line))
+
+ # if we have some policies commands to delete, append them to lines_to_del
+ if len(pollist_to_del) > 0:
+ lines_to_del.extend(pollist_to_del)
+
+ # if we have some segment list commands to delete, append them to lines_to_del
+ if len(seglist_to_del) > 0:
+ lines_to_del.extend(seglist_to_del)
+
+ # if we have some pce list commands to delete, append them to lines_to_del
+ if len(pceconf_to_del) > 0:
+ lines_to_del.extend(pceconf_to_del)
+
+ # if we have some pcc list commands to delete, append them to lines_to_del
+ if len(pcclist_to_del) > 0:
+ lines_to_del.extend(pcclist_to_del)
+
+ # Find the lines within each context to add
+ # Find the lines within each context to del
+ for (newconf_ctx_keys, newconf_ctx) in iteritems(newconf.contexts):
+
+ if newconf_ctx_keys in running.contexts:
+ running_ctx = running.contexts[newconf_ctx_keys]
+
+ for line in newconf_ctx.lines:
+ if line not in running_ctx.dlines:
+
+ # candidate paths can only be added after the policy and segment list,
+ # so add them to a separate array that is going to be appended at the end
+ if (
+ len(newconf_ctx_keys) == 3
+ and newconf_ctx_keys[0].startswith("segment-routing")
+ and newconf_ctx_keys[2].startswith("policy ")
+ and line.startswith("candidate-path ")
+ ):
+ candidates_to_add.append((newconf_ctx_keys, line))
+
+ else:
+ lines_to_add.append((newconf_ctx_keys, line))
+
+ for line in running_ctx.lines:
+ if line not in newconf_ctx.dlines:
+ lines_to_del.append((newconf_ctx_keys, line))
+
+ for (newconf_ctx_keys, newconf_ctx) in iteritems(newconf.contexts):
+
+ if newconf_ctx_keys not in running.contexts:
+
+ # candidate paths can only be added after the policy and segment list,
+ # so add them to a separate array that is going to be appended at the end
+ if (
+ len(newconf_ctx_keys) == 4
+ and newconf_ctx_keys[0].startswith("segment-routing")
+ and newconf_ctx_keys[3].startswith("candidate-path")
+ ):
+ candidates_to_add.append((newconf_ctx_keys, None))
+ for line in newconf_ctx.lines:
+ candidates_to_add.append((newconf_ctx_keys, line))
+
+ else:
+ lines_to_add.append((newconf_ctx_keys, None))
+
+ for line in newconf_ctx.lines:
+ lines_to_add.append((newconf_ctx_keys, line))
+
+ # if we have some candidate paths commands to add, append them to lines_to_add
+ if len(candidates_to_add) > 0:
+ lines_to_add.extend(candidates_to_add)
+
+ (lines_to_add, lines_to_del) = ignore_delete_re_add_lines(
+ lines_to_add, lines_to_del
+ )
+ (lines_to_add, lines_to_del) = delete_move_lines(lines_to_add, lines_to_del)
+ (lines_to_add, lines_to_del) = ignore_unconfigurable_lines(
+ lines_to_add, lines_to_del
+ )
+
+ return (lines_to_add, lines_to_del)
+
+
+if __name__ == "__main__":
+ # Command line options
+ parser = argparse.ArgumentParser(
+ description="Dynamically apply diff in frr configs"
+ )
+ parser.add_argument(
+ "--input", help='Read running config from file instead of "show running"'
+ )
+ group = parser.add_mutually_exclusive_group(required=True)
+ group.add_argument(
+ "--reload", action="store_true", help="Apply the deltas", default=False
+ )
+ group.add_argument(
+ "--test", action="store_true", help="Show the deltas", default=False
+ )
+ level_group = parser.add_mutually_exclusive_group()
+ level_group.add_argument(
+ "--debug",
+ action="store_true",
+ help="Enable debugs (synonym for --log-level=debug)",
+ default=False,
+ )
+ level_group.add_argument(
+ "--log-level",
+ help="Log level",
+ default="info",
+ choices=("critical", "error", "warning", "info", "debug"),
+ )
+ parser.add_argument(
+ "--stdout", action="store_true", help="Log to STDOUT", default=False
+ )
+ parser.add_argument(
+ "--pathspace",
+ "-N",
+ metavar="NAME",
+ help="Reload specified path/namespace",
+ default=None,
+ )
+ parser.add_argument("filename", help="Location of new frr config file")
+ parser.add_argument(
+ "--overwrite",
+ action="store_true",
+ help="Overwrite frr.conf with running config output",
+ default=False,
+ )
+ parser.add_argument(
+ "--bindir", help="path to the vtysh executable", default="/usr/bin"
+ )
+ parser.add_argument(
+ "--confdir", help="path to the daemon config files", default="/etc/frr"
+ )
+ parser.add_argument(
+ "--rundir", help="path for the temp config file", default="/var/run/frr"
+ )
+ parser.add_argument(
+ "--vty_socket",
+ help="socket to be used by vtysh to connect to the daemons",
+ default=None,
+ )
+ parser.add_argument(
+ "--daemon", help="daemon for which want to replace the config", default=""
+ )
+ parser.add_argument(
+ "--test-reset",
+ action="store_true",
+ help="Used by topotest to not delete debug or log file commands",
+ )
+
+ args = parser.parse_args()
+
+ # Logging
+ # For --test log to stdout
+ # For --reload log to /var/log/frr/frr-reload.log
+ if args.test or args.stdout:
+ logging.basicConfig(format="%(asctime)s %(levelname)5s: %(message)s")
+
+ # Color the errors and warnings in red
+ logging.addLevelName(
+ logging.ERROR, "\033[91m %s\033[0m" % logging.getLevelName(logging.ERROR)
+ )
+ logging.addLevelName(
+ logging.WARNING, "\033[91m%s\033[0m" % logging.getLevelName(logging.WARNING)
+ )
+
+ elif args.reload:
+ if not os.path.isdir("/var/log/frr/"):
+ os.makedirs("/var/log/frr/", mode=0o0755)
+
+ logging.basicConfig(
+ filename="/var/log/frr/frr-reload.log",
+ format="%(asctime)s %(levelname)5s: %(message)s",
+ )
+
+ # argparse should prevent this from happening but just to be safe...
+ else:
+ raise Exception("Must specify --reload or --test")
+ log = logging.getLogger(__name__)
+
+ if args.debug:
+ log.setLevel(logging.DEBUG)
+ else:
+ log.setLevel(args.log_level.upper())
+
+ if args.reload and not args.stdout:
+ # Additionally send errors and above to STDOUT, with no metadata,
+ # when we are logging to a file. This specifically does not follow
+ # args.log_level, and is analagous to behaviour in earlier versions
+ # which additionally logged most errors using print().
+
+ stdout_hdlr = logging.StreamHandler(sys.stdout)
+ stdout_hdlr.setLevel(logging.ERROR)
+ stdout_hdlr.setFormatter(logging.Formatter())
+ log.addHandler(stdout_hdlr)
+
+ # Verify the new config file is valid
+ if not os.path.isfile(args.filename):
+ log.error("Filename %s does not exist" % args.filename)
+ sys.exit(1)
+
+ if not os.path.getsize(args.filename):
+ log.error("Filename %s is an empty file" % args.filename)
+ sys.exit(1)
+
+ # Verify that confdir is correct
+ if not os.path.isdir(args.confdir):
+ log.error("Confdir %s is not a valid path" % args.confdir)
+ sys.exit(1)
+
+ # Verify that bindir is correct
+ if not os.path.isdir(args.bindir) or not os.path.isfile(args.bindir + "/vtysh"):
+ log.error("Bindir %s is not a valid path to vtysh" % args.bindir)
+ sys.exit(1)
+
+ # verify that the vty_socket, if specified, is valid
+ if args.vty_socket and not os.path.isdir(args.vty_socket):
+ log.error("vty_socket %s is not a valid path" % args.vty_socket)
+ sys.exit(1)
+
+ # verify that the daemon, if specified, is valid
+ if args.daemon and args.daemon not in [
+ "zebra",
+ "bgpd",
+ "fabricd",
+ "isisd",
+ "babeld",
+ "ospf6d",
+ "ospfd",
+ "pbrd",
+ "pimd",
+ "pim6d",
+ "ripd",
+ "ripngd",
+ "sharpd",
+ "staticd",
+ "vrrpd",
+ "ldpd",
+ "nhrpd",
+ "pathd",
+ "bfdd",
+ "eigrpd",
+ ]:
+ msg = "Daemon %s is not a valid option for 'show running-config'" % args.daemon
+ print(msg)
+ log.error(msg)
+ sys.exit(1)
+
+ vtysh = Vtysh(args.bindir, args.confdir, args.vty_socket, args.pathspace)
+
+ # Verify that 'service integrated-vtysh-config' is configured
+ if args.pathspace:
+ vtysh_filename = args.confdir + "/" + args.pathspace + "/vtysh.conf"
+ else:
+ vtysh_filename = args.confdir + "/vtysh.conf"
+ service_integrated_vtysh_config = True
+
+ if os.path.isfile(vtysh_filename):
+ with open(vtysh_filename, "r") as fh:
+ for line in fh.readlines():
+ line = line.strip()
+
+ if line == "no service integrated-vtysh-config":
+ service_integrated_vtysh_config = False
+ break
+
+ if not args.test and not service_integrated_vtysh_config and not args.daemon:
+ log.error(
+ "'service integrated-vtysh-config' is not configured, this is required for 'service frr reload'"
+ )
+ sys.exit(1)
+
+ log.info('Called via "%s"', str(args))
+
+ # Create a Config object from the config generated by newconf
+ newconf = Config(vtysh)
+ try:
+ newconf.load_from_file(args.filename)
+ reload_ok = True
+ except VtyshException as ve:
+ log.error("vtysh failed to process new configuration: {}".format(ve))
+ reload_ok = False
+
+ if args.test:
+
+ # Create a Config object from the running config
+ running = Config(vtysh)
+
+ if args.input:
+ running.load_from_file(args.input)
+ else:
+ running.load_from_show_running(args.daemon)
+
+ (lines_to_add, lines_to_del) = compare_context_objects(newconf, running)
+
+ if lines_to_del:
+ if not args.test_reset:
+ print("\nLines To Delete")
+ print("===============")
+
+ for (ctx_keys, line) in lines_to_del:
+
+ if line == "!":
+ continue
+
+ nolines = lines_to_config(ctx_keys, line, True)
+
+ if args.test_reset:
+ # For topotests the original code stripped the lines, and ommitted blank lines
+ # after, do that here
+ nolines = [x.strip() for x in nolines]
+ # For topotests leave these lines in (don't delete them)
+ # [chopps: why is "log file" more special than other "log" commands?]
+ nolines = [
+ x for x in nolines if "debug" not in x and "log file" not in x
+ ]
+ if not nolines:
+ continue
+
+ cmd = "\n".join(nolines)
+ print(cmd)
+
+ if lines_to_add:
+ if not args.test_reset:
+ print("\nLines To Add")
+ print("============")
+
+ for (ctx_keys, line) in lines_to_add:
+
+ if line == "!":
+ continue
+
+ lines = lines_to_config(ctx_keys, line, False)
+
+ if args.test_reset:
+ # For topotests the original code stripped the lines, and ommitted blank lines
+ # after, do that here
+ lines = [x.strip() for x in lines if x.strip()]
+ if not lines:
+ continue
+
+ cmd = "\n".join(lines)
+ print(cmd)
+
+ elif args.reload:
+ lines_to_configure = []
+
+ # We will not be able to do anything, go ahead and exit(1)
+ if not vtysh.is_config_available() or not reload_ok:
+ sys.exit(1)
+
+ log.debug("New Frr Config\n%s", newconf.get_lines())
+
+ # This looks a little odd but we have to do this twice...here is why
+ # If the user had this running bgp config:
+ #
+ # router bgp 10
+ # neighbor 1.1.1.1 remote-as 50
+ # neighbor 1.1.1.1 route-map FOO out
+ #
+ # and this config in the newconf config file
+ #
+ # router bgp 10
+ # neighbor 1.1.1.1 remote-as 999
+ # neighbor 1.1.1.1 route-map FOO out
+ #
+ #
+ # Then the script will do
+ # - no neighbor 1.1.1.1 remote-as 50
+ # - neighbor 1.1.1.1 remote-as 999
+ #
+ # The problem is the "no neighbor 1.1.1.1 remote-as 50" will also remove
+ # the "neighbor 1.1.1.1 route-map FOO out" line...so we compare the
+ # configs again to put this line back.
+
+ # There are many keywords in FRR that can only appear one time under
+ # a context, take "bgp router-id" for example. If the config that we are
+ # reloading against has the following:
+ #
+ # router bgp 10
+ # bgp router-id 1.1.1.1
+ # bgp router-id 2.2.2.2
+ #
+ # The final config needs to contain "bgp router-id 2.2.2.2". On the
+ # first pass we will add "bgp router-id 2.2.2.2" but then on the second
+ # pass we will see that "bgp router-id 1.1.1.1" is missing and add that
+ # back which cancels out the "bgp router-id 2.2.2.2". The fix is for the
+ # second pass to include all of the "adds" from the first pass.
+ lines_to_add_first_pass = []
+
+ for x in range(2):
+ running = Config(vtysh)
+ running.load_from_show_running(args.daemon)
+ log.debug("Running Frr Config (Pass #%d)\n%s", x, running.get_lines())
+
+ (lines_to_add, lines_to_del) = compare_context_objects(newconf, running)
+
+ if x == 0:
+ lines_to_add_first_pass = lines_to_add
+ else:
+ lines_to_add.extend(lines_to_add_first_pass)
+
+ # Only do deletes on the first pass. The reason being if we
+ # configure a bgp neighbor via "neighbor swp1 interface" FRR
+ # will automatically add:
+ #
+ # interface swp1
+ # ipv6 nd ra-interval 10
+ # no ipv6 nd suppress-ra
+ # !
+ #
+ # but those lines aren't in the config we are reloading against so
+ # on the 2nd pass they will show up in lines_to_del. This could
+ # apply to other scenarios as well where configuring FOO adds BAR
+ # to the config.
+ if lines_to_del and x == 0:
+ for (ctx_keys, line) in lines_to_del:
+
+ if line == "!":
+ continue
+
+ # 'no' commands are tricky, we can't just put them in a file and
+ # vtysh -f that file. See the next comment for an explanation
+ # of their quirks
+ cmd = lines_to_config(ctx_keys, line, True)
+ original_cmd = cmd
+
+ # Some commands in frr are picky about taking a "no" of the entire line.
+ # OSPF is bad about this, you can't "no" the entire line, you have to "no"
+ # only the beginning. If we hit one of these command an exception will be
+ # thrown. Catch it and remove the last '-c', 'FOO' from cmd and try again.
+ #
+ # Example:
+ # frr(config-if)# ip ospf authentication message-digest 1.1.1.1
+ # frr(config-if)# no ip ospf authentication message-digest 1.1.1.1
+ # % Unknown command.
+ # frr(config-if)# no ip ospf authentication message-digest
+ # % Unknown command.
+ # frr(config-if)# no ip ospf authentication
+ # frr(config-if)#
+
+ stdouts = []
+ while True:
+ try:
+ vtysh(["configure"] + cmd, stdouts)
+
+ except VtyshException:
+
+ # - Pull the last entry from cmd (this would be
+ # 'no ip ospf authentication message-digest 1.1.1.1' in
+ # our example above
+ # - Split that last entry by whitespace and drop the last word
+ log.info("Failed to execute %s", " ".join(cmd))
+ last_arg = cmd[-1].split(" ")
+
+ if len(last_arg) <= 2:
+ log.error(
+ '"%s" we failed to remove this command',
+ " -- ".join(original_cmd),
+ )
+ # Log first error msg for original_cmd
+ if stdouts:
+ log.error(stdouts[0])
+ reload_ok = False
+ break
+
+ new_last_arg = last_arg[0:-1]
+ cmd[-1] = " ".join(new_last_arg)
+ else:
+ log.info('Executed "%s"', " ".join(cmd))
+ break
+
+ if lines_to_add:
+ lines_to_configure = []
+
+ for (ctx_keys, line) in lines_to_add:
+
+ if line == "!":
+ continue
+
+ # Don't run "no" commands twice since they can error
+ # out the second time due to first deletion
+ if x == 1 and ctx_keys[0].startswith("no "):
+ continue
+
+ cmd = "\n".join(lines_to_config(ctx_keys, line, False)) + "\n"
+ lines_to_configure.append(cmd)
+
+ if lines_to_configure:
+ random_string = "".join(
+ random.SystemRandom().choice(
+ string.ascii_uppercase + string.digits
+ )
+ for _ in range(6)
+ )
+
+ filename = args.rundir + "/reload-%s.txt" % random_string
+ log.info("%s content\n%s" % (filename, pformat(lines_to_configure)))
+
+ with open(filename, "w") as fh:
+ for line in lines_to_configure:
+ fh.write(line + "\n")
+
+ try:
+ vtysh.exec_file(filename)
+ except VtyshException as e:
+ log.warning("frr-reload.py failed due to\n%s" % e.args)
+ reload_ok = False
+ os.unlink(filename)
+
+ # Make these changes persistent
+ target = str(args.confdir + "/frr.conf")
+ if args.overwrite or (not args.daemon and args.filename != target):
+ vtysh("write")
+
+ if not reload_ok:
+ sys.exit(1)
diff --git a/tools/frr.in b/tools/frr.in
new file mode 100755
index 0000000..cd24a96
--- /dev/null
+++ b/tools/frr.in
@@ -0,0 +1,612 @@
+#!/bin/bash
+#
+### BEGIN INIT INFO
+# Provides: frr
+# Required-Start: $local_fs $network $remote_fs $syslog
+# Required-Stop: $local_fs $network $remote_fs $syslog
+# Default-Start: 2 3 4 5
+# Default-Stop: 0 1 6
+# Short-Description: start and stop the Frr routing suite
+# Description: Frr is a routing suite for IP routing protocols like
+# BGP, OSPF, RIP and others. This script contols the main
+# daemon "frr" as well as the individual protocol daemons.
+### END INIT INFO
+#
+
+PATH=/bin:/usr/bin:/sbin:/usr/sbin
+D_PATH="@CFG_SBIN@" # /usr/lib/frr
+C_PATH="@CFG_SYSCONF@" # /etc/frr
+V_PATH="@CFG_STATE@" # /var/run/frr
+B_PATH="@CFG_BIN@"
+VTYSH="@vtysh_bin@" # /usr/bin/vtysh
+FRR_USER="@enable_user@" # frr
+FRR_GROUP="@enable_group@" # frr
+FRR_VTY_GROUP="@enable_vty_group@" # frrvty
+FRR_CONFIG_MODE="@enable_configfile_mask@" # 0600
+FRR_DEFAULT_PROFILE="@DFLT_NAME@" # traditional / datacenter
+
+# Local Daemon selection may be done by using /etc/frr/daemons.
+# See /usr/share/doc/frr/README.Debian.gz for further information.
+# Keep zebra first and do not list watchfrr!
+DAEMONS="mgmtd zebra bgpd ripd ripngd ospfd ospf6d isisd babeld pimd pim6d ldpd nhrpd eigrpd sharpd pbrd staticd bfdd fabricd vrrpd pathd"
+MAX_INSTANCES=5
+RELOAD_SCRIPT="$D_PATH/frr-reload.py"
+
+if [ -e /lib/lsb/init-functions ]; then
+ . /lib/lsb/init-functions
+fi
+
+if [ -f $D_PATH/ssd ]; then
+ SSD=$D_PATH/ssd
+else
+ SSD=`which start-stop-daemon`
+fi
+
+# Print the name of the pidfile.
+pidfile()
+{
+ echo "$V_PATH/$1.pid"
+}
+
+# Print the name of the vtysh.
+vtyfile()
+{
+ echo "$V_PATH/$1.vty"
+}
+
+# Check if daemon is started by using the pidfile.
+started()
+{
+ [ ! -e `pidfile $1` ] && return 3
+ if [ -n "$2" ] && [ "$2" == "log" ]; then
+ status_of_proc -p `pidfile $1` $1 $1 && return 0 || return $?
+ else
+ kill -0 `cat \`pidfile $1\`` 2> /dev/null || return 1
+ return 0
+ fi
+}
+
+# Loads the config via vtysh -b if configured to do so.
+vtysh_b ()
+{
+ # Rember, that all variables have been incremented by 1 in convert_daemon_prios()
+ if [ "$vtysh_enable" = 2 -a -f $C_PATH/frr.conf ]; then
+ $VTYSH -b
+ fi
+}
+
+# Check if the daemon is activated and if its executable and config files
+# are in place.
+# params: daemon name
+# returns: 0=ok, 1=error
+check_daemon()
+{
+ if [ $1 != "watchfrr" -a $1 != "vtysh_enable" ]; then
+ # check for daemon binary
+ if [ ! -x "$D_PATH/$1" ]; then return 1; fi
+ fi
+
+ # If the integrated config file is used the others are not checked.
+ if [ -r "$C_PATH/frr.conf" ]; then
+ return 0
+ fi
+
+ # vtysh_enable has no config file nor binary so skip check.
+ # (Not sure why vtysh_enable is in this list but does not hurt)
+ if [ $1 != "watchfrr" -a $1 != "vtysh_enable" ]; then
+ # check for config file
+ if [ -n "$2" ]; then
+ if [ ! -r "$C_PATH/$1-$2.conf" ]; then
+ install -g "$FRR_GROUP" -o "$FRR_USER" -m "$FRR_CONFIG_MODE" /dev/null "$C_PATH/$1-$2.conf"
+ fi
+ elif [ ! -r "$C_PATH/$1.conf" ]; then
+ install -g "$FRR_GROUP" -o "$FRR_USER" -m "$FRR_CONFIG_MODE" /dev/null "$C_PATH/$1.conf"
+ fi
+ fi
+ return 0
+}
+
+# Starts the server if it's not alrady running according to the pid file.
+# The Frr daemons creates the pidfile when starting.
+start()
+{
+ local dmn inst
+ dmn="$1"
+ inst="$2"
+
+ ulimit -n $MAX_FDS > /dev/null 2> /dev/null
+ if [ "$dmn" = "watchfrr" ]; then
+
+ # We may need to restart watchfrr if new daemons are added and/or
+ # removed
+ if started "$dmn" ; then
+ stop watchfrr
+ else
+ # Echo only once. watchfrr is printed in the stop above
+ echo -n " $dmn"
+ fi
+
+ eval "set - $watchfrr_options"
+ ${SSD} \
+ --start \
+ --pidfile=`pidfile $dmn` \
+ --exec "$D_PATH/$dmn" \
+ -- \
+ "$@"
+
+ elif [ -n "$inst" ]; then
+ echo -n " $dmn-$inst"
+ if ! check_daemon $dmn $inst ; then
+ echo -n " (binary does not exist)"
+ return;
+ fi
+
+ ${SSD} \
+ --start \
+ --pidfile=`pidfile $dmn-$inst` \
+ --exec "$D_PATH/$dmn" \
+ -- \
+ `eval echo "$""$dmn""_options"` $frr_global_options -n "$inst"
+ else
+ if ! check_daemon $dmn; then
+ echo -n " (binary does not exist)"
+ return;
+ fi
+
+ if [ "$valgrind_enable" = "yes" ]; then
+ ${SSD} \
+ --start \
+ --pidfile=`pidfile $dmn` \
+ --exec "$valgrind" \
+ -- --trace-children=no --leak-check=full --log-file=/var/log/frr/$dmn-valgrind.log $D_PATH/$dmn \
+ `eval echo "$""$dmn""_options"` $frr_global_options
+ else
+ ${SSD} \
+ --start \
+ --pidfile=`pidfile $dmn` \
+ --exec "$D_PATH/$dmn" \
+ -- \
+ `eval echo "$""$dmn""_options"` $frr_global_options
+ fi
+ fi
+
+ # Start the staticd automatically
+ if [ "$dmn" = "zebra" ]; then
+ echo -n "starting staticd since zebra is running"
+ if ! check_daemon staticd ; then
+ echo -n " (binary does not exist)"
+ return;
+ fi
+
+ ${SSD} \
+ --start \
+ --pidfile=`pidfile staticd` \
+ --exec "$D_PATH/staticd" \
+ -- \
+ `eval echo "$"staticd"_options"` $frr_global_options
+ fi
+}
+
+# Stop the daemon given in the parameter, printing its name to the terminal.
+stop()
+{
+ local inst
+
+ if [ -n "$2" ]; then
+ inst="$1-$2"
+ else
+ inst="$1"
+ fi
+
+ if ! started "$inst" ; then
+ echo -n " ($inst)"
+ return 0
+ else
+ PIDFILE=`pidfile $inst`
+ PID=`cat $PIDFILE 2>/dev/null`
+ kill -2 $PID 2>/dev/null
+ #
+ # Now we have to wait until $DAEMON has _really_ stopped.
+ #
+ if test -n "$PID" && kill -0 $PID 2>/dev/null; then
+ cnt=0
+ while kill -0 $PID 2>/dev/null; do
+ cnt=`expr $cnt + 1`
+ if [ $cnt -gt 60 ]; then
+ # Waited 120 secs now, fail.
+ echo -n "Failed.. "
+ break
+ fi
+ sleep 2
+ done
+ fi
+ rm -f `pidfile $inst`
+ rm -f `vtyfile $inst`
+
+ if [ "$1" = "zebra" ]; then
+ echo -n "Stopping staticd since zebra is running"
+ stop staticd
+ fi
+ fi
+}
+
+# Converts values from /etc/frr/daemons to all-numeric values.
+convert_daemon_prios()
+{
+ for name in $DAEMONS zebra vtysh_enable watchfrr_enable; do
+ # First, assign the value set by the user to $value
+ eval value=\${${name}:0:3}
+
+ # Daemon not activated or entry missing?
+ if [ "$value" = "no" -o "$value" = "" ]; then value=0; fi
+
+ # These strings parsed for backwards compatibility.
+ if [ "$value" = "yes" -o "$value" = "true" ]; then
+ value=1;
+ fi
+
+ # Zebra is threatened special. It must be between 0=off and the first
+ # user assigned value "1" so we increase all other enabled daemons' values.
+ if [ "$name" != "zebra" -a "$value" -gt 0 ]; then value=`expr "$value" + 1`; fi
+
+ # If e.g. name is zebra then we set "zebra=yes".
+ eval $name=$value
+ done
+}
+
+# Starts watchfrr for all wanted daemons.
+start_watchfrr()
+{
+ local daemon_name
+ local daemon_prio
+ local found_one
+ local daemon_inst
+
+ # Start the monitor daemon only if desired.
+ if [ 0 -eq "$watchfrr_enable" ]; then
+ return
+ fi
+
+ # Check variable type
+ if declare -p watchfrr_options | grep -q '^declare \-a'; then
+ # old array support
+ watchfrr_options="${watchfrr_options[@]}"
+ fi
+
+ # Which daemons have been started?
+ found_one=0
+ for daemon_name in $DAEMONS; do
+ eval daemon_prio=\$$daemon_name
+ if [ "$daemon_prio" -gt 0 ]; then
+ eval "daemon_inst=\${${daemon_name}_instances//,/ }"
+ if [ -n "$daemon_inst" ]; then
+ for inst in ${daemon_inst}; do
+ eval "inst_disable=\${${daemon_name}_${inst}}"
+ if [ -z ${inst_disable} ] || [ ${inst_disable} != 0 ]; then
+ if check_daemon $daemon_name $inst; then
+ watchfrr_options="$watchfrr_options ${daemon_name}-${inst}"
+ fi
+ fi
+ done
+ else
+ if check_daemon $daemon_name; then
+ watchfrr_options="$watchfrr_options $daemon_name"
+ fi
+ fi
+ found_one=1
+ fi
+ done
+
+ # Start if at least one daemon is activated.
+ if [ $found_one -eq 1 ]; then
+ start watchfrr
+ echo "."
+ fi
+}
+
+# Stopps watchfrr.
+stop_watchfrr()
+{
+ echo -n "Stopping Frr monitor daemon:"
+ stop watchfrr
+ echo "."
+}
+
+# Stops all daemons that have a lower level of priority than the given.
+# (technically if daemon_prio >= wanted_prio)
+stop_prio()
+{
+ local wanted_prio
+ local daemon_prio
+ local daemon_list
+ local daemon_inst
+ local inst
+
+ if [ -n "$2" ] && [[ "$2" =~ (.*)-(.*) ]]; then
+ daemon=${BASH_REMATCH[1]}
+ inst=${BASH_REMATCH[2]}
+ else
+ daemon="$2"
+ fi
+
+ wanted_prio=$1
+ daemon_list=${daemon:-$DAEMONS}
+
+ echo -n "Stopping Frr daemons (prio:$wanted_prio):"
+
+ for prio_i in `seq 10 -1 $wanted_prio`; do
+ for daemon_name in $daemon_list; do
+ eval daemon_prio=\${${daemon_name}:0:3}
+ daemon_inst=""
+ if [ $daemon_prio -eq $prio_i ]; then
+ eval "daemon_inst=\${${daemon_name}_instances//,/ }"
+ if [ -n "$daemon_inst" ]; then
+ for i in ${daemon_inst}; do
+ if [ -n "$inst" ] && [ "$i" == "$inst" ]; then
+ stop "$daemon_name" "$inst"
+ elif [ x"$inst" == x ]; then
+ stop "$daemon_name" "$i"
+ fi
+ done
+ else
+ stop "$daemon_name"
+ fi
+ fi
+ done
+ done
+
+ echo "."
+ if [ -z "$inst" ]; then
+ # Now stop other daemons that're prowling, coz the daemons file changed
+ echo -n "Stopping other frr daemons"
+ if [ -n "$daemon" ]; then
+ eval "file_list_suffix="$V_PATH"/"$daemon*""
+ else
+ eval "file_list_suffix="$V_PATH/*""
+ fi
+ for pidfile in $file_list_suffix.pid; do
+ PID=`cat $pidfile 2>/dev/null`
+ ${SSD} --stop --quiet --oknodo --pidfile "$pidfile"
+ echo -n "."
+ rm -rf "$pidfile"
+ done
+ echo "."
+
+ echo -n "Removing remaining .vty files"
+ for vtyfile in $file_list_suffix.vty; do
+ rm -rf "$vtyfile"
+ done
+ echo "."
+ fi
+}
+
+# Starts all daemons that have a higher level of priority than the given.
+# (technically if daemon_prio <= wanted_prio)
+start_prio()
+{
+ local wanted_prio
+ local daemon_prio
+ local daemon_list
+ local daemon_name
+ local daemon_inst
+ local inst
+
+ if [ -n "$2" ] && [[ "$2" =~ (.*)-(.*) ]]; then
+ daemon=${BASH_REMATCH[1]}
+ inst=${BASH_REMATCH[2]}
+ else
+ daemon="$2"
+ fi
+
+ wanted_prio=$1
+ daemon_list=${daemon:-$DAEMONS}
+
+ for prio_i in `seq 1 $wanted_prio`; do
+ for daemon_name in $daemon_list; do
+ eval daemon_prio=\$${daemon_name}
+ daemon_inst=""
+ if [ $daemon_prio -eq $prio_i ]; then
+ eval "daemon_inst=\${${daemon_name}_instances//,/ }"
+ if [ -n "$daemon_inst" ]; then
+ if [ `echo "$daemon_inst" | wc -w` -gt ${MAX_INSTANCES} ]; then
+ echo "Max instances supported is ${MAX_INSTANCES}. Aborting"
+ exit 1
+ fi
+ # Check if we're starting again by switching from single instance
+ # to MI version
+ if started "$daemon_name"; then
+ PIDFILE=`pidfile $daemon_name`
+ ${SSD} \
+ --stop --quiet --oknodo \
+ --pidfile "$PIDFILE" \
+ --exec "$D_PATH/$daemon_name"
+
+ rm -f `pidfile $1`
+ rm -f `vtyfile $1`
+ fi
+
+ for i in ${daemon_inst}; do
+ if [ -n "$inst" ] && [ "$i" == "$inst" ]; then
+ start "$daemon_name" "$inst"
+ elif [ x"$inst" == x ]; then
+ start "$daemon_name" "$i"
+ fi
+ done
+ else
+ # Check if we're starting again by switching from
+ # single instance to MI version
+ eval "file_list_suffix="$V_PATH"/"$daemon_name-*""
+ for pidfile in $file_list_suffix.pid; do
+ ${SSD} --stop --quiet --oknodo --pidfile "$pidfile"
+ rm -rf "$pidfile"
+ done
+ for vtyfile in $file_list_suffix.vty; do
+ rm -rf "$vtyfile"
+ done
+
+ start "$daemon_name"
+ fi
+ fi
+ done
+ done
+}
+
+check_status()
+{
+ local daemon_name
+ local daemon_prio
+ local daemon_inst
+ local failed_status=0
+
+ if [ -n "$1" ] && [[ "$1" =~ (.*)-(.*) ]]; then
+ daemon=${BASH_REMATCH[1]}
+ inst=${BASH_REMATCH[2]}
+ else
+ daemon="$1"
+ fi
+
+ daemon_list=${daemon:-$DAEMONS}
+
+ # Which daemons have been started?
+ for daemon_name in $daemon_list; do
+ eval daemon_prio=\$$daemon_name
+ if [ "$daemon_prio" -gt 0 ]; then
+ eval "daemon_inst=\${${daemon_name}_instances//,/ }"
+ if [ -n "$daemon_inst" ]; then
+ for i in ${daemon_inst}; do
+ if [ -n "$inst" -a "$inst" = "$i" ]; then
+ started "$1" "log" || failed_status=$?
+ elif [ -z "$inst" ]; then
+ started "$daemon_name-$i" "log" || failed_status=$?
+ fi
+ done
+ else
+ started "$daemon_name" "log" || failed_status=$?
+ fi
+ fi
+ done
+
+ # All daemons that need to have been started are up and running
+ return $failed_status
+}
+
+#########################################################
+# Main program #
+#########################################################
+
+# Config broken but script must exit silently.
+[ ! -r "$C_PATH/daemons" ] && exit 0
+
+# Load configuration
+. "$C_PATH/daemons"
+if [ -e "$C_PATH/daemons.conf" ]; then
+ . "$C_PATH/daemons.conf"
+fi
+
+# Read configuration variable file if it is present
+[ -r /etc/default/frr ] && . /etc/default/frr
+
+if test -z "$frr_profile"; then
+ # try to autodetect config profile
+ if test -d /etc/cumulus; then
+ frr_profile=datacenter
+ # elif test ...; then
+ # -- add your distro/system here
+ elif test -n "$FRR_DEFAULT_PROFILE"; then
+ frr_profile="$FRR_DEFAULT_PROFILE"
+ fi
+fi
+test -n "$frr_profile" && frr_global_options="$frr_global_options -F $frr_profile"
+
+MAX_INSTANCES=${MAX_INSTANCES:=5}
+
+# Set priority of un-startable daemons to 'no' and substitute 'yes' to '0'
+convert_daemon_prios
+
+if [ ! -d $V_PATH ]; then
+ echo "Creating $V_PATH"
+ install -g "$FRR_GROUP" -o "$FRR_USER" -m "$FRR_CONFIG_MODE" -d "$V_PATH"
+ chmod gu+x "${V_PATH}"
+fi
+
+if [ -n "$3" ] && [ "$3" != "all" ]; then
+ dmn="$2"-"$3"
+elif [ -n "$2" ] && [ "$2" != "all" ]; then
+ dmn="$2"
+fi
+
+case "$1" in
+ start)
+ # Try to load this necessary (at least for 2.6) module.
+ if [ -d /lib/modules/`uname -r` ] ; then
+ echo "Loading capability module if not yet done."
+ set +e; LC_ALL=C modprobe -a capability 2>&1 | egrep -v "(not found|Can't locate)"; set -e
+ fi
+
+ # Start all daemons
+ cd $C_PATH/
+ if [ "$2" != "watchfrr" ]; then
+ start_prio 10 $dmn
+ fi
+ start_watchfrr
+ vtysh_b
+ ;;
+
+ 1|2|3|4|5|6|7|8|9|10)
+ # Stop/start daemons for the appropriate priority level
+ stop_prio $1
+ start_prio $1
+ vtysh_b
+ ;;
+
+ stop|0)
+ # Stop all daemons at level '0' or 'stop'
+ stop_watchfrr
+ if [ "$dmn" != "watchfrr" ]; then
+ [ -n "${dmn}" ] && eval "${dmn/-/_}=0"
+ stop_prio 0 $dmn
+ fi
+
+ if [ -n "$dmn" -a "$dmn" != "zebra" ]; then
+ [ -n "$dmn" ] && eval "${dmn/-/_}=0"
+ start_watchfrr
+ fi
+ ;;
+
+ reload)
+ # Just apply the commands that have changed, no restart necessary
+ if [ ! -x "$RELOAD_SCRIPT" ]; then
+ echo "Please install frr-pythontools package. Required for reload"
+ exit 0
+ fi
+
+ NEW_CONFIG_FILE="${2:-$C_PATH/frr.conf}"
+ [ ! -r $NEW_CONFIG_FILE ] && echo "Unable to read new configuration file $NEW_CONFIG_FILE" && exit 1
+ echo "Applying only incremental changes to running configuration from frr.conf"
+ "$RELOAD_SCRIPT" --reload --bindir "$B_PATH" --confdir "$C_PATH" --rundir "$V_PATH" "$C_PATH/frr.conf"
+ exit $?
+ ;;
+
+ status)
+ check_status $dmn
+ exit $?
+ ;;
+
+ restart|force-reload)
+ $0 stop $dmn
+ sleep 1
+ $0 start $dmn
+ ;;
+
+ *)
+ echo "Usage: /etc/init.d/frr {start|stop|status|reload|restart|force-reload|<priority>} [daemon]"
+ echo " E.g. '/etc/init.d/frr 5' would start all daemons with a prio 1-5."
+ echo " reload applies only modifications from the running config to all daemons."
+ echo " reload neither restarts starts any daemon nor starts any new ones."
+ echo " Read /usr/share/doc/frr/README.Debian for details."
+ exit 1
+ ;;
+esac
+
+echo "Exiting from the script"
+exit 0
diff --git a/tools/frr.service.in b/tools/frr.service.in
new file mode 100644
index 0000000..1e958dd
--- /dev/null
+++ b/tools/frr.service.in
@@ -0,0 +1,26 @@
+[Unit]
+Description=FRRouting
+Documentation=https://frrouting.readthedocs.io/en/latest/setup.html
+Wants=network.target
+After=network-pre.target systemd-sysctl.service
+Before=network.target
+OnFailure=heartbeat-failed@%n
+
+[Service]
+Nice=-5
+Type=forking
+NotifyAccess=all
+StartLimitInterval=3m
+StartLimitBurst=3
+TimeoutSec=@TIMEOUT_MIN@m
+WatchdogSec=60s
+RestartSec=5
+Restart=always
+LimitNOFILE=1024
+PIDFile=@CFG_STATE@/watchfrr.pid
+ExecStart=@CFG_SBIN@/frrinit.sh start
+ExecStop=@CFG_SBIN@/frrinit.sh stop
+ExecReload=@CFG_SBIN@/frrinit.sh reload
+
+[Install]
+WantedBy=multi-user.target
diff --git a/tools/frr.vim b/tools/frr.vim
new file mode 100644
index 0000000..19ac3fc
--- /dev/null
+++ b/tools/frr.vim
@@ -0,0 +1,78 @@
+" SPDX-License-Identifier: NONE
+" settings & syntax hilighting for FRR codebase
+" 2019 by David Lamparter, placed in public domain
+
+let c_gnu=1
+
+function! CStyleFRR()
+ syn clear cFormat
+ syn match cFormat display "%\(\d\+\$\)\=[-+' #0*]*\(\d*\|\*\|\*\d\+\$\)\(\.\(\d*\|\*\|\*\d\+\$\)\)\=\([hlLjzt]\|ll\|hh\)\=\([aAbiuoxXDOUfFeEgGcCsSn]\|[pd]\([A-Z][A-Z0-9]*[a-z]*\|\)\|\[\^\=.[^]]*\]\)" contained
+ syn match cFormat display "%%" contained
+
+ syn keyword cIterator frr_each frr_each_safe frr_each_from
+ syn keyword cMacroOp offsetof container_of container_of_null array_size
+
+ syn keyword cStorageClass atomic
+ syn keyword cFormatConst PRId64 PRIu64 PRIx64
+ syn keyword cFormatConst PRId32 PRIu32 PRIx32
+ syn keyword cFormatConst PRId16 PRIu16 PRIx16
+ syn keyword cFormatConst PRId8 PRIu8 PRIx8
+
+ " you can unlink these by just giving them their own hilighting / color
+ hi link cFormatConst cFormat
+ hi link cIterator cRepeat
+ hi link cMacroOp cOperator
+
+ " indentation
+ setlocal cindent
+ setlocal cinoptions=:0,(0,u4,w1,W8
+ setlocal shiftwidth=8
+ setlocal softtabstop=0
+ setlocal textwidth=0
+ setlocal fo=croql
+ setlocal noet
+endfunction
+
+" auto-apply the above based on path rules
+"autocmd BufRead,BufNewFile /home/.../frr/*.[ch] call CStyleFRR()
+
+" only load xref file once, remember on script-scope
+let s:xrefjson = ""
+let s:xrefpath = ""
+
+" call directly to force reload with :call FRRLoadXrefJson()
+function! FRRLoadXrefJson() abort
+ let s:xrefpath = findfile("frr.xref", ".;")
+ if empty(s:xrefpath)
+ throw "frr.xref JSON file not found in current or parent directories"
+ endif
+ let xreflines = readfile(s:xrefpath)
+ let s:xrefjson = json_decode(join(xreflines, "\n"))
+endfunction
+
+function! FRRXrefJson() abort
+ if empty(s:xrefjson)
+ call FRRLoadXrefJson()
+ endif
+ return s:xrefjson
+endfunction
+
+function! FRRGotoXref(ident) abort
+ let refs = FRRXrefJson()["refs"]
+ if has_key(refs, a:ident)
+ " TODO: in rare cases, one ID may occur in multiple places.
+ " Add some UI for that. (This happens if the exact same
+ " format string is logged in multiple places in the same
+ " file.)
+ let loc = refs[a:ident][0]
+ let basepath = fnamemodify(s:xrefpath, ":p:h")
+ let path = fnamemodify(basepath . "/" . loc["file"], ":.")
+ execute "e ".fnameescape(path)
+ execute ":".loc["line"]
+ else
+ echoerr printf("cannot find xref with ID %s", a:ident)
+ endif
+endfunction
+
+" invoke as :GotoXref 23456-ABCDE
+command! -bang -nargs=1 GotoXref :call FRRGotoXref(<q-args>)
diff --git a/tools/frr@.service.in b/tools/frr@.service.in
new file mode 100644
index 0000000..85408a0
--- /dev/null
+++ b/tools/frr@.service.in
@@ -0,0 +1,26 @@
+[Unit]
+Description=FRRouting
+Documentation=https://frrouting.readthedocs.io/en/latest/setup.html
+Wants=network.target
+After=network-pre.target systemd-sysctl.service
+Before=network.target
+OnFailure=heartbeat-failed@%n
+
+[Service]
+Nice=-5
+Type=forking
+NotifyAccess=all
+StartLimitInterval=3m
+StartLimitBurst=3
+TimeoutSec=@TIMEOUT_MIN@m
+WatchdogSec=60s
+RestartSec=5
+Restart=always
+LimitNOFILE=1024
+PIDFile=@CFG_STATE@/%I/watchfrr.pid
+ExecStart=@CFG_SBIN@/frrinit.sh start %I
+ExecStop=@CFG_SBIN@/frrinit.sh stop %I
+ExecReload=@CFG_SBIN@/frrinit.sh reload %I
+
+[Install]
+WantedBy=multi-user.target
diff --git a/tools/frr_babeltrace.py b/tools/frr_babeltrace.py
new file mode 100755
index 0000000..4d974ad
--- /dev/null
+++ b/tools/frr_babeltrace.py
@@ -0,0 +1,253 @@
+#!/usr/bin/env python3
+# SPDX-License-Identifier: GPL-2.0-or-later
+"""
+Usage: frr_babeltrace.py trace_path
+
+FRR pushes data into lttng tracepoints in the least overhead way possible
+i.e. as binary-data/crf_arrays. These traces need to be converted into pretty
+strings for easy greping etc. This script is a babeltrace python plugin for
+that pretty printing.
+
+Copyright (C) 2021 NVIDIA Corporation
+Anuradha Karuppiah
+"""
+
+import ipaddress
+import socket
+import sys
+
+import babeltrace
+
+########################### common parsers - start ############################
+def print_ip_addr(field_val):
+ """
+ pretty print "struct ipaddr"
+ """
+ if field_val[0] == socket.AF_INET:
+ addr = [str(fv) for fv in field_val[4:8]]
+ return str(ipaddress.IPv4Address(".".join(addr)))
+
+ if field_val[0] == socket.AF_INET6:
+ tmp = "".join("%02x" % fb for fb in field_val[4:])
+ addr = []
+ while tmp:
+ addr.append(tmp[:4])
+ tmp = tmp[4:]
+ addr = ":".join(addr)
+ return str(ipaddress.IPv6Address(addr))
+
+ if not field_val[0]:
+ return ""
+
+ return field_val
+
+
+def print_mac(field_val):
+ """
+ pretty print "u8 mac[6]"
+ """
+ return ":".join("%02x" % fb for fb in field_val)
+
+def print_net_ipv4_addr(field_val):
+ """
+ pretty print ctf_integer_network ipv4
+ """
+ return str(ipaddress.IPv4Address(field_val))
+
+def print_esi(field_val):
+ """
+ pretty print ethernet segment id, esi_t
+ """
+ return ":".join("%02x" % fb for fb in field_val)
+
+def get_field_list(event):
+ """
+ only fetch fields added via the TP, skip metadata etc.
+ """
+ return event.field_list_with_scope(babeltrace.CTFScope.EVENT_FIELDS)
+
+def parse_event(event, field_parsers):
+ """
+ Wild card event parser; doesn't make things any prettier
+ """
+ field_list = get_field_list(event)
+ field_info = {}
+ for field in field_list:
+ if field in field_parsers:
+ field_parser = field_parsers.get(field)
+ field_info[field] = field_parser(event.get(field))
+ else:
+ field_info[field] = event.get(field)
+ print(event.name, field_info)
+############################ common parsers - end #############################
+
+############################ evpn parsers - start #############################
+def parse_frr_bgp_evpn_mac_ip_zsend(event):
+ """
+ bgp evpn mac-ip parser; raw format -
+ ctf_array(unsigned char, mac, &pfx->prefix.macip_addr.mac,
+ sizeof(struct ethaddr))
+ ctf_array(unsigned char, ip, &pfx->prefix.macip_addr.ip,
+ sizeof(struct ipaddr))
+ ctf_integer_network_hex(unsigned int, vtep, vtep.s_addr)
+ ctf_array(unsigned char, esi, esi, sizeof(esi_t))
+ """
+ field_parsers = {"ip": print_ip_addr,
+ "mac": print_mac,
+ "esi": print_esi,
+ "vtep": print_net_ipv4_addr}
+
+ parse_event(event, field_parsers)
+
+def parse_frr_bgp_evpn_bum_vtep_zsend(event):
+ """
+ bgp evpn bum-vtep parser; raw format -
+ ctf_integer_network_hex(unsigned int, vtep,
+ pfx->prefix.imet_addr.ip.ipaddr_v4.s_addr)
+
+ """
+ field_parsers = {"vtep": print_net_ipv4_addr}
+
+ parse_event(event, field_parsers)
+
+def parse_frr_bgp_evpn_mh_nh_rmac_send(event):
+ """
+ bgp evpn nh-rmac parser; raw format -
+ ctf_array(unsigned char, rmac, &nh->rmac, sizeof(struct ethaddr))
+ """
+ field_parsers = {"rmac": print_mac}
+
+ parse_event(event, field_parsers)
+
+def parse_frr_bgp_evpn_mh_local_es_add_zrecv(event):
+ """
+ bgp evpn local-es parser; raw format -
+ ctf_array(unsigned char, esi, esi, sizeof(esi_t))
+ ctf_integer_network_hex(unsigned int, vtep, vtep.s_addr)
+ """
+ field_parsers = {"esi": print_esi,
+ "vtep": print_net_ipv4_addr}
+
+ parse_event(event, field_parsers)
+
+def parse_frr_bgp_evpn_mh_local_es_del_zrecv(event):
+ """
+ bgp evpn local-es parser; raw format -
+ ctf_array(unsigned char, esi, esi, sizeof(esi_t))
+ """
+ field_parsers = {"esi": print_esi}
+
+ parse_event(event, field_parsers)
+
+def parse_frr_bgp_evpn_mh_local_es_evi_add_zrecv(event):
+ """
+ bgp evpn local-es-evi parser; raw format -
+ ctf_array(unsigned char, esi, esi, sizeof(esi_t))
+ """
+ field_parsers = {"esi": print_esi}
+
+ parse_event(event, field_parsers)
+
+def parse_frr_bgp_evpn_mh_local_es_evi_del_zrecv(event):
+ """
+ bgp evpn local-es-evi parser; raw format -
+ ctf_array(unsigned char, esi, esi, sizeof(esi_t))
+ """
+ field_parsers = {"esi": print_esi}
+
+ parse_event(event, field_parsers)
+
+def parse_frr_bgp_evpn_local_vni_add_zrecv(event):
+ """
+ bgp evpn local-vni parser; raw format -
+ ctf_integer_network_hex(unsigned int, vtep, vtep.s_addr)
+ ctf_integer_network_hex(unsigned int, mc_grp, mc_grp.s_addr)
+ """
+ field_parsers = {"vtep": print_net_ipv4_addr,
+ "mc_grp": print_net_ipv4_addr}
+
+ parse_event(event, field_parsers)
+
+def parse_frr_bgp_evpn_local_l3vni_add_zrecv(event):
+ """
+ bgp evpn local-l3vni parser; raw format -
+ ctf_integer_network_hex(unsigned int, vtep, vtep.s_addr)
+ ctf_array(unsigned char, svi_rmac, svi_rmac, sizeof(struct ethaddr))
+ ctf_array(unsigned char, vrr_rmac, vrr_rmac, sizeof(struct ethaddr))
+ """
+ field_parsers = {"vtep": print_net_ipv4_addr,
+ "svi_rmac": print_mac,
+ "vrr_rmac": print_mac}
+
+ parse_event(event, field_parsers)
+
+def parse_frr_bgp_evpn_local_macip_add_zrecv(event):
+ """
+ bgp evpn local-mac-ip parser; raw format -
+ ctf_array(unsigned char, ip, ip, sizeof(struct ipaddr))
+ ctf_array(unsigned char, mac, mac, sizeof(struct ethaddr))
+ ctf_array(unsigned char, esi, esi, sizeof(esi_t))
+ """
+ field_parsers = {"ip": print_ip_addr,
+ "mac": print_mac,
+ "esi": print_esi}
+
+ parse_event(event, field_parsers)
+
+def parse_frr_bgp_evpn_local_macip_del_zrecv(event):
+ """
+ bgp evpn local-mac-ip del parser; raw format -
+ ctf_array(unsigned char, ip, ip, sizeof(struct ipaddr))
+ ctf_array(unsigned char, mac, mac, sizeof(struct ethaddr))
+ """
+ field_parsers = {"ip": print_ip_addr,
+ "mac": print_mac}
+
+ parse_event(event, field_parsers)
+
+############################ evpn parsers - end *#############################
+
+def main():
+ """
+ FRR lttng trace output parser; babel trace plugin
+ """
+ event_parsers = {"frr_bgp:evpn_mac_ip_zsend":
+ parse_frr_bgp_evpn_mac_ip_zsend,
+ "frr_bgp:evpn_bum_vtep_zsend":
+ parse_frr_bgp_evpn_bum_vtep_zsend,
+ "frr_bgp:evpn_mh_nh_rmac_zsend":
+ parse_frr_bgp_evpn_mh_nh_rmac_send,
+ "frr_bgp:evpn_mh_local_es_add_zrecv":
+ parse_frr_bgp_evpn_mh_local_es_add_zrecv,
+ "frr_bgp:evpn_mh_local_es_del_zrecv":
+ parse_frr_bgp_evpn_mh_local_es_del_zrecv,
+ "frr_bgp:evpn_mh_local_es_evi_add_zrecv":
+ parse_frr_bgp_evpn_mh_local_es_evi_add_zrecv,
+ "frr_bgp:evpn_mh_local_es_evi_del_zrecv":
+ parse_frr_bgp_evpn_mh_local_es_evi_del_zrecv,
+ "frr_bgp:evpn_local_vni_add_zrecv":
+ parse_frr_bgp_evpn_local_vni_add_zrecv,
+ "frr_bgp:evpn_local_l3vni_add_zrecv":
+ parse_frr_bgp_evpn_local_l3vni_add_zrecv,
+ "frr_bgp:evpn_local_macip_add_zrecv":
+ parse_frr_bgp_evpn_local_macip_add_zrecv,
+ "frr_bgp:evpn_local_macip_del_zrecv":
+ parse_frr_bgp_evpn_local_macip_del_zrecv,
+}
+
+ # get the trace path from the first command line argument
+ trace_path = sys.argv[1]
+
+ # grab events
+ trace_collection = babeltrace.TraceCollection()
+ trace_collection.add_traces_recursive(trace_path, "ctf")
+
+ for event in trace_collection.events:
+ if event.name in event_parsers:
+ event_parser = event_parsers.get(event.name)
+ event_parser(event)
+ else:
+ parse_event(event, {})
+
+if __name__ == "__main__":
+ main()
diff --git a/tools/frrcommon.sh.in b/tools/frrcommon.sh.in
new file mode 100755
index 0000000..00b63a7
--- /dev/null
+++ b/tools/frrcommon.sh.in
@@ -0,0 +1,385 @@
+#!/bin/bash
+#
+# This is a "library" of sorts for use by the other FRR shell scripts. It
+# has most of the daemon start/stop logic, but expects the following shell
+# functions/commands to be provided by the "calling" script:
+#
+# log_success_msg
+# log_warning_msg
+# log_failure_msg
+#
+# (coincidentally, these are LSB standard functions.)
+#
+# Sourcing this file in a shell script will load FRR config variables but
+# not perform any action. Note there is an "exit 1" if the main config
+# file does not exist.
+#
+# This script should be installed in @CFG_SBIN@/frrcommon.sh
+
+# FRR_PATHSPACE is passed in from watchfrr
+suffix="${FRR_PATHSPACE:+/${FRR_PATHSPACE}}"
+nsopt="${FRR_PATHSPACE:+-N ${FRR_PATHSPACE}}"
+
+PATH=/bin:/usr/bin:/sbin:/usr/sbin
+D_PATH="@CFG_SBIN@" # /usr/lib/frr
+C_PATH="@CFG_SYSCONF@${suffix}" # /etc/frr
+V_PATH="@CFG_STATE@${suffix}" # /var/run/frr
+B_PATH="@CFG_BIN@"
+VTYSH="@vtysh_bin@" # /usr/bin/vtysh
+FRR_USER="@enable_user@" # frr
+FRR_GROUP="@enable_group@" # frr
+FRR_VTY_GROUP="@enable_vty_group@" # frrvty
+FRR_CONFIG_MODE="@enable_configfile_mask@" # 0600
+FRR_DEFAULT_PROFILE="@DFLT_NAME@" # traditional / datacenter
+
+# ORDER MATTERS FOR $DAEMONS!
+# - keep zebra first
+# - watchfrr does NOT belong in this list
+
+DAEMONS="zebra mgmtd bgpd ripd ripngd ospfd ospf6d isisd babeld pimd pim6d ldpd nhrpd eigrpd sharpd pbrd staticd bfdd fabricd vrrpd pathd"
+RELOAD_SCRIPT="$D_PATH/frr-reload.py"
+
+#
+# general helpers
+#
+
+is_user_root () {
+ if [[ ! -z $FRR_NO_ROOT && "${FRR_NO_ROOT}" == "yes" ]]; then
+ return 0
+ fi
+
+ [ "${EUID:-$(id -u)}" -eq 0 ] || {
+ log_failure_msg "Only users having EUID=0 can start/stop daemons"
+ return 1
+ }
+}
+
+debug() {
+ [ -n "$watchfrr_debug" ] || return 0
+
+ printf '%s %s(%s):' "$(date +%Y-%m-%dT%H:%M:%S.%N)" "$0" $$ >&2
+ # this is to show how arguments are split regarding whitespace & co.
+ # (e.g. for use with `debug "message" "$@"`)
+ while [ $# -gt 0 ]; do
+ printf ' "%s"' "$1" >&2
+ shift
+ done
+ printf '\n' >&2
+}
+
+vtysh_b () {
+ [ "$1" = "watchfrr" ] && return 0
+ if [ ! -r "$C_PATH/frr.conf" ]; then
+ log_warning_msg "$C_PATH/frr.conf does not exist; skipping config apply"
+ return 0
+ fi
+
+ cmd="$VTYSH $nsopt -b"
+ [ -n "$1" ] && cmd="${cmd} -d $1"
+
+ log_success_msg "Sending config with '$cmd'"
+ eval "$cmd"
+}
+
+daemon_inst() {
+ # note this sets global variables ($dmninst, $daemon, $inst)
+ dmninst="$1"
+ daemon="${dmninst%-*}"
+ inst=""
+ [ "$daemon" != "$dmninst" ] && inst="${dmninst#*-}"
+}
+
+daemon_list() {
+ # note $1 and $2 specify names for global variables to be set
+ local enabled disabled evar dvar
+ enabled=""
+ disabled=""
+ evar="$1"
+ dvar="$2"
+
+ for daemon in $DAEMONS; do
+ eval cfg=\$$daemon
+ eval inst=\$${daemon}_instances
+ [ "$daemon" = zebra -o "$daemon" = staticd -o "$daemon" = mgmtd ] && cfg=yes
+ if [ -n "$cfg" -a "$cfg" != "no" -a "$cfg" != "0" ]; then
+ if ! daemon_prep "$daemon" "$inst"; then
+ continue
+ fi
+ debug "$daemon enabled"
+
+ if [ -n "$inst" ]; then
+ debug "$daemon multi-instance $inst"
+ oldifs="${IFS}"
+ IFS="${IFS},"
+ for i in $inst; do
+ enabled="$enabled $daemon-$i"
+ done
+ IFS="${oldifs}"
+ else
+ enabled="$enabled $daemon"
+ fi
+ else
+ debug "$daemon disabled"
+ disabled="$disabled $daemon"
+ fi
+ done
+
+ enabled="${enabled# }"
+ disabled="${disabled# }"
+ [ -z "$evar" ] && echo "$enabled"
+ [ -n "$evar" ] && eval $evar="\"$enabled\""
+ [ -n "$dvar" ] && eval $dvar="\"$disabled\""
+}
+
+#
+# individual daemon management
+#
+
+daemon_prep() {
+ local daemon inst cfg
+ daemon="$1"
+ inst="$2"
+ [ "$daemon" = "watchfrr" ] && return 0
+ [ -x "$D_PATH/$daemon" ] || {
+ log_failure_msg "cannot start $daemon${inst:+ (instance $inst)}: daemon binary not installed"
+ return 1
+ }
+ [ -r "$C_PATH/frr.conf" ] && return 0
+
+ cfg="$C_PATH/$daemon${inst:+-$inst}.conf"
+ if [ ! -r "$cfg" ]; then
+ install -g "$FRR_GROUP" -o "$FRR_USER" -m "$FRR_CONFIG_MODE" /dev/null "$cfg"
+ fi
+ return 0
+}
+
+daemon_start() {
+ local dmninst daemon inst args instopt wrap bin
+
+ is_user_root || exit 1
+
+ all=false
+ [ "$1" = "--all" ] && { all=true; shift; }
+
+ daemon_inst "$1"
+
+ [ "$MAX_FDS" != "" ] && ulimit -n "$MAX_FDS" > /dev/null 2> /dev/null
+ daemon_prep "$daemon" "$inst" || return 1
+ if test ! -d "$V_PATH"; then
+ install -g "$FRR_GROUP" -o "$FRR_USER" -m "$FRR_CONFIG_MODE" -d "$V_PATH"
+ chmod gu+x "${V_PATH}"
+ fi
+
+ eval wrap="\$${daemon}_wrap"
+ bin="$D_PATH/$daemon"
+ instopt="${inst:+-n $inst}"
+ eval args="\$${daemon}_options"
+
+ cmd="$all_wrap $wrap $bin $nsopt -d $frr_global_options $instopt $args"
+ log_success_msg "Starting $daemon with command: '$cmd'"
+ if eval "$cmd"; then
+ log_success_msg "Started $dmninst"
+ if $all; then
+ debug "Skipping startup of vtysh until all have started"
+ else
+ vtysh_b "$daemon"
+ fi
+ else
+ log_failure_msg "Failed to start $dmninst!"
+ fi
+}
+
+daemon_stop() {
+ local dmninst daemon inst pidfile vtyfile pid cnt fail
+ daemon_inst "$1"
+
+ is_user_root || exit 1
+
+ all=false
+ [ "$2" = "--reallyall" ] && all=true
+
+ pidfile="$V_PATH/$daemon${inst:+-$inst}.pid"
+ vtyfile="$V_PATH/$daemon${inst:+-$inst}.vty"
+
+ [ -r "$pidfile" ] || fail="pid file not found"
+ $all && [ -n "$fail" ] && return 0
+ [ -z "$fail" ] && pid="$(cat "$pidfile")"
+ [ -z "$fail" -a -z "$pid" ] && fail="pid file is empty"
+ [ -n "$fail" ] || kill -0 "$pid" 2>/dev/null || fail="pid $pid not running"
+
+ if [ -n "$fail" ]; then
+ [ "$2" = "--quiet" ] || log_failure_msg "Cannot stop $dmninst: $fail"
+ return 1
+ fi
+
+ debug "kill -2 $pid"
+ kill -2 "$pid"
+ cnt=1200
+ while kill -0 "$pid" 2>/dev/null; do
+ sleep .1
+ [ $(( cnt -= 1 )) -gt 0 ] || break
+ done
+ if kill -0 "$pid" 2>/dev/null; then
+ [ "$2" = "--quiet" ] || log_failure_msg "Failed to stop $dmninst, pid $pid still running"
+ still_running=1
+ return 1
+ else
+ [ "$2" = "--quiet" ] || log_success_msg "Stopped $dmninst"
+ rm -f "$pidfile"
+ return 0
+ fi
+}
+
+daemon_status() {
+ local dmninst daemon inst pidfile pid fail
+ daemon_inst "$1"
+
+ pidfile="$V_PATH/$daemon${inst:+-$inst}.pid"
+
+ [ -r "$pidfile" ] || return 3
+ pid="$(cat "$pidfile")"
+ [ -z "$pid" ] && return 1
+ kill -0 "$pid" 2>/dev/null || return 1
+ return 0
+}
+
+print_status() {
+ daemon_status "$1"
+ rv=$?
+ if [ "$rv" -eq 0 ]; then
+ log_success_msg "Status of $1: running"
+ else
+ log_failure_msg "Status of $1: FAILED"
+ fi
+ return $rv
+}
+
+#
+# all-daemon commands
+#
+
+all_start() {
+ daemon_list daemons
+ for dmninst in $daemons; do
+ daemon_start --all "$dmninst"
+ done
+ vtysh_b
+}
+
+all_stop() {
+ local pids reversed
+
+ daemon_list enabled_daemons disabled_daemons
+ [ "$1" = "--reallyall" ] && enabled_daemons="$enabled_daemons $disabled_daemons"
+
+ reversed=""
+ for dmninst in $enabled_daemons; do
+ reversed="$dmninst $reversed"
+ done
+
+ for dmninst in $reversed; do
+ daemon_stop "$dmninst" "$1" &
+ pids="$pids $!"
+ done
+ for pid in $pids; do
+ wait $pid
+ done
+}
+
+all_status() {
+ local fail
+
+ daemon_list daemons
+ fail=0
+ for dmninst in $daemons; do
+ print_status "$dmninst" || fail=1
+ done
+ return $fail
+}
+
+#
+# config sourcing
+#
+
+load_old_config() {
+ oldcfg="$1"
+ [ -r "$oldcfg" ] || return 0
+ [ -s "$oldcfg" ] || return 0
+ grep -v '^[[:blank:]]*\(#\|$\)' "$oldcfg" > /dev/null || return 0
+
+ log_warning_msg "Reading deprecated $oldcfg. Please move its settings to $C_PATH/daemons and remove it."
+
+ # save off settings from daemons for the OR below
+ for dmn in $DAEMONS; do eval "_new_$dmn=\${$dmn:-no}"; done
+
+ . "$oldcfg"
+
+ # OR together the daemon enabling options between config files
+ for dmn in $DAEMONS; do eval "test \$_new_$dmn != no && $dmn=\$_new_$dmn; unset _new_$dmn"; done
+}
+
+[ -r "$C_PATH/daemons" ] || {
+ log_failure_msg "cannot run $@: $C_PATH/daemons does not exist"
+ exit 1
+}
+. "$C_PATH/daemons"
+
+if [ -z "$FRR_PATHSPACE" ]; then
+ load_old_config "$C_PATH/daemons.conf"
+ load_old_config "/etc/default/frr"
+ load_old_config "/etc/sysconfig/frr"
+fi
+
+if { declare -p watchfrr_options 2>/dev/null || true; } | grep -q '^declare -a'; then
+ log_warning_msg "watchfrr_options contains a bash array value." \
+ "The configured value is intentionally ignored since it is likely wrong." \
+ "Please remove or fix the setting."
+ unset watchfrr_options
+fi
+
+if test -z "$frr_profile"; then
+ # try to autodetect config profile
+ if test -d /etc/cumulus; then
+ frr_profile=datacenter
+ # elif test ...; then
+ # -- add your distro/system here
+ elif test -n "$FRR_DEFAULT_PROFILE"; then
+ frr_profile="$FRR_DEFAULT_PROFILE"
+ fi
+fi
+test -n "$frr_profile" && frr_global_options="$frr_global_options -F $frr_profile"
+
+#
+# other defaults and dispatch
+#
+
+frrcommon_main() {
+ local cmd
+
+ debug "frrcommon_main" "$@"
+
+ cmd="$1"
+ shift
+
+ if [ "$1" = "all" ] || [ -z "$1" ]; then
+ case "$cmd" in
+ start) all_start;;
+ stop) all_stop;;
+ restart)
+ all_stop --quiet
+ all_start
+ ;;
+ *) $cmd "$@";;
+ esac
+ else
+ case "$cmd" in
+ start) daemon_start "$@";;
+ stop) daemon_stop "$@";;
+ restart)
+ daemon_stop "$@"
+ daemon_start "$@"
+ ;;
+ *) $cmd "$@";;
+ esac
+ fi
+}
diff --git a/tools/frrinit.sh.in b/tools/frrinit.sh.in
new file mode 100644
index 0000000..428d57c
--- /dev/null
+++ b/tools/frrinit.sh.in
@@ -0,0 +1,135 @@
+#!/bin/bash
+#
+### BEGIN INIT INFO
+# Provides: frr
+# Required-Start: $local_fs $network $remote_fs $syslog
+# Required-Stop: $local_fs $network $remote_fs $syslog
+# Default-Start: 2 3 4 5
+# Default-Stop: 0 1 6
+# Short-Description: start and stop the FRR routing suite
+# Description: FRR is a routing suite for IP routing protocols like
+# BGP, OSPF, RIP and others. This script contols the main
+# "watchfrr" daemon.
+### END INIT INFO
+#
+# This is the main init script for FRR. It mostly wraps frrcommon.sh which
+# provides the actual functions to start/stop/restart things.
+#
+
+if [ -r "/lib/lsb/init-functions" ]; then
+ . /lib/lsb/init-functions
+else
+ log_success_msg() {
+ echo "$@"
+ }
+ log_warning_msg() {
+ echo "$@" >&2
+ }
+ log_failure_msg() {
+ echo "$@" >&2
+ }
+fi
+
+# "/usr/lib/frr/frrinit.sh start somenamespace"
+FRR_PATHSPACE="$2"
+
+self="`dirname $0`"
+if [ -r "$self/frrcommon.sh" ]; then
+ . "$self/frrcommon.sh"
+else
+ . "@CFG_SBIN@/frrcommon.sh"
+fi
+
+case "$1" in
+start)
+ daemon_list daemons
+ watchfrr_options="$watchfrr_options $daemons"
+ daemon_start watchfrr
+ ;;
+stop)
+ daemon_stop watchfrr
+ all_stop --reallyall
+ exit ${still_running:-0}
+ ;;
+
+restart|force-reload)
+ daemon_stop watchfrr
+ all_stop --reallyall
+
+ daemon_list daemons
+ watchfrr_options="$watchfrr_options $daemons"
+ daemon_start watchfrr
+ ;;
+
+status)
+ fail=0
+ print_status watchfrr || fail=1
+ all_status || fail=1
+ exit $fail
+ ;;
+
+reload)
+ if [ ! -x "$RELOAD_SCRIPT" ]; then
+ log_failure_msg "The frr-pythontools package is required for reload functionality."
+ exit 1
+ fi
+
+ # systemd doesn't set WATCHDOG_USEC for reload commands.
+ watchfrr_pidfile="$V_PATH/watchfrr.pid"
+ watchfrr_pid="`cat \"$watchfrr_pidfile\"`"
+ watchfrr_cmdline="`tr '\0' '\n' < /proc/$watchfrr_pid/cmdline`"
+ if [ -d "/proc/$watchfrr_pid" ]; then
+ wdt="`tr '\0' '\n' < /proc/$watchfrr_pid/environ | grep '^WATCHDOG_USEC='`"
+ wdt="${wdt#WATCHDOG_USEC=}"
+ [ -n "$wdt" ] && : ${WATCHDOG_USEC:=$wdt}
+ [ -n "$WATCHDOG_USEC" ] && export WATCHDOG_USEC
+ fi
+
+ # restart watchfrr to pick up added daemons.
+ # NB: This will NOT cause the other daemons to be restarted.
+ daemon_list enabled_daemons disabled_daemons
+ watchfrr_options="$watchfrr_options $enabled_daemons"
+ daemon_stop watchfrr && \
+ daemon_start watchfrr
+
+ # If we disable an arbitrary daemon and do reload,
+ # disabled daemon is still running and we should stop it.
+ for daemon in $disabled_daemons; do
+ if grep -q "$daemon" <<< "$watchfrr_cmdline"; then
+ daemon_stop "$daemon" &
+ pids="$pids $!"
+ fi
+ done
+
+ for pid in $pids; do
+ wait "$pid"
+ done
+
+ # make systemd not kill watchfrr after ExecReload completes
+ # 3 goats were sacrificed to restore sanity after coding this
+ watchfrr_pid="`cat \"$watchfrr_pidfile\"`"
+ if [ -f "/proc/$watchfrr_pid/cgroup" -a -d "/sys/fs/cgroup/systemd" ]; then
+ cg="`egrep '^[0-9]+:name=systemd:' \"/proc/$watchfrr_pid/cgroup\"`"
+ cg="${cg#*:*:}"
+
+ cgmain="$cg"
+ cgmain="${cgmain%/.control}"
+ cgmain="${cgmain%/control}"
+
+ [ -n "$cg" -a "$cg" != "$cgmain" ] && \
+ echo "$watchfrr_pid" > "/sys/fs/cgroup/systemd/$cgmain/tasks"
+ fi
+
+ NEW_CONFIG_FILE="${2:-$C_PATH/frr.conf}"
+ [ ! -r $NEW_CONFIG_FILE ] && log_failure_msg "Unable to read new configuration file $NEW_CONFIG_FILE" && exit 1
+ "$RELOAD_SCRIPT" --reload --bindir "$B_PATH" --confdir "$C_PATH" --rundir "$V_PATH" "$NEW_CONFIG_FILE" `echo $nsopt`
+ exit 0
+ ;;
+
+*)
+ echo "Usage:"
+ echo " ${0} <start|stop|restart|force-reload|reload|status> [namespace]"
+ echo " ${0} stop namespace1"
+ exit 1
+ ;;
+esac
diff --git a/tools/gcc-plugins/.gitignore b/tools/gcc-plugins/.gitignore
new file mode 100644
index 0000000..dd8d0cb
--- /dev/null
+++ b/tools/gcc-plugins/.gitignore
@@ -0,0 +1,7 @@
+*.so
+*.o
+debian/.debhelper
+debian/files
+debian/*.substvars
+debian/gcc-9-frr-plugin
+!gcc-retain-typeinfo.patch
diff --git a/tools/gcc-plugins/Makefile b/tools/gcc-plugins/Makefile
new file mode 100644
index 0000000..2af28fe
--- /dev/null
+++ b/tools/gcc-plugins/Makefile
@@ -0,0 +1,23 @@
+all: frr-format.so
+
+CXX=g++-9
+
+PLUGBASE=`$(CXX) -print-file-name=plugin`
+CPPFLAGS=-I$(PLUGBASE)/include -I$(PLUGBASE)/include/c-family
+
+# NB: compiler flags must match those used to build gcc, otherwise inlining
+# behavior is different and linker errors will result due to missing symbols
+# (which should in fact be inlined)
+
+frr-format.so: frr-format.o
+ $(CXX) -fno-rtti -fno-exceptions -fasynchronous-unwind-tables -ggdb -shared -o $@ $^
+
+frr-format.o: frr-format.c gcc-common.h
+ $(CXX) -fno-rtti -fno-exceptions -fasynchronous-unwind-tables -ggdb $(CPPFLAGS) -fPIC -Wall -Wextra -Wno-unused-parameter -c -o $@ $<
+
+install:
+ install -d $(DESTDIR)$(PLUGBASE)
+ install frr-format.so $(DESTDIR)$(PLUGBASE)
+
+clean:
+ rm -f frr-format.so frr-format.o
diff --git a/tools/gcc-plugins/README.md b/tools/gcc-plugins/README.md
new file mode 100644
index 0000000..ab31d0e
--- /dev/null
+++ b/tools/gcc-plugins/README.md
@@ -0,0 +1,102 @@
+frr-format GCC plugin
+=====================
+
+Context
+-------
+
+This plugin provides improved type checking for Linux kernel style printf
+extensions (i.e. `%pI4` printing `struct in_addr *` as `1.2.3.4`.)
+
+Other than additional warnings, (non-)usage of this plugin should not affect
+the build outcome. It is perfectly fine to build FRR without this plugin.
+
+
+Binary Debian packages
+----------------------
+
+Can be found at [https://deb.nox.tf/devel/].
+
+
+GCC requirements
+----------------
+
+To use this plugin, you need a **patched 9.3.0** or a **patched 10.1.0**
+version of GCC using the [gcc-retain-typeinfo.patch] provided in this repo.
+
+Without this patch, GCC strips type information too early during compilation,
+leaving to the plugin being unable to perform more meaningful type checks.
+(Specifically, all `typedef` casts will be "cooked down" to their final type.)
+If the patch is missing, `format-test.c` will show 4 false negative/positive
+warnings marked with `(need retain-typeinfo patch)`.
+
+(@eqvinox has discussed this one-line diff with some GCC people on their
+IRC channel around mid 2019, the consensus was that the line is an "early
+optimization" and removing it should not be harmful. However, doing so is
+likely to break GCC's unit tests since warnings would print different types.)
+
+Other versions of gcc are not supported. gcc 8 previously did work but isn't
+actively tested/maintained.
+
+
+Usage
+-----
+
+First, all plugin-specific statements should be wrapped by an ifdef:
+
+```
+#ifdef _FRR_ATTRIBUTE_PRINTFRR
+...
+#endif
+```
+
+`_FRR_ATTRIBUTE_PRINTFRR` will be defined to the plugin's version (currently
+0x10000) whenever the plugin is loaded.
+
+Then, annotate extended printf functions with the `frr_format` attribute.
+This works exactly like the `format` attribute:
+
+```
+int printfn(const char *fmt, ...) __attribute__((frr_format("frr_printf", 1, 2)));
+```
+
+In the FRR codebase, use the `PRINTFRR` macro provided in
+[../../lib/compiler.h].
+
+Lastly, "declare" extensions with `#pragma FRR printfrr_ext`:
+```
+#ifdef _FRR_ATTRIBUTE_PRINTFRR
+#pragma FRR printfrr_ext "%pI4" (struct in_addr *)
+#pragma FRR printfrr_ext "%pI4" (in_addr_t *)
+#endif
+```
+
+Note that you can use multiple such lines if a particular extended printer
+works for more than one type (as seen above.)
+
+The pragma type "parameter" looks like a C cast but unfortunately due to GCC
+not exporting a good interface to proper type parsing, it is "ghetto parsed",
+with only `struct`, `union`, `enum` being properly supported. `const` is
+ignored if it occurs as the first token. (The plugin always accepts `const`
+parameters for printf since printf shouldn't change the passed data it's
+printing.) The last token may be zero or more counts of `*`, note that
+qualifiers on the intermediate pointers (e.g. `const char * const *`) are not
+supported.
+
+
+TODOs and future direction
+--------------------------
+
+* support two-parameter extension printers that use the precision field
+ (e.g. `"%.*pI5" (int af, void *addr)` to print an IP address with the
+ address family in the "precision".
+
+* port to future GCC versions
+
+* get the one-liner patch upstreamed
+
+
+License
+-------
+
+This plugin is **derivative of GCC 9.x**. It was created by copying off
+`c-format.c`. It must therefore adhere to GCC's GPLv3+ license.
diff --git a/tools/gcc-plugins/debian/changelog b/tools/gcc-plugins/debian/changelog
new file mode 100644
index 0000000..a772803
--- /dev/null
+++ b/tools/gcc-plugins/debian/changelog
@@ -0,0 +1,11 @@
+gcc-frr-plugin (9.3.0d15+equi1) unstable; urgency=medium
+
+ * update & rebuild for gcc 9.3.0-15+equi1
+
+ -- David Lamparter <equinox-debian@diac24.net> Tue, 14 Jul 2020 19:49:24 +0200
+
+gcc-frr-plugin (9.3.0d8+equi2) unstable; urgency=medium
+
+ * package created (+equi1 used during development, never released.)
+
+ -- David Lamparter <equinox-debian@diac24.net> Sun, 29 Mar 2020 08:32:24 +0200
diff --git a/tools/gcc-plugins/debian/compat b/tools/gcc-plugins/debian/compat
new file mode 100644
index 0000000..48082f7
--- /dev/null
+++ b/tools/gcc-plugins/debian/compat
@@ -0,0 +1 @@
+12
diff --git a/tools/gcc-plugins/debian/control b/tools/gcc-plugins/debian/control
new file mode 100644
index 0000000..b9b5134
--- /dev/null
+++ b/tools/gcc-plugins/debian/control
@@ -0,0 +1,19 @@
+Source: gcc-frr-plugin
+Section: devel
+Priority: optional
+Maintainer: David Lamparter <equinox-debian@diac24.net>
+Build-Depends:
+ gcc-9-plugin-dev (=9.3.0-15+equi1),
+ debhelper (>= 12)
+Standards-Version: 4.4.1
+Homepage: https://www.frrouting.org/
+Vcs-Browser: https://github.com/FRRouting/frr/
+Vcs-Git: https://github.com/FRRouting/frr.git
+
+Package: gcc-9-frr-plugin
+Architecture: linux-any
+Depends:
+ gcc-9 (=9.3.0-15+equi1),
+ ${misc:Depends},
+ ${shlibs:Depends}
+Description: GCC plugin for FRRouting
diff --git a/tools/gcc-plugins/debian/copyright b/tools/gcc-plugins/debian/copyright
new file mode 100644
index 0000000..dcd9fa1
--- /dev/null
+++ b/tools/gcc-plugins/debian/copyright
@@ -0,0 +1,9 @@
+Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
+Upstream-Name: FRR
+Upstream-Contact: maintainers@frrouting.org, security@frrouting.org
+Source: https://www.frrouting.org/
+
+Files: *
+Copyright:
+ 2019-2020 by David Lamparter
+ Code derived from GCC, please refer to gcc package copyright.
diff --git a/tools/gcc-plugins/debian/rules b/tools/gcc-plugins/debian/rules
new file mode 100755
index 0000000..f8f42ad
--- /dev/null
+++ b/tools/gcc-plugins/debian/rules
@@ -0,0 +1,11 @@
+#!/usr/bin/make -f
+
+# standard Debian options & profiles
+
+export DEB_BUILD_MAINT_OPTIONS = hardening=+all
+
+%:
+ dh $@
+
+override_dh_auto_test:
+ true
diff --git a/tools/gcc-plugins/debian/source/format b/tools/gcc-plugins/debian/source/format
new file mode 100644
index 0000000..89ae9db
--- /dev/null
+++ b/tools/gcc-plugins/debian/source/format
@@ -0,0 +1 @@
+3.0 (native)
diff --git a/tools/gcc-plugins/format-test.c b/tools/gcc-plugins/format-test.c
new file mode 100644
index 0000000..fb7e41c
--- /dev/null
+++ b/tools/gcc-plugins/format-test.c
@@ -0,0 +1,113 @@
+#include <stddef.h>
+#include <stdlib.h>
+#include <netinet/in.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+typedef unsigned long mytype;
+typedef size_t mysize;
+
+typedef unsigned int not_in_addr_t;
+typedef in_addr_t yes_in_addr_t;
+typedef struct in_addr in_addr_s;
+
+struct other {
+ int x;
+};
+
+int testfn(const char *fmt, ...) __attribute__((frr_format("frr_printf", 1, 2)));
+
+#ifndef _FRR_ATTRIBUTE_PRINTFRR
+#error please load the frr-format plugin
+#endif
+
+#pragma FRR printfrr_ext "%pI4" (struct in_addr *)
+#pragma FRR printfrr_ext "%pI4" (in_addr_t *)
+
+int test(unsigned long long ay)
+{
+ size_t v_size_t = 0;
+ long v_long = 0;
+ int v_int = 0;
+ uint64_t v_uint64_t = 0;
+ mytype v_mytype = 0;
+ mysize v_mysize = 0;
+ pid_t v_pid_t = 0;
+
+ testfn("%zu", v_size_t); // NOWARN
+ testfn("%zu", v_long); // WARN
+ testfn("%zu", v_int); // WARN
+ testfn("%zu", sizeof(v_int)); // NOWARN
+ testfn("%zu", v_mytype); // WARN
+ testfn("%zu", v_mysize); // NOWARN
+ testfn("%zu", v_uint64_t); // WARN
+ testfn("%zu", v_pid_t); // WARN
+
+ testfn("%lu", v_long); // NOWARN PEDANTIC
+ testfn("%lu", v_int); // WARN
+ testfn("%lu", v_size_t); // WARN
+ testfn("%lu", sizeof(v_int)); // NOWARN (integer constant)
+ testfn("%lu", v_uint64_t); // WARN
+ testfn("%lu", v_pid_t); // WARN
+
+ testfn("%ld", v_long); // NOWARN
+ testfn("%ld", v_int); // WARN
+ testfn("%ld", v_size_t); // WARN
+ testfn("%ld", sizeof(v_int)); // NOWARN (integer constant)
+ testfn("%ld", v_uint64_t); // WARN
+ testfn("%ld", v_pid_t); // WARN
+
+ testfn("%d", v_int); // NOWARN
+ testfn("%d", v_long); // WARN
+ testfn("%d", v_size_t); // WARN
+ testfn("%d", sizeof(v_int)); // WARN
+ testfn("%d", v_uint64_t); // WARN
+ testfn("%d", v_pid_t); // WARN
+
+ testfn("%Lu", v_size_t); // WARN
+ testfn("%Lu", v_long); // WARN
+ testfn("%Lu", v_int); // WARN
+ testfn("%Lu", sizeof(v_int)); // NOWARN (integer constant)
+ testfn("%Lu", v_mytype); // WARN
+ testfn("%Lu", v_mysize); // WARN
+ testfn("%Lu", v_pid_t); // WARN
+ testfn("%Lu", v_uint64_t); // NOWARN
+
+ testfn("%Ld", v_size_t); // WARN
+ testfn("%Ld", v_long); // WARN
+ testfn("%Ld", v_int); // WARN
+ testfn("%Ld", sizeof(v_int)); // NOWARN (integer constant)
+ testfn("%Ld", v_mytype); // WARN
+ testfn("%Ld", v_mysize); // WARN
+ testfn("%Ld", v_pid_t); // WARN
+ testfn("%Ld", v_uint64_t); // NOWARN
+
+ /* retain-typeinfo patch */
+ testfn("%zu", (size_t)v_pid_t); // NOWARN (need retain-typeinfo patch)
+ testfn("%lu", (size_t)v_pid_t); // WARN (need retain-typeinfo patch)
+ testfn("%Lu", (uint64_t)v_pid_t); // NOWARN (need retain-typeinfo patch)
+ testfn("%lu", (uint64_t)v_pid_t); // WARN (need retain-typeinfo patch)
+
+ testfn("%pI4", &v_long); // WARN
+
+ in_addr_t v_in_addr_t;
+ yes_in_addr_t v_yes_in_addr_t;
+ not_in_addr_t v_not_in_addr_t;
+ void *v_voidp = &v_in_addr_t;
+
+ testfn("%pI4", &v_in_addr_t); // NOWARN
+ testfn("%pI4", &v_yes_in_addr_t); // NOWARN
+ testfn("%pI4", &v_not_in_addr_t); // WARN
+ testfn("%pI4", v_voidp); // WARN
+
+ struct in_addr v_in_addr;
+ in_addr_s v_in_addr_s;
+ struct other v_other;
+ const struct in_addr *v_in_addr_const = &v_in_addr;
+
+ testfn("%pI4", &v_in_addr); // NOWARN
+ testfn("%pI4", &v_in_addr_s); // NOWARN
+ testfn("%pI4", &v_other); // WARN
+ testfn("%pI4", v_in_addr_const); // NOWARN
+ return 0;
+}
diff --git a/tools/gcc-plugins/format-test.py b/tools/gcc-plugins/format-test.py
new file mode 100644
index 0000000..ddf71aa
--- /dev/null
+++ b/tools/gcc-plugins/format-test.py
@@ -0,0 +1,74 @@
+import subprocess
+import sys
+import shlex
+import os
+import re
+
+os.environ["LC_ALL"] = "C"
+os.environ["LANG"] = "C"
+for k in list(os.environ.keys()):
+ if k.startswith("LC_"):
+ os.environ.pop(k)
+
+if len(sys.argv) < 2:
+ sys.stderr.write("start as format-test.py gcc-123.45 [-options ...]\n")
+ sys.exit(1)
+
+c_re = re.compile(r"//\s+(NO)?WARN")
+expect = {}
+lines = {}
+
+with open("format-test.c", "r") as fd:
+ for lno, line in enumerate(fd.readlines(), 1):
+ lines[lno] = line.strip()
+ m = c_re.search(line)
+ if m is None:
+ continue
+ if m.group(1) is None:
+ expect[lno] = "warn"
+ else:
+ expect[lno] = "nowarn"
+
+cmd = shlex.split(
+ "-Wall -Wextra -Wno-unused -fplugin=./frr-format.so -fno-diagnostics-show-caret -c -o format-test.o format-test.c"
+)
+
+gcc = subprocess.Popen(
+ sys.argv[1:] + cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE
+)
+sout, serr = gcc.communicate()
+gcc.wait()
+
+gcclines = serr.decode("UTF-8").splitlines()
+line_re = re.compile(r"^format-test\.c:(\d+):(.*)$")
+gcc_warns = {}
+
+for line in gcclines:
+ if line.find("In function") >= 0:
+ continue
+ m = line_re.match(line)
+ if m is None:
+ sys.stderr.write("cannot process GCC output: %s\n" % line)
+ continue
+
+ lno = int(m.group(1))
+ gcc_warns.setdefault(lno, []).append(line)
+
+for lno, val in expect.items():
+ if val == "nowarn" and lno in gcc_warns:
+ sys.stderr.write(
+ "unexpected gcc warning on line %d:\n\t%s\n\t%s\n"
+ % (lno, lines[lno], "\n\t".join(gcc_warns[lno]))
+ )
+ if val == "warn" and lno not in gcc_warns:
+ sys.stderr.write(
+ "expected warning on line %d but did not get one\n\t%s\n"
+ % (lno, lines[lno])
+ )
+
+leftover = set(gcc_warns.keys()) - set(expect.keys())
+for lno in sorted(leftover):
+ sys.stderr.write(
+ "unmarked gcc warning on line %d:\n\t%s\n\t%s\n"
+ % (lno, lines[lno], "\n\t".join(gcc_warns[lno]))
+ )
diff --git a/tools/gcc-plugins/frr-format.c b/tools/gcc-plugins/frr-format.c
new file mode 100644
index 0000000..4e2c2d3
--- /dev/null
+++ b/tools/gcc-plugins/frr-format.c
@@ -0,0 +1,4468 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+/* Check calls to formatted I/O functions (-Wformat).
+ Copyright (C) 1992-2019 Free Software Foundation, Inc.
+
+ Extended for FRR's printfrr() with Linux kernel style extensions
+ Copyright (C) 2019-2020 David Lamparter, for NetDEF, Inc.
+ */
+
+#include "gcc-common.h"
+
+#include "config.h"
+#include "system.h"
+#include "coretypes.h"
+#include "tm.h"
+//include "c-target.h"
+#include "c-common.h"
+#include "alloc-pool.h"
+#include "stringpool.h"
+#include "c-tree.h"
+#include "c-objc.h"
+#include "intl.h"
+#include "langhooks.h"
+#include "frr-format.h"
+#include "diagnostic.h"
+#include "substring-locations.h"
+#include "selftest.h"
+#include "selftest-diagnostic.h"
+#ifndef FIRST_PSEUDO_REGISTER
+#define FIRST_PSEUDO_REGISTER 0
+#endif
+#include "builtins.h"
+#include "attribs.h"
+#include "gcc-rich-location.h"
+#include "c-pretty-print.h"
+#include "c-pragma.h"
+
+extern struct cpp_reader *parse_in;
+
+#pragma GCC visibility push(hidden)
+
+/* Handle attributes associated with format checking. */
+
+/* This must be in the same order as format_types, except for
+ format_type_error. Target-specific format types do not have
+ matching enum values. */
+enum format_type { frr_printf_format_type,
+ format_type_error = -1};
+
+struct function_format_info
+{
+ int format_type; /* type of format (printf, scanf, etc.) */
+ unsigned HOST_WIDE_INT format_num; /* number of format argument */
+ unsigned HOST_WIDE_INT first_arg_num; /* number of first arg (zero for varargs) */
+};
+
+static GTY(()) tree local_uint64_t_node;
+static GTY(()) tree local_int64_t_node;
+
+static GTY(()) tree local_size_t_node;
+static GTY(()) tree local_ssize_t_node;
+static GTY(()) tree local_atomic_size_t_node;
+static GTY(()) tree local_atomic_ssize_t_node;
+static GTY(()) tree local_ptrdiff_t_node;
+
+static GTY(()) tree local_pid_t_node;
+static GTY(()) tree local_uid_t_node;
+static GTY(()) tree local_gid_t_node;
+static GTY(()) tree local_time_t_node;
+
+static GTY(()) tree local_socklen_t_node;
+static GTY(()) tree local_in_addr_t_node;
+
+static struct type_special {
+ tree *match;
+ tree *replace;
+ tree *cousin;
+} special_types[] = {
+ { &local_atomic_size_t_node, &local_size_t_node, &local_ssize_t_node, },
+ { &local_atomic_ssize_t_node, &local_ssize_t_node, &local_size_t_node, },
+ { &local_size_t_node, NULL, &local_ssize_t_node, },
+ { &local_ssize_t_node, NULL, &local_size_t_node, },
+ { &local_uint64_t_node, NULL, &local_int64_t_node, },
+ { &local_int64_t_node, NULL, &local_uint64_t_node, },
+ { &local_pid_t_node, NULL, &local_pid_t_node, },
+ { &local_uid_t_node, NULL, &local_uid_t_node, },
+ { &local_gid_t_node, NULL, &local_gid_t_node, },
+ { &local_time_t_node, NULL, &local_time_t_node, },
+ { NULL, NULL, NULL, }
+};
+
+static bool decode_format_attr (tree, function_format_info *, int);
+static int decode_format_type (const char *);
+
+static bool check_format_string (tree argument,
+ unsigned HOST_WIDE_INT format_num,
+ int flags, bool *no_add_attrs,
+ int expected_format_type);
+static bool get_constant (tree expr, unsigned HOST_WIDE_INT *value,
+ int validated_p);
+static const char *convert_format_name_to_system_name (const char *attr_name);
+
+static int first_target_format_type;
+static const char *format_name (int format_num);
+static int format_flags (int format_num);
+
+/* Emit a warning as per format_warning_va, but construct the substring_loc
+ for the character at offset (CHAR_IDX - 1) within a string constant
+ FORMAT_STRING_CST at FMT_STRING_LOC. */
+
+ATTRIBUTE_GCC_DIAG (5,6)
+static bool
+format_warning_at_char (location_t fmt_string_loc, tree format_string_cst,
+ int char_idx, int opt, const char *gmsgid, ...)
+{
+ va_list ap;
+ va_start (ap, gmsgid);
+ tree string_type = TREE_TYPE (format_string_cst);
+
+ /* The callers are of the form:
+ format_warning (format_string_loc, format_string_cst,
+ format_chars - orig_format_chars,
+ where format_chars has already been incremented, so that
+ CHAR_IDX is one character beyond where the warning should
+ be emitted. Fix it. */
+ char_idx -= 1;
+
+ substring_loc fmt_loc (fmt_string_loc, string_type, char_idx, char_idx,
+ char_idx);
+#if BUILDING_GCC_VERSION >= 9000
+ format_string_diagnostic_t diag (fmt_loc, NULL, UNKNOWN_LOCATION, NULL,
+ NULL);
+ bool warned = diag.emit_warning_va (opt, gmsgid, &ap);
+#else
+ bool warned = format_warning_va (fmt_loc, UNKNOWN_LOCATION, NULL,
+ opt, gmsgid, &ap);
+#endif
+ va_end (ap);
+
+ return warned;
+}
+
+/* Check that we have a pointer to a string suitable for use as a format.
+ The default is to check for a char type.
+ For objective-c dialects, this is extended to include references to string
+ objects validated by objc_string_ref_type_p ().
+ Targets may also provide a string object type that can be used within c and
+ c++ and shared with their respective objective-c dialects. In this case the
+ reference to a format string is checked for validity via a hook.
+
+ The function returns true if strref points to any string type valid for the
+ language dialect and target. */
+
+static bool
+valid_stringptr_type_p (tree strref)
+{
+ return (strref != NULL
+ && TREE_CODE (strref) == POINTER_TYPE
+ && (TYPE_MAIN_VARIANT (TREE_TYPE (strref)) == char_type_node
+ || objc_string_ref_type_p (strref)));
+// || (*targetcm.string_object_ref_type_p) ((const_tree) strref)));
+}
+
+/* Handle a "format_arg" attribute; arguments as in
+ struct attribute_spec.handler. */
+tree
+handle_frr_format_arg_attribute (tree *node, tree ARG_UNUSED (name),
+ tree args, int flags, bool *no_add_attrs)
+{
+ tree type = *node;
+ tree format_num_expr = TREE_VALUE (args);
+ unsigned HOST_WIDE_INT format_num = 0;
+
+ if (!get_constant (format_num_expr, &format_num, 0))
+ {
+ error ("format string has invalid operand number");
+ *no_add_attrs = true;
+ return NULL_TREE;
+ }
+
+ if (prototype_p (type))
+ {
+ /* The format arg can be any string reference valid for the language and
+ target. We cannot be more specific in this case. */
+ if (!check_format_string (type, format_num, flags, no_add_attrs, -1))
+ return NULL_TREE;
+ }
+
+ if (!valid_stringptr_type_p (TREE_TYPE (type)))
+ {
+ if (!(flags & (int) ATTR_FLAG_BUILT_IN))
+ error ("function does not return string type");
+ *no_add_attrs = true;
+ return NULL_TREE;
+ }
+
+ return NULL_TREE;
+}
+
+/* Verify that the format_num argument is actually a string reference suitable,
+ for the language dialect and target (in case the format attribute is in
+ error). When we know the specific reference type expected, this is also
+ checked. */
+static bool
+check_format_string (tree fntype, unsigned HOST_WIDE_INT format_num,
+ int flags, bool *no_add_attrs, int expected_format_type)
+{
+ unsigned HOST_WIDE_INT i;
+ bool is_target_sref, is_char_ref;
+ tree ref;
+ int fmt_flags;
+ function_args_iterator iter;
+
+ i = 1;
+ FOREACH_FUNCTION_ARGS (fntype, ref, iter)
+ {
+ if (i == format_num)
+ break;
+ i++;
+ }
+
+ if (!ref
+ || !valid_stringptr_type_p (ref))
+ {
+ if (!(flags & (int) ATTR_FLAG_BUILT_IN))
+ error ("format string argument is not a string type");
+ *no_add_attrs = true;
+ return false;
+ }
+
+ /* We only know that we want a suitable string reference. */
+ if (expected_format_type < 0)
+ return true;
+
+ /* Now check that the arg matches the expected type. */
+ is_char_ref =
+ (TYPE_MAIN_VARIANT (TREE_TYPE (ref)) == char_type_node);
+
+ fmt_flags = format_flags (expected_format_type);
+ is_target_sref = false;
+
+ if (!(fmt_flags & FMT_FLAG_PARSE_ARG_CONVERT_EXTERNAL))
+ {
+ if (is_char_ref)
+ return true; /* OK, we expected a char and found one. */
+ else
+ {
+ error ("found a %qT but the format argument should be a string",
+ ref);
+ *no_add_attrs = true;
+ return false;
+ }
+ }
+
+ /* We expect a string object type as the format arg. */
+ if (is_char_ref)
+ {
+ error ("format argument should be a %qs reference but a string was found", format_name (expected_format_type));
+ *no_add_attrs = true;
+ return false;
+ }
+
+ /* We will allow a target string ref to match only itself. */
+ if (first_target_format_type
+ && expected_format_type >= first_target_format_type
+ && is_target_sref)
+ return true;
+ else
+ {
+ error ("format argument should be a %qs reference",
+ format_name (expected_format_type));
+ *no_add_attrs = true;
+ return false;
+ }
+
+ gcc_unreachable ();
+}
+
+/* Verify EXPR is a constant, and store its value.
+ If validated_p is true there should be no errors.
+ Returns true on success, false otherwise. */
+static bool
+get_constant (tree expr, unsigned HOST_WIDE_INT *value, int validated_p)
+{
+ if (!tree_fits_uhwi_p (expr))
+ {
+ gcc_assert (!validated_p);
+ return false;
+ }
+
+ *value = TREE_INT_CST_LOW (expr);
+
+ return true;
+}
+
+/* Decode the arguments to a "format" attribute into a
+ function_format_info structure. It is already known that the list
+ is of the right length. If VALIDATED_P is true, then these
+ attributes have already been validated and must not be erroneous;
+ if false, it will give an error message. Returns true if the
+ attributes are successfully decoded, false otherwise. */
+
+static bool
+decode_format_attr (tree args, function_format_info *info, int validated_p)
+{
+ tree format_type_id = TREE_VALUE (args);
+ tree format_num_expr = TREE_VALUE (TREE_CHAIN (args));
+ tree first_arg_num_expr
+ = TREE_VALUE (TREE_CHAIN (TREE_CHAIN (args)));
+
+ if (TREE_CODE (format_type_id) != STRING_CST)
+ {
+ gcc_assert (!validated_p);
+ error ("unrecognized format specifier");
+ return false;
+ }
+ else
+ {
+ const char *p = TREE_STRING_POINTER (format_type_id);
+
+ p = convert_format_name_to_system_name (p);
+
+ info->format_type = decode_format_type (p);
+
+ if (info->format_type == format_type_error)
+ {
+ gcc_assert (!validated_p);
+ warning (OPT_Wformat_, "%qE is an unrecognized format function type",
+ format_type_id);
+ return false;
+ }
+ }
+
+ if (!get_constant (format_num_expr, &info->format_num, validated_p))
+ {
+ error ("format string has invalid operand number");
+ return false;
+ }
+
+ if (!get_constant (first_arg_num_expr, &info->first_arg_num, validated_p))
+ {
+ error ("%<...%> has invalid operand number");
+ return false;
+ }
+
+ if (info->first_arg_num != 0 && info->first_arg_num <= info->format_num)
+ {
+ gcc_assert (!validated_p);
+ error ("format string argument follows the arguments to be formatted");
+ return false;
+ }
+
+ return true;
+}
+
+/* Check a call to a format function against a parameter list. */
+
+/* The C standard version C++ is treated as equivalent to
+ or inheriting from, for the purpose of format features supported. */
+#define CPLUSPLUS_STD_VER (cxx_dialect < cxx11 ? STD_C94 : STD_C99)
+/* The C standard version we are checking formats against when pedantic. */
+#define C_STD_VER ((int) (c_dialect_cxx () \
+ ? CPLUSPLUS_STD_VER \
+ : (flag_isoc99 \
+ ? STD_C99 \
+ : (flag_isoc94 ? STD_C94 : STD_C89))))
+/* The name to give to the standard version we are warning about when
+ pedantic. FEATURE_VER is the version in which the feature warned out
+ appeared, which is higher than C_STD_VER. */
+#define C_STD_NAME(FEATURE_VER) (c_dialect_cxx () \
+ ? (cxx_dialect < cxx11 ? "ISO C++98" \
+ : "ISO C++11") \
+ : ((FEATURE_VER) == STD_EXT \
+ ? "ISO C" \
+ : "ISO C90"))
+/* Adjust a C standard version, which may be STD_C9L, to account for
+ -Wno-long-long. Returns other standard versions unchanged. */
+#define ADJ_STD(VER) ((int) ((VER) == STD_C9L \
+ ? (warn_long_long ? STD_C99 : STD_C89) \
+ : (VER)))
+
+/* Enum describing the kind of specifiers present in the format and
+ requiring an argument. */
+enum format_specifier_kind {
+ CF_KIND_FORMAT,
+ CF_KIND_FIELD_WIDTH,
+ CF_KIND_FIELD_PRECISION
+};
+
+static const char *kind_descriptions[] = {
+ N_("format"),
+ N_("field width specifier"),
+ N_("field precision specifier")
+};
+
+/* Structure describing details of a type expected in format checking,
+ and the type to check against it. */
+struct format_wanted_type
+{
+ /* The type wanted. */
+ tree wanted_type;
+ /* The name of this type to use in diagnostics. */
+ const char *wanted_type_name;
+ /* Should be type checked just for scalar width identity. */
+ int scalar_identity_flag;
+ /* The level of indirection through pointers at which this type occurs. */
+ int pointer_count;
+ /* Whether, when pointer_count is 1, to allow any character type when
+ pedantic, rather than just the character or void type specified. */
+ int char_lenient_flag;
+ /* Whether the argument, dereferenced once, is written into and so the
+ argument must not be a pointer to a const-qualified type. */
+ int writing_in_flag;
+ /* Whether the argument, dereferenced once, is read from and so
+ must not be a NULL pointer. */
+ int reading_from_flag;
+ /* The kind of specifier that this type is used for. */
+ enum format_specifier_kind kind;
+ /* The starting character of the specifier. This never includes the
+ initial percent sign. */
+ const char *format_start;
+ /* The length of the specifier. */
+ int format_length;
+ /* The actual parameter to check against the wanted type. */
+ tree param;
+ /* The argument number of that parameter. */
+ int arg_num;
+ /* The offset location of this argument with respect to the format
+ string location. */
+ unsigned int offset_loc;
+ /* The next type to check for this format conversion, or NULL if none. */
+ struct format_wanted_type *next;
+};
+
+/* Convenience macro for format_length_info meaning unused. */
+#define NO_FMT NULL, FMT_LEN_none, STD_C89
+
+static const format_length_info printf_length_specs[] =
+{
+ { "h", FMT_LEN_h, STD_C89, "hh", FMT_LEN_hh, STD_C99, 0 },
+ { "l", FMT_LEN_l, STD_C89, "ll", FMT_LEN_ll, STD_C9L, 0 },
+ { "q", FMT_LEN_ll, STD_EXT, NO_FMT, 0 },
+ { "L", FMT_LEN_L, STD_C89, NO_FMT, 0 },
+ { "z", FMT_LEN_z, STD_C99, NO_FMT, 0 },
+ { "Z", FMT_LEN_z, STD_EXT, NO_FMT, 0 },
+ { "t", FMT_LEN_t, STD_C99, NO_FMT, 0 },
+ { "j", FMT_LEN_j, STD_C99, NO_FMT, 0 },
+ { "H", FMT_LEN_H, STD_EXT, NO_FMT, 0 },
+ { "D", FMT_LEN_D, STD_EXT, "DD", FMT_LEN_DD, STD_EXT, 0 },
+ { NO_FMT, NO_FMT, 0 }
+};
+
+static const format_flag_spec printf_flag_specs[] =
+{
+ { ' ', 0, 0, 0, N_("' ' flag"), N_("the ' ' printf flag"), STD_C89 },
+ { '+', 0, 0, 0, N_("'+' flag"), N_("the '+' printf flag"), STD_C89 },
+ { '#', 0, 0, 0, N_("'#' flag"), N_("the '#' printf flag"), STD_C89 },
+ { '0', 0, 0, 0, N_("'0' flag"), N_("the '0' printf flag"), STD_C89 },
+ { '-', 0, 0, 0, N_("'-' flag"), N_("the '-' printf flag"), STD_C89 },
+ { '\'', 0, 0, 0, N_("''' flag"), N_("the ''' printf flag"), STD_EXT },
+ { 'I', 0, 0, 0, N_("'I' flag"), N_("the 'I' printf flag"), STD_EXT },
+ { 'w', 0, 0, 0, N_("field width"), N_("field width in printf format"), STD_C89 },
+ { 'p', 0, 0, 0, N_("precision"), N_("precision in printf format"), STD_C89 },
+ { 'L', 0, 0, 0, N_("length modifier"), N_("length modifier in printf format"), STD_C89 },
+ { 0, 0, 0, 0, NULL, NULL, STD_C89 }
+};
+
+
+static const format_flag_pair printf_flag_pairs[] =
+{
+ { ' ', '+', 1, 0 },
+ { '0', '-', 1, 0 },
+ { '0', 'p', 1, 'i' },
+ { 0, 0, 0, 0 }
+};
+
+#define ETAB_SZ 128
+static kernel_ext_fmt ext_p[ETAB_SZ] = {
+ { }
+};
+static kernel_ext_fmt ext_d[ETAB_SZ] = {
+ { }
+};
+
+static const format_char_info print_char_table[] =
+{
+ /* C89 conversion specifiers. */
+ /* none, hh, h, l, ll, L, z, t, j, H, D, DD */
+ { "di", 0, STD_C89, { T89_I, T99_SC, T89_S, T89_L, T9L_LL, TEX_S64, T99_SST, T99_PD, T99_IM, BADLEN, BADLEN, BADLEN }, "-wp0 +'I", "i", NULL, ext_d },
+ { "oxXb",0, STD_C89, { T89_UI, T99_UC, T89_US, T89_UL, T9L_ULL, TEX_U64, T99_ST, T99_UPD, T99_UIM, BADLEN, BADLEN, BADLEN }, "-wp0#", "i", NULL, NULL },
+ { "u", 0, STD_C89, { T89_UI, T99_UC, T89_US, T89_UL, T9L_ULL, TEX_U64, T99_ST, T99_UPD, T99_UIM, BADLEN, BADLEN, BADLEN }, "-wp0'I", "i", NULL, NULL },
+ { "fgG", 0, STD_C89, { T89_D, BADLEN, BADLEN, T99_D, BADLEN, T89_LD, BADLEN, BADLEN, BADLEN, TEX_D32, TEX_D64, TEX_D128 }, "-wp0 +#'I", "", NULL, NULL },
+ { "eE", 0, STD_C89, { T89_D, BADLEN, BADLEN, T99_D, BADLEN, T89_LD, BADLEN, BADLEN, BADLEN, TEX_D32, TEX_D64, TEX_D128 }, "-wp0 +#I", "", NULL, NULL },
+ { "c", 0, STD_C89, { T89_I, BADLEN, BADLEN, T94_WI, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN }, "-w", "", NULL, NULL },
+ { "s", 1, STD_C89, { T89_C, BADLEN, BADLEN, T94_W, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN }, "-wp", "cR", NULL, NULL },
+ { "p", 1, STD_C89, { T89_V, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN }, "-wp", "c", NULL, ext_p },
+ { "n", 1, STD_C89, { T89_I, T99_SC, T89_S, T89_L, T9L_LL, BADLEN, T99_SST, T99_PD, T99_IM, BADLEN, BADLEN, BADLEN }, "", "W", NULL, NULL },
+ /* C99 conversion specifiers. */
+ { "F", 0, STD_C99, { T99_D, BADLEN, BADLEN, T99_D, BADLEN, T99_LD, BADLEN, BADLEN, BADLEN, TEX_D32, TEX_D64, TEX_D128 }, "-wp0 +#'I", "", NULL, NULL },
+ { "aA", 0, STD_C99, { T99_D, BADLEN, BADLEN, T99_D, BADLEN, T99_LD, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN }, "-wp0 +#", "", NULL, NULL },
+ /* X/Open conversion specifiers. */
+ { "C", 0, STD_EXT, { TEX_WI, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN }, "-w", "", NULL, NULL },
+ { "S", 1, STD_EXT, { TEX_W, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN }, "-wp", "R", NULL, NULL },
+ /* GNU conversion specifiers. */
+ { "m", 0, STD_EXT, { T89_V, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN }, "-wp", "", NULL, NULL },
+ { NULL, 0, STD_C89, NOLENGTHS, NULL, NULL, NULL, NULL }
+};
+
+/* This must be in the same order as enum format_type. */
+static const format_kind_info format_types_orig[] =
+{
+ { "frr_printf", printf_length_specs, print_char_table, " +#0-'I", NULL,
+ printf_flag_specs, printf_flag_pairs,
+ FMT_FLAG_ARG_CONVERT|FMT_FLAG_DOLLAR_MULTIPLE|FMT_FLAG_USE_DOLLAR|FMT_FLAG_EMPTY_PREC_OK,
+ 'w', 0, 'p', 0, 'L', 0,
+ &integer_type_node, &integer_type_node
+ },
+};
+
+/* This layer of indirection allows GCC to reassign format_types with
+ new data if necessary, while still allowing the original data to be
+ const. */
+static const format_kind_info *format_types = format_types_orig;
+
+static int n_format_types = ARRAY_SIZE (format_types_orig);
+
+/* Structure detailing the results of checking a format function call
+ where the format expression may be a conditional expression with
+ many leaves resulting from nested conditional expressions. */
+struct format_check_results
+{
+ /* Number of leaves of the format argument that could not be checked
+ as they were not string literals. */
+ int number_non_literal;
+ /* Number of leaves of the format argument that were null pointers or
+ string literals, but had extra format arguments. */
+ int number_extra_args;
+ location_t extra_arg_loc;
+ /* Number of leaves of the format argument that were null pointers or
+ string literals, but had extra format arguments and used $ operand
+ numbers. */
+ int number_dollar_extra_args;
+ /* Number of leaves of the format argument that were wide string
+ literals. */
+ int number_wide;
+ /* Number of leaves of the format argument that are not array of "char". */
+ int number_non_char;
+ /* Number of leaves of the format argument that were empty strings. */
+ int number_empty;
+ /* Number of leaves of the format argument that were unterminated
+ strings. */
+ int number_unterminated;
+ /* Number of leaves of the format argument that were not counted above. */
+ int number_other;
+ /* Location of the format string. */
+ location_t format_string_loc;
+};
+
+struct format_check_context
+{
+ format_check_results *res;
+ function_format_info *info;
+ tree params;
+ vec<location_t> *arglocs;
+};
+
+/* Return the format name (as specified in the original table) for the format
+ type indicated by format_num. */
+static const char *
+format_name (int format_num)
+{
+ if (format_num >= 0 && format_num < n_format_types)
+ return format_types[format_num].name;
+ gcc_unreachable ();
+}
+
+/* Return the format flags (as specified in the original table) for the format
+ type indicated by format_num. */
+static int
+format_flags (int format_num)
+{
+ if (format_num >= 0 && format_num < n_format_types)
+ return format_types[format_num].flags;
+ gcc_unreachable ();
+}
+
+static void check_format_info (function_format_info *, tree,
+ vec<location_t> *);
+static void check_format_arg (void *, tree, unsigned HOST_WIDE_INT);
+static void check_format_info_main (format_check_results *,
+ function_format_info *, const char *,
+ location_t, tree,
+ int, tree,
+ unsigned HOST_WIDE_INT,
+ object_allocator<format_wanted_type> &,
+ vec<location_t> *);
+
+static void init_dollar_format_checking (int, tree);
+static int maybe_read_dollar_number (const char **, int,
+ tree, tree *, const format_kind_info *);
+static bool avoid_dollar_number (const char *);
+static void finish_dollar_format_checking (format_check_results *, int);
+
+static const format_flag_spec *get_flag_spec (const format_flag_spec *,
+ int, const char *);
+
+static void check_format_types (const substring_loc &fmt_loc,
+ format_wanted_type *,
+ const format_kind_info *fki,
+ int offset_to_type_start,
+ char conversion_char,
+ vec<location_t> *arglocs);
+static void format_type_warning (const substring_loc &fmt_loc,
+ location_t param_loc,
+ format_wanted_type *, tree,
+ tree,
+ const format_kind_info *fki,
+ int offset_to_type_start,
+ char conversion_char,
+ const char *extra = NULL);
+
+static bool check_kef_type (const substring_loc &fmt_loc,
+ const struct kernel_ext_fmt *kef,
+ unsigned arg_num,
+ tree cur_param,
+ tree wanted_type,
+ const format_kind_info *fki,
+ int offset_to_type_start,
+ char conversion_char,
+ vec<location_t> *arglocs);
+
+/* Decode a format type from a string, returning the type, or
+ format_type_error if not valid, in which case the caller should print an
+ error message. */
+static int
+decode_format_type (const char *s)
+{
+ int i;
+ int slen;
+
+ s = convert_format_name_to_system_name (s);
+ slen = strlen (s);
+ for (i = 0; i < n_format_types; i++)
+ {
+ int alen;
+ if (!strcmp (s, format_types[i].name))
+ return i;
+ alen = strlen (format_types[i].name);
+ if (slen == alen + 4 && s[0] == '_' && s[1] == '_'
+ && s[slen - 1] == '_' && s[slen - 2] == '_'
+ && !strncmp (s + 2, format_types[i].name, alen))
+ return i;
+ }
+ return format_type_error;
+}
+
+
+/* Check the argument list of a call to printf, scanf, etc.
+ ATTRS are the attributes on the function type. There are NARGS argument
+ values in the array ARGARRAY.
+ Also, if -Wsuggest-attribute=format,
+ warn for calls to vprintf or vscanf in functions with no such format
+ attribute themselves. */
+
+void
+check_function_format (tree attrs, int nargs, tree *argarray,
+ vec<location_t> *arglocs)
+{
+ tree a;
+
+ /* See if this function has any format attributes. */
+ for (a = attrs; a; a = TREE_CHAIN (a))
+ {
+ if (is_attribute_p ("frr_format", TREE_PURPOSE (a)))
+ {
+ /* Yup; check it. */
+ function_format_info info;
+ decode_format_attr (TREE_VALUE (a), &info, /*validated=*/true);
+ if (warn_format)
+ {
+ /* FIXME: Rewrite all the internal functions in this file
+ to use the ARGARRAY directly instead of constructing this
+ temporary list. */
+ tree params = NULL_TREE;
+ int i;
+ for (i = nargs - 1; i >= 0; i--)
+ params = tree_cons (NULL_TREE, argarray[i], params);
+ check_format_info (&info, params, arglocs);
+ }
+
+ /* Attempt to detect whether the current function might benefit
+ from the format attribute if the called function is decorated
+ with it. Avoid using calls with string literal formats for
+ guidance since those are unlikely to be viable candidates. */
+ if (warn_suggest_attribute_format
+ && current_function_decl != NULL_TREE
+ && info.first_arg_num == 0
+ && (format_types[info.format_type].flags
+ & (int) FMT_FLAG_ARG_CONVERT)
+ /* c_strlen will fail for a function parameter but succeed
+ for a literal or constant array. */
+ && !c_strlen (argarray[info.format_num - 1], 1))
+ {
+ tree c;
+ for (c = TYPE_ATTRIBUTES (TREE_TYPE (current_function_decl));
+ c;
+ c = TREE_CHAIN (c))
+ if (is_attribute_p ("frr_format", TREE_PURPOSE (c))
+ && (decode_format_type (IDENTIFIER_POINTER
+ (TREE_VALUE (TREE_VALUE (c))))
+ == info.format_type))
+ break;
+ if (c == NULL_TREE)
+ {
+ /* Check if the current function has a parameter to which
+ the format attribute could be attached; if not, it
+ can't be a candidate for a format attribute, despite
+ the vprintf-like or vscanf-like call. */
+ tree args;
+ for (args = DECL_ARGUMENTS (current_function_decl);
+ args != 0;
+ args = DECL_CHAIN (args))
+ {
+ if (TREE_CODE (TREE_TYPE (args)) == POINTER_TYPE
+ && (TYPE_MAIN_VARIANT (TREE_TYPE (TREE_TYPE (args)))
+ == char_type_node))
+ break;
+ }
+ if (args != 0)
+ warning (OPT_Wsuggest_attribute_format,
+ "function %qD might be a candidate for %qs %<frr_format%> attribute",
+ current_function_decl,
+ format_types[info.format_type].name);
+ }
+ }
+ }
+ }
+}
+
+
+/* Variables used by the checking of $ operand number formats. */
+static char *dollar_arguments_used = NULL;
+static char *dollar_arguments_pointer_p = NULL;
+static int dollar_arguments_alloc = 0;
+static int dollar_arguments_count;
+static int dollar_first_arg_num;
+static int dollar_max_arg_used;
+static int dollar_format_warned;
+
+/* Initialize the checking for a format string that may contain $
+ parameter number specifications; we will need to keep track of whether
+ each parameter has been used. FIRST_ARG_NUM is the number of the first
+ argument that is a parameter to the format, or 0 for a vprintf-style
+ function; PARAMS is the list of arguments starting at this argument. */
+
+static void
+init_dollar_format_checking (int first_arg_num, tree params)
+{
+ tree oparams = params;
+
+ dollar_first_arg_num = first_arg_num;
+ dollar_arguments_count = 0;
+ dollar_max_arg_used = 0;
+ dollar_format_warned = 0;
+ if (first_arg_num > 0)
+ {
+ while (params)
+ {
+ dollar_arguments_count++;
+ params = TREE_CHAIN (params);
+ }
+ }
+ if (dollar_arguments_alloc < dollar_arguments_count)
+ {
+ free (dollar_arguments_used);
+ free (dollar_arguments_pointer_p);
+ dollar_arguments_alloc = dollar_arguments_count;
+ dollar_arguments_used = XNEWVEC (char, dollar_arguments_alloc);
+ dollar_arguments_pointer_p = XNEWVEC (char, dollar_arguments_alloc);
+ }
+ if (dollar_arguments_alloc)
+ {
+ memset (dollar_arguments_used, 0, dollar_arguments_alloc);
+ if (first_arg_num > 0)
+ {
+ int i = 0;
+ params = oparams;
+ while (params)
+ {
+ dollar_arguments_pointer_p[i] = (TREE_CODE (TREE_TYPE (TREE_VALUE (params)))
+ == POINTER_TYPE);
+ params = TREE_CHAIN (params);
+ i++;
+ }
+ }
+ }
+}
+
+
+/* Look for a decimal number followed by a $ in *FORMAT. If DOLLAR_NEEDED
+ is set, it is an error if one is not found; otherwise, it is OK. If
+ such a number is found, check whether it is within range and mark that
+ numbered operand as being used for later checking. Returns the operand
+ number if found and within range, zero if no such number was found and
+ this is OK, or -1 on error. PARAMS points to the first operand of the
+ format; PARAM_PTR is made to point to the parameter referred to. If
+ a $ format is found, *FORMAT is updated to point just after it. */
+
+static int
+maybe_read_dollar_number (const char **format,
+ int dollar_needed, tree params, tree *param_ptr,
+ const format_kind_info *fki)
+{
+ int argnum;
+ int overflow_flag;
+ const char *fcp = *format;
+ if (!ISDIGIT (*fcp))
+ {
+ if (dollar_needed)
+ {
+ warning (OPT_Wformat_, "missing $ operand number in format");
+ return -1;
+ }
+ else
+ return 0;
+ }
+ argnum = 0;
+ overflow_flag = 0;
+ while (ISDIGIT (*fcp))
+ {
+ int nargnum;
+ nargnum = 10 * argnum + (*fcp - '0');
+ if (nargnum < 0 || nargnum / 10 != argnum)
+ overflow_flag = 1;
+ argnum = nargnum;
+ fcp++;
+ }
+ if (*fcp != '$')
+ {
+ if (dollar_needed)
+ {
+ warning (OPT_Wformat_, "missing $ operand number in format");
+ return -1;
+ }
+ else
+ return 0;
+ }
+ *format = fcp + 1;
+ if (pedantic && !dollar_format_warned)
+ {
+ warning (OPT_Wformat_, "%s does not support %%n$ operand number formats",
+ C_STD_NAME (STD_EXT));
+ dollar_format_warned = 1;
+ }
+ if (overflow_flag || argnum == 0
+ || (dollar_first_arg_num && argnum > dollar_arguments_count))
+ {
+ warning (OPT_Wformat_, "operand number out of range in format");
+ return -1;
+ }
+ if (argnum > dollar_max_arg_used)
+ dollar_max_arg_used = argnum;
+ /* For vprintf-style functions we may need to allocate more memory to
+ track which arguments are used. */
+ while (dollar_arguments_alloc < dollar_max_arg_used)
+ {
+ int nalloc;
+ nalloc = 2 * dollar_arguments_alloc + 16;
+ dollar_arguments_used = XRESIZEVEC (char, dollar_arguments_used,
+ nalloc);
+ dollar_arguments_pointer_p = XRESIZEVEC (char, dollar_arguments_pointer_p,
+ nalloc);
+ memset (dollar_arguments_used + dollar_arguments_alloc, 0,
+ nalloc - dollar_arguments_alloc);
+ dollar_arguments_alloc = nalloc;
+ }
+ if (!(fki->flags & (int) FMT_FLAG_DOLLAR_MULTIPLE)
+ && dollar_arguments_used[argnum - 1] == 1)
+ {
+ dollar_arguments_used[argnum - 1] = 2;
+ warning (OPT_Wformat_, "format argument %d used more than once in %s format",
+ argnum, fki->name);
+ }
+ else
+ dollar_arguments_used[argnum - 1] = 1;
+ if (dollar_first_arg_num)
+ {
+ int i;
+ *param_ptr = params;
+ for (i = 1; i < argnum && *param_ptr != 0; i++)
+ *param_ptr = TREE_CHAIN (*param_ptr);
+
+ /* This case shouldn't be caught here. */
+ gcc_assert (*param_ptr);
+ }
+ else
+ *param_ptr = 0;
+ return argnum;
+}
+
+/* Ensure that FORMAT does not start with a decimal number followed by
+ a $; give a diagnostic and return true if it does, false otherwise. */
+
+static bool
+avoid_dollar_number (const char *format)
+{
+ if (!ISDIGIT (*format))
+ return false;
+ while (ISDIGIT (*format))
+ format++;
+ if (*format == '$')
+ {
+ warning (OPT_Wformat_, "%<$%> operand number used after format without operand number");
+ return true;
+ }
+ return false;
+}
+
+
+/* Finish the checking for a format string that used $ operand number formats
+ instead of non-$ formats. We check for unused operands before used ones
+ (a serious error, since the implementation of the format function
+ can't know what types to pass to va_arg to find the later arguments).
+ and for unused operands at the end of the format (if we know how many
+ arguments the format had, so not for vprintf). If there were operand
+ numbers out of range on a non-vprintf-style format, we won't have reached
+ here. If POINTER_GAP_OK, unused arguments are OK if all arguments are
+ pointers. */
+
+static void
+finish_dollar_format_checking (format_check_results *res, int pointer_gap_ok)
+{
+ int i;
+ bool found_pointer_gap = false;
+ for (i = 0; i < dollar_max_arg_used; i++)
+ {
+ if (!dollar_arguments_used[i])
+ {
+ if (pointer_gap_ok && (dollar_first_arg_num == 0
+ || dollar_arguments_pointer_p[i]))
+ found_pointer_gap = true;
+ else
+ warning_at (res->format_string_loc, OPT_Wformat_,
+ "format argument %d unused before used argument %d in %<$%>-style format",
+ i + 1, dollar_max_arg_used);
+ }
+ }
+ if (found_pointer_gap
+ || (dollar_first_arg_num
+ && dollar_max_arg_used < dollar_arguments_count))
+ {
+ res->number_other--;
+ res->number_dollar_extra_args++;
+ }
+}
+
+
+/* Retrieve the specification for a format flag. SPEC contains the
+ specifications for format flags for the applicable kind of format.
+ FLAG is the flag in question. If PREDICATES is NULL, the basic
+ spec for that flag must be retrieved and must exist. If
+ PREDICATES is not NULL, it is a string listing possible predicates
+ for the spec entry; if an entry predicated on any of these is
+ found, it is returned, otherwise NULL is returned. */
+
+static const format_flag_spec *
+get_flag_spec (const format_flag_spec *spec, int flag, const char *predicates)
+{
+ int i;
+ for (i = 0; spec[i].flag_char != 0; i++)
+ {
+ if (spec[i].flag_char != flag)
+ continue;
+ if (predicates != NULL)
+ {
+ if (spec[i].predicate != 0
+ && strchr (predicates, spec[i].predicate) != 0)
+ return &spec[i];
+ }
+ else if (spec[i].predicate == 0)
+ return &spec[i];
+ }
+ gcc_assert (predicates);
+ return NULL;
+}
+
+
+/* Check the argument list of a call to printf, scanf, etc.
+ INFO points to the function_format_info structure.
+ PARAMS is the list of argument values. */
+
+static void
+check_format_info (function_format_info *info, tree params,
+ vec<location_t> *arglocs)
+{
+ format_check_context format_ctx;
+ unsigned HOST_WIDE_INT arg_num;
+ tree format_tree;
+ format_check_results res;
+ /* Skip to format argument. If the argument isn't available, there's
+ no work for us to do; prototype checking will catch the problem. */
+ for (arg_num = 1; ; ++arg_num)
+ {
+ if (params == 0)
+ return;
+ if (arg_num == info->format_num)
+ break;
+ params = TREE_CHAIN (params);
+ }
+ format_tree = TREE_VALUE (params);
+ params = TREE_CHAIN (params);
+ if (format_tree == 0)
+ return;
+
+ res.number_non_literal = 0;
+ res.number_extra_args = 0;
+ res.extra_arg_loc = UNKNOWN_LOCATION;
+ res.number_dollar_extra_args = 0;
+ res.number_wide = 0;
+ res.number_non_char = 0;
+ res.number_empty = 0;
+ res.number_unterminated = 0;
+ res.number_other = 0;
+ res.format_string_loc = input_location;
+
+ format_ctx.res = &res;
+ format_ctx.info = info;
+ format_ctx.params = params;
+ format_ctx.arglocs = arglocs;
+
+ check_function_arguments_recurse (check_format_arg, &format_ctx,
+ format_tree, arg_num, OPT_Wformat_);
+
+ location_t loc = format_ctx.res->format_string_loc;
+
+ if (res.number_non_literal > 0)
+ {
+ /* Functions taking a va_list normally pass a non-literal format
+ string. These functions typically are declared with
+ first_arg_num == 0, so avoid warning in those cases. */
+ if (!(format_types[info->format_type].flags & (int) FMT_FLAG_ARG_CONVERT))
+ {
+ /* For strftime-like formats, warn for not checking the format
+ string; but there are no arguments to check. */
+ warning_at (loc, OPT_Wformat_nonliteral,
+ "format not a string literal, format string not checked");
+ }
+ else if (info->first_arg_num != 0)
+ {
+ /* If there are no arguments for the format at all, we may have
+ printf (foo) which is likely to be a security hole. */
+ while (arg_num + 1 < info->first_arg_num)
+ {
+ if (params == 0)
+ break;
+ params = TREE_CHAIN (params);
+ ++arg_num;
+ }
+ if (params == 0 && warn_format_security)
+ warning_at (loc, OPT_Wformat_security,
+ "format not a string literal and no format arguments");
+ else if (params == 0 && warn_format_nonliteral)
+ warning_at (loc, OPT_Wformat_nonliteral,
+ "format not a string literal and no format arguments");
+ else
+ warning_at (loc, OPT_Wformat_nonliteral,
+ "format not a string literal, argument types not checked");
+ }
+ }
+
+ /* If there were extra arguments to the format, normally warn. However,
+ the standard does say extra arguments are ignored, so in the specific
+ case where we have multiple leaves (conditional expressions or
+ ngettext) allow extra arguments if at least one leaf didn't have extra
+ arguments, but was otherwise OK (either non-literal or checked OK).
+ If the format is an empty string, this should be counted similarly to the
+ case of extra format arguments. */
+ if (res.number_extra_args > 0 && res.number_non_literal == 0
+ && res.number_other == 0)
+ {
+ if (res.extra_arg_loc == UNKNOWN_LOCATION)
+ res.extra_arg_loc = loc;
+ warning_at (res.extra_arg_loc, OPT_Wformat_extra_args,
+ "too many arguments for format");
+ }
+ if (res.number_dollar_extra_args > 0 && res.number_non_literal == 0
+ && res.number_other == 0)
+ warning_at (loc, OPT_Wformat_extra_args, "unused arguments in %<$%>-style format");
+ if (res.number_empty > 0 && res.number_non_literal == 0
+ && res.number_other == 0)
+ warning_at (loc, OPT_Wformat_zero_length, "zero-length %s format string",
+ format_types[info->format_type].name);
+
+ if (res.number_wide > 0)
+ warning_at (loc, OPT_Wformat_, "format is a wide character string");
+
+ if (res.number_non_char > 0)
+ warning_at (loc, OPT_Wformat_,
+ "format string is not an array of type %qs", "char");
+
+ if (res.number_unterminated > 0)
+ warning_at (loc, OPT_Wformat_, "unterminated format string");
+}
+
+/* Callback from check_function_arguments_recurse to check a
+ format string. FORMAT_TREE is the format parameter. ARG_NUM
+ is the number of the format argument. CTX points to a
+ format_check_context. */
+
+static void
+check_format_arg (void *ctx, tree format_tree,
+ unsigned HOST_WIDE_INT arg_num)
+{
+ format_check_context *format_ctx = (format_check_context *) ctx;
+ format_check_results *res = format_ctx->res;
+ function_format_info *info = format_ctx->info;
+ tree params = format_ctx->params;
+ vec<location_t> *arglocs = format_ctx->arglocs;
+
+ int format_length;
+ HOST_WIDE_INT offset;
+ const char *format_chars;
+ tree array_size = 0;
+ tree array_init;
+
+ location_t fmt_param_loc = EXPR_LOC_OR_LOC (format_tree, input_location);
+
+ /* Pull out a constant value if the front end didn't, and handle location
+ wrappers. */
+ format_tree = fold_for_warn (format_tree);
+ STRIP_NOPS (format_tree);
+
+ if (integer_zerop (format_tree))
+ {
+ /* Skip to first argument to check, so we can see if this format
+ has any arguments (it shouldn't). */
+ while (arg_num + 1 < info->first_arg_num)
+ {
+ if (params == 0)
+ return;
+ params = TREE_CHAIN (params);
+ ++arg_num;
+ }
+
+ if (params == 0)
+ res->number_other++;
+ else
+ {
+ if (res->number_extra_args == 0)
+ res->extra_arg_loc = EXPR_LOC_OR_LOC (TREE_VALUE (params),
+ input_location);
+ res->number_extra_args++;
+ }
+ return;
+ }
+
+ offset = 0;
+ if (TREE_CODE (format_tree) == POINTER_PLUS_EXPR)
+ {
+ tree arg0, arg1;
+
+ arg0 = TREE_OPERAND (format_tree, 0);
+ arg1 = TREE_OPERAND (format_tree, 1);
+ STRIP_NOPS (arg0);
+ STRIP_NOPS (arg1);
+ if (TREE_CODE (arg1) == INTEGER_CST)
+ format_tree = arg0;
+ else
+ {
+ res->number_non_literal++;
+ return;
+ }
+ /* POINTER_PLUS_EXPR offsets are to be interpreted signed. */
+ if (!cst_and_fits_in_hwi (arg1))
+ {
+ res->number_non_literal++;
+ return;
+ }
+ offset = int_cst_value (arg1);
+ }
+ if (TREE_CODE (format_tree) != ADDR_EXPR)
+ {
+ res->number_non_literal++;
+ return;
+ }
+ res->format_string_loc = EXPR_LOC_OR_LOC (format_tree, input_location);
+ format_tree = TREE_OPERAND (format_tree, 0);
+ if (format_types[info->format_type].flags
+ & (int) FMT_FLAG_PARSE_ARG_CONVERT_EXTERNAL)
+ {
+ /* We cannot examine this string here - but we can check that it is
+ a valid type. */
+ if (TREE_CODE (format_tree) != CONST_DECL)
+ {
+ res->number_non_literal++;
+ return;
+ }
+ /* Skip to first argument to check. */
+ while (arg_num + 1 < info->first_arg_num)
+ {
+ if (params == 0)
+ return;
+ params = TREE_CHAIN (params);
+ ++arg_num;
+ }
+ return;
+ }
+ if (TREE_CODE (format_tree) == ARRAY_REF
+ && tree_fits_shwi_p (TREE_OPERAND (format_tree, 1))
+ && (offset += tree_to_shwi (TREE_OPERAND (format_tree, 1))) >= 0)
+ format_tree = TREE_OPERAND (format_tree, 0);
+ if (offset < 0)
+ {
+ res->number_non_literal++;
+ return;
+ }
+ if (VAR_P (format_tree)
+ && TREE_CODE (TREE_TYPE (format_tree)) == ARRAY_TYPE
+ && (array_init = decl_constant_value (format_tree)) != format_tree
+ && TREE_CODE (array_init) == STRING_CST)
+ {
+ /* Extract the string constant initializer. Note that this may include
+ a trailing NUL character that is not in the array (e.g.
+ const char a[3] = "foo";). */
+ array_size = DECL_SIZE_UNIT (format_tree);
+ format_tree = array_init;
+ }
+ if (TREE_CODE (format_tree) != STRING_CST)
+ {
+ res->number_non_literal++;
+ return;
+ }
+ tree underlying_type
+ = TYPE_MAIN_VARIANT (TREE_TYPE (TREE_TYPE (format_tree)));
+ if (underlying_type != char_type_node)
+ {
+ if (underlying_type == char16_type_node
+ || underlying_type == char32_type_node
+ || underlying_type == wchar_type_node)
+ res->number_wide++;
+ else
+ res->number_non_char++;
+ return;
+ }
+ format_chars = TREE_STRING_POINTER (format_tree);
+ format_length = TREE_STRING_LENGTH (format_tree);
+ if (array_size != 0)
+ {
+ /* Variable length arrays can't be initialized. */
+ gcc_assert (TREE_CODE (array_size) == INTEGER_CST);
+
+ if (tree_fits_shwi_p (array_size))
+ {
+ HOST_WIDE_INT array_size_value = tree_to_shwi (array_size);
+ if (array_size_value > 0
+ && array_size_value == (int) array_size_value
+ && format_length > array_size_value)
+ format_length = array_size_value;
+ }
+ }
+ if (offset)
+ {
+ if (offset >= format_length)
+ {
+ res->number_non_literal++;
+ return;
+ }
+ format_chars += offset;
+ format_length -= offset;
+ }
+ if (format_length < 1 || format_chars[--format_length] != 0)
+ {
+ res->number_unterminated++;
+ return;
+ }
+ if (format_length == 0)
+ {
+ res->number_empty++;
+ return;
+ }
+
+ /* Skip to first argument to check. */
+ while (arg_num + 1 < info->first_arg_num)
+ {
+ if (params == 0)
+ return;
+ params = TREE_CHAIN (params);
+ ++arg_num;
+ }
+ /* Provisionally increment res->number_other; check_format_info_main
+ will decrement it if it finds there are extra arguments, but this way
+ need not adjust it for every return. */
+ res->number_other++;
+ object_allocator <format_wanted_type> fwt_pool ("format_wanted_type pool");
+ check_format_info_main (res, info, format_chars, fmt_param_loc, format_tree,
+ format_length, params, arg_num, fwt_pool, arglocs);
+}
+
+/* Support class for argument_parser and check_format_info_main.
+ Tracks any flag characters that have been applied to the
+ current argument. */
+
+class flag_chars_t
+{
+ public:
+ flag_chars_t ();
+ bool has_char_p (char ch) const;
+ void add_char (char ch);
+ void validate (const format_kind_info *fki,
+ const format_char_info *fci,
+ const format_flag_spec *flag_specs,
+ const char * const format_chars,
+ tree format_string_cst,
+ location_t format_string_loc,
+ const char * const orig_format_chars,
+ char format_char,
+ bool quoted);
+ int get_alloc_flag (const format_kind_info *fki);
+ int assignment_suppression_p (const format_kind_info *fki);
+
+ private:
+ char m_flag_chars[256];
+};
+
+/* Support struct for argument_parser and check_format_info_main.
+ Encapsulates any length modifier applied to the current argument. */
+
+struct length_modifier
+{
+ length_modifier ()
+ : chars (NULL), val (FMT_LEN_none), std (STD_C89),
+ scalar_identity_flag (0)
+ {
+ }
+
+ length_modifier (const char *chars_,
+ enum format_lengths val_,
+ enum format_std_version std_,
+ int scalar_identity_flag_)
+ : chars (chars_), val (val_), std (std_),
+ scalar_identity_flag (scalar_identity_flag_)
+ {
+ }
+
+ const char *chars;
+ enum format_lengths val;
+ enum format_std_version std;
+ int scalar_identity_flag;
+};
+
+/* Parsing one argument within a format string. */
+
+class argument_parser
+{
+ public:
+ argument_parser (function_format_info *info, const char *&format_chars,
+ tree format_string_cst,
+ const char * const orig_format_chars,
+ location_t format_string_loc, flag_chars_t &flag_chars,
+ int &has_operand_number, tree first_fillin_param,
+ object_allocator <format_wanted_type> &fwt_pool_,
+ vec<location_t> *arglocs);
+
+ bool read_any_dollar ();
+
+ bool read_format_flags ();
+
+ bool
+ read_any_format_width (tree &params,
+ unsigned HOST_WIDE_INT &arg_num);
+
+ void
+ read_any_format_left_precision ();
+
+ bool
+ read_any_format_precision (tree &params,
+ unsigned HOST_WIDE_INT &arg_num);
+
+ void handle_alloc_chars ();
+
+ length_modifier read_any_length_modifier ();
+
+ void read_any_other_modifier ();
+
+ const format_char_info *find_format_char_info (char format_char);
+
+ void
+ validate_flag_pairs (const format_char_info *fci,
+ char format_char);
+
+ void
+ give_y2k_warnings (const format_char_info *fci,
+ char format_char);
+
+ void parse_any_scan_set (const format_char_info *fci);
+
+ bool handle_conversions (const format_char_info *fci,
+ const length_modifier &len_modifier,
+ tree &wanted_type,
+ const char *&wanted_type_name,
+ unsigned HOST_WIDE_INT &arg_num,
+ tree &params,
+ char format_char);
+
+ bool
+ check_argument_type (const format_char_info *fci,
+ const struct kernel_ext_fmt *kef,
+ const length_modifier &len_modifier,
+ tree &wanted_type,
+ const char *&wanted_type_name,
+ const bool suppressed,
+ unsigned HOST_WIDE_INT &arg_num,
+ tree &params,
+ const int alloc_flag,
+ const char * const format_start,
+ const char * const type_start,
+ location_t fmt_param_loc,
+ char conversion_char);
+
+ private:
+ const function_format_info *const info;
+ const format_kind_info * const fki;
+ const format_flag_spec * const flag_specs;
+ const char *start_of_this_format;
+ const char *&format_chars;
+ const tree format_string_cst;
+ const char * const orig_format_chars;
+ const location_t format_string_loc;
+ object_allocator <format_wanted_type> &fwt_pool;
+ flag_chars_t &flag_chars;
+ int main_arg_num;
+ tree main_arg_params;
+ int &has_operand_number;
+ const tree first_fillin_param;
+ format_wanted_type width_wanted_type;
+ format_wanted_type precision_wanted_type;
+ public:
+ format_wanted_type main_wanted_type;
+ private:
+ format_wanted_type *first_wanted_type;
+ format_wanted_type *last_wanted_type;
+ vec<location_t> *arglocs;
+};
+
+/* flag_chars_t's constructor. */
+
+flag_chars_t::flag_chars_t ()
+{
+ m_flag_chars[0] = 0;
+}
+
+/* Has CH been seen as a flag within the current argument? */
+
+bool
+flag_chars_t::has_char_p (char ch) const
+{
+ return strchr (m_flag_chars, ch) != 0;
+}
+
+/* Add CH to the flags seen within the current argument. */
+
+void
+flag_chars_t::add_char (char ch)
+{
+ int i = strlen (m_flag_chars);
+ m_flag_chars[i++] = ch;
+ m_flag_chars[i] = 0;
+}
+
+/* Validate the individual flags used, removing any that are invalid. */
+
+void
+flag_chars_t::validate (const format_kind_info *fki,
+ const format_char_info *fci,
+ const format_flag_spec *flag_specs,
+ const char * const format_chars,
+ tree format_string_cst,
+ location_t format_string_loc,
+ const char * const orig_format_chars,
+ char format_char,
+ bool quoted)
+{
+ int i;
+ int d = 0;
+ bool quotflag = false;
+
+ for (i = 0; m_flag_chars[i] != 0; i++)
+ {
+ const format_flag_spec *s = get_flag_spec (flag_specs,
+ m_flag_chars[i], NULL);
+ m_flag_chars[i - d] = m_flag_chars[i];
+ if (m_flag_chars[i] == fki->length_code_char)
+ continue;
+
+ /* Remember if a quoting flag is seen. */
+ quotflag |= s->quoting;
+
+ if (strchr (fci->flag_chars, m_flag_chars[i]) == 0)
+ {
+ format_warning_at_char (format_string_loc, format_string_cst,
+ format_chars - orig_format_chars,
+ OPT_Wformat_,
+ "%s used with %<%%%c%> %s format",
+ _(s->name), format_char, fki->name);
+ d++;
+ continue;
+ }
+ if (pedantic)
+ {
+ const format_flag_spec *t;
+ if (ADJ_STD (s->std) > C_STD_VER)
+ warning_at (format_string_loc, OPT_Wformat_,
+ "%s does not support %s",
+ C_STD_NAME (s->std), _(s->long_name));
+ t = get_flag_spec (flag_specs, m_flag_chars[i], fci->flags2);
+ if (t != NULL && ADJ_STD (t->std) > ADJ_STD (s->std))
+ {
+ const char *long_name = (t->long_name != NULL
+ ? t->long_name
+ : s->long_name);
+ if (ADJ_STD (t->std) > C_STD_VER)
+ warning_at (format_string_loc, OPT_Wformat_,
+ "%s does not support %s with the %<%%%c%> %s format",
+ C_STD_NAME (t->std), _(long_name),
+ format_char, fki->name);
+ }
+ }
+
+ /* Detect quoting directives used within a quoted sequence, such
+ as GCC's "%<...%qE". */
+ if (quoted && s->quoting)
+ {
+ format_warning_at_char (format_string_loc, format_string_cst,
+ format_chars - orig_format_chars - 1,
+ OPT_Wformat_,
+ "%s used within a quoted sequence",
+ _(s->name));
+ }
+ }
+ m_flag_chars[i - d] = 0;
+
+ if (!quoted
+ && !quotflag
+ && strchr (fci->flags2, '\''))
+ {
+ format_warning_at_char (format_string_loc, format_string_cst,
+ format_chars - orig_format_chars,
+ OPT_Wformat_,
+ "%qc conversion used unquoted",
+ format_char);
+ }
+}
+
+/* Determine if an assignment-allocation has been set, requiring
+ an extra char ** for writing back a dynamically-allocated char *.
+ This is for handling the optional 'm' character in scanf. */
+
+int
+flag_chars_t::get_alloc_flag (const format_kind_info *fki)
+{
+ if ((fki->flags & (int) FMT_FLAG_SCANF_A_KLUDGE)
+ && has_char_p ('a'))
+ return 1;
+ if (fki->alloc_char && has_char_p (fki->alloc_char))
+ return 1;
+ return 0;
+}
+
+/* Determine if an assignment-suppression character was seen.
+ ('*' in scanf, for discarding the converted input). */
+
+int
+flag_chars_t::assignment_suppression_p (const format_kind_info *fki)
+{
+ if (fki->suppression_char
+ && has_char_p (fki->suppression_char))
+ return 1;
+ return 0;
+}
+
+/* Constructor for argument_parser. Initialize for parsing one
+ argument within a format string. */
+
+argument_parser::
+argument_parser (function_format_info *info_, const char *&format_chars_,
+ tree format_string_cst_,
+ const char * const orig_format_chars_,
+ location_t format_string_loc_,
+ flag_chars_t &flag_chars_,
+ int &has_operand_number_,
+ tree first_fillin_param_,
+ object_allocator <format_wanted_type> &fwt_pool_,
+ vec<location_t> *arglocs_)
+: info (info_),
+ fki (&format_types[info->format_type]),
+ flag_specs (fki->flag_specs),
+ start_of_this_format (format_chars_),
+ format_chars (format_chars_),
+ format_string_cst (format_string_cst_),
+ orig_format_chars (orig_format_chars_),
+ format_string_loc (format_string_loc_),
+ fwt_pool (fwt_pool_),
+ flag_chars (flag_chars_),
+ main_arg_num (0),
+ main_arg_params (NULL),
+ has_operand_number (has_operand_number_),
+ first_fillin_param (first_fillin_param_),
+ first_wanted_type (NULL),
+ last_wanted_type (NULL),
+ arglocs (arglocs_)
+{
+}
+
+/* Handle dollars at the start of format arguments, setting up main_arg_params
+ and main_arg_num.
+
+ Return true if format parsing is to continue, false otherwise. */
+
+bool
+argument_parser::read_any_dollar ()
+{
+ if ((fki->flags & (int) FMT_FLAG_USE_DOLLAR) && has_operand_number != 0)
+ {
+ /* Possibly read a $ operand number at the start of the format.
+ If one was previously used, one is required here. If one
+ is not used here, we can't immediately conclude this is a
+ format without them, since it could be printf %m or scanf %*. */
+ int opnum;
+ opnum = maybe_read_dollar_number (&format_chars, 0,
+ first_fillin_param,
+ &main_arg_params, fki);
+ if (opnum == -1)
+ return false;
+ else if (opnum > 0)
+ {
+ has_operand_number = 1;
+ main_arg_num = opnum + info->first_arg_num - 1;
+ }
+ }
+ else if (fki->flags & FMT_FLAG_USE_DOLLAR)
+ {
+ if (avoid_dollar_number (format_chars))
+ return false;
+ }
+ return true;
+}
+
+/* Read any format flags, but do not yet validate them beyond removing
+ duplicates, since in general validation depends on the rest of
+ the format.
+
+ Return true if format parsing is to continue, false otherwise. */
+
+bool
+argument_parser::read_format_flags ()
+{
+ while (*format_chars != 0
+ && strchr (fki->flag_chars, *format_chars) != 0)
+ {
+ const format_flag_spec *s = get_flag_spec (flag_specs,
+ *format_chars, NULL);
+ if (flag_chars.has_char_p (*format_chars))
+ {
+ format_warning_at_char (format_string_loc, format_string_cst,
+ format_chars + 1 - orig_format_chars,
+ OPT_Wformat_,
+ "repeated %s in format", _(s->name));
+ }
+ else
+ flag_chars.add_char (*format_chars);
+
+ if (s->skip_next_char)
+ {
+ ++format_chars;
+ if (*format_chars == 0)
+ {
+ warning_at (format_string_loc, OPT_Wformat_,
+ "missing fill character at end of strfmon format");
+ return false;
+ }
+ }
+ ++format_chars;
+ }
+
+ return true;
+}
+
+/* Read any format width, possibly * or *m$.
+
+ Return true if format parsing is to continue, false otherwise. */
+
+bool
+argument_parser::
+read_any_format_width (tree &params,
+ unsigned HOST_WIDE_INT &arg_num)
+{
+ if (!fki->width_char)
+ return true;
+
+ if (fki->width_type != NULL && *format_chars == '*')
+ {
+ flag_chars.add_char (fki->width_char);
+ /* "...a field width...may be indicated by an asterisk.
+ In this case, an int argument supplies the field width..." */
+ ++format_chars;
+ if (has_operand_number != 0)
+ {
+ int opnum;
+ opnum = maybe_read_dollar_number (&format_chars,
+ has_operand_number == 1,
+ first_fillin_param,
+ &params, fki);
+ if (opnum == -1)
+ return false;
+ else if (opnum > 0)
+ {
+ has_operand_number = 1;
+ arg_num = opnum + info->first_arg_num - 1;
+ }
+ else
+ has_operand_number = 0;
+ }
+ else
+ {
+ if (avoid_dollar_number (format_chars))
+ return false;
+ }
+ if (info->first_arg_num != 0)
+ {
+ tree cur_param;
+ if (params == 0)
+ cur_param = NULL;
+ else
+ {
+ cur_param = TREE_VALUE (params);
+ if (has_operand_number <= 0)
+ {
+ params = TREE_CHAIN (params);
+ ++arg_num;
+ }
+ }
+ width_wanted_type.wanted_type = *fki->width_type;
+ width_wanted_type.wanted_type_name = NULL;
+ width_wanted_type.pointer_count = 0;
+ width_wanted_type.char_lenient_flag = 0;
+ width_wanted_type.scalar_identity_flag = 0;
+ width_wanted_type.writing_in_flag = 0;
+ width_wanted_type.reading_from_flag = 0;
+ width_wanted_type.kind = CF_KIND_FIELD_WIDTH;
+ width_wanted_type.format_start = format_chars - 1;
+ width_wanted_type.format_length = 1;
+ width_wanted_type.param = cur_param;
+ width_wanted_type.arg_num = arg_num;
+ width_wanted_type.offset_loc =
+ format_chars - orig_format_chars;
+ width_wanted_type.next = NULL;
+ if (last_wanted_type != 0)
+ last_wanted_type->next = &width_wanted_type;
+ if (first_wanted_type == 0)
+ first_wanted_type = &width_wanted_type;
+ last_wanted_type = &width_wanted_type;
+ }
+ }
+ else
+ {
+ /* Possibly read a numeric width. If the width is zero,
+ we complain if appropriate. */
+ int non_zero_width_char = FALSE;
+ int found_width = FALSE;
+ while (ISDIGIT (*format_chars))
+ {
+ found_width = TRUE;
+ if (*format_chars != '0')
+ non_zero_width_char = TRUE;
+ ++format_chars;
+ }
+ if (found_width && !non_zero_width_char &&
+ (fki->flags & (int) FMT_FLAG_ZERO_WIDTH_BAD))
+ warning_at (format_string_loc, OPT_Wformat_,
+ "zero width in %s format", fki->name);
+ if (found_width)
+ flag_chars.add_char (fki->width_char);
+ }
+
+ return true;
+}
+
+/* Read any format left precision (must be a number, not *). */
+void
+argument_parser::read_any_format_left_precision ()
+{
+ if (fki->left_precision_char == 0)
+ return;
+ if (*format_chars != '#')
+ return;
+
+ ++format_chars;
+ flag_chars.add_char (fki->left_precision_char);
+ if (!ISDIGIT (*format_chars))
+ format_warning_at_char (format_string_loc, format_string_cst,
+ format_chars - orig_format_chars,
+ OPT_Wformat_,
+ "empty left precision in %s format", fki->name);
+ while (ISDIGIT (*format_chars))
+ ++format_chars;
+}
+
+/* Read any format precision, possibly * or *m$.
+
+ Return true if format parsing is to continue, false otherwise. */
+
+bool
+argument_parser::
+read_any_format_precision (tree &params,
+ unsigned HOST_WIDE_INT &arg_num)
+{
+ if (fki->precision_char == 0)
+ return true;
+ if (*format_chars != '.')
+ return true;
+
+ ++format_chars;
+ flag_chars.add_char (fki->precision_char);
+ if (fki->precision_type != NULL && *format_chars == '*')
+ {
+ /* "...a...precision...may be indicated by an asterisk.
+ In this case, an int argument supplies the...precision." */
+ ++format_chars;
+ if (has_operand_number != 0)
+ {
+ int opnum;
+ opnum = maybe_read_dollar_number (&format_chars,
+ has_operand_number == 1,
+ first_fillin_param,
+ &params, fki);
+ if (opnum == -1)
+ return false;
+ else if (opnum > 0)
+ {
+ has_operand_number = 1;
+ arg_num = opnum + info->first_arg_num - 1;
+ }
+ else
+ has_operand_number = 0;
+ }
+ else
+ {
+ if (avoid_dollar_number (format_chars))
+ return false;
+ }
+ if (info->first_arg_num != 0)
+ {
+ tree cur_param;
+ if (params == 0)
+ cur_param = NULL;
+ else
+ {
+ cur_param = TREE_VALUE (params);
+ if (has_operand_number <= 0)
+ {
+ params = TREE_CHAIN (params);
+ ++arg_num;
+ }
+ }
+ precision_wanted_type.wanted_type = *fki->precision_type;
+ precision_wanted_type.wanted_type_name = NULL;
+ precision_wanted_type.pointer_count = 0;
+ precision_wanted_type.char_lenient_flag = 0;
+ precision_wanted_type.scalar_identity_flag = 0;
+ precision_wanted_type.writing_in_flag = 0;
+ precision_wanted_type.reading_from_flag = 0;
+ precision_wanted_type.kind = CF_KIND_FIELD_PRECISION;
+ precision_wanted_type.param = cur_param;
+ precision_wanted_type.format_start = format_chars - 2;
+ precision_wanted_type.format_length = 2;
+ precision_wanted_type.arg_num = arg_num;
+ precision_wanted_type.offset_loc =
+ format_chars - orig_format_chars;
+ precision_wanted_type.next = NULL;
+ if (last_wanted_type != 0)
+ last_wanted_type->next = &precision_wanted_type;
+ if (first_wanted_type == 0)
+ first_wanted_type = &precision_wanted_type;
+ last_wanted_type = &precision_wanted_type;
+ }
+ }
+ else
+ {
+ if (!(fki->flags & (int) FMT_FLAG_EMPTY_PREC_OK)
+ && !ISDIGIT (*format_chars))
+ format_warning_at_char (format_string_loc, format_string_cst,
+ format_chars - orig_format_chars,
+ OPT_Wformat_,
+ "empty precision in %s format", fki->name);
+ while (ISDIGIT (*format_chars))
+ ++format_chars;
+ }
+
+ return true;
+}
+
+/* Parse any assignment-allocation flags, which request an extra
+ char ** for writing back a dynamically-allocated char *.
+ This is for handling the optional 'm' character in scanf,
+ and, before C99, 'a' (for compatibility with a non-standard
+ GNU libc extension). */
+
+void
+argument_parser::handle_alloc_chars ()
+{
+ if (fki->alloc_char && fki->alloc_char == *format_chars)
+ {
+ flag_chars.add_char (fki->alloc_char);
+ format_chars++;
+ }
+
+ /* Handle the scanf allocation kludge. */
+ if (fki->flags & (int) FMT_FLAG_SCANF_A_KLUDGE)
+ {
+ if (*format_chars == 'a' && !flag_isoc99)
+ {
+ if (format_chars[1] == 's' || format_chars[1] == 'S'
+ || format_chars[1] == '[')
+ {
+ /* 'a' is used as a flag. */
+ flag_chars.add_char ('a');
+ format_chars++;
+ }
+ }
+ }
+}
+
+/* Look for length modifiers within the current format argument,
+ returning a length_modifier instance describing it (or the
+ default if one is not found).
+
+ Issue warnings about non-standard modifiers. */
+
+length_modifier
+argument_parser::read_any_length_modifier ()
+{
+ length_modifier result;
+
+ const format_length_info *fli = fki->length_char_specs;
+ if (!fli)
+ return result;
+
+ while (fli->name != 0
+ && strncmp (fli->name, format_chars, strlen (fli->name)))
+ fli++;
+ if (fli->name != 0)
+ {
+ format_chars += strlen (fli->name);
+ if (fli->double_name != 0 && fli->name[0] == *format_chars)
+ {
+ format_chars++;
+ result = length_modifier (fli->double_name, fli->double_index,
+ fli->double_std, 0);
+ }
+ else
+ {
+ result = length_modifier (fli->name, fli->index, fli->std,
+ fli->scalar_identity_flag);
+ }
+ flag_chars.add_char (fki->length_code_char);
+ }
+ if (pedantic)
+ {
+ /* Warn if the length modifier is non-standard. */
+ if (ADJ_STD (result.std) > C_STD_VER)
+ warning_at (format_string_loc, OPT_Wformat_,
+ "%s does not support the %qs %s length modifier",
+ C_STD_NAME (result.std), result.chars,
+ fki->name);
+ }
+
+ return result;
+}
+
+/* Read any other modifier (strftime E/O). */
+
+void
+argument_parser::read_any_other_modifier ()
+{
+ if (fki->modifier_chars == NULL)
+ return;
+
+ while (*format_chars != 0
+ && strchr (fki->modifier_chars, *format_chars) != 0)
+ {
+ if (flag_chars.has_char_p (*format_chars))
+ {
+ const format_flag_spec *s = get_flag_spec (flag_specs,
+ *format_chars, NULL);
+ format_warning_at_char (format_string_loc, format_string_cst,
+ format_chars - orig_format_chars,
+ OPT_Wformat_,
+ "repeated %s in format", _(s->name));
+ }
+ else
+ flag_chars.add_char (*format_chars);
+ ++format_chars;
+ }
+}
+
+/* Return the format_char_info corresponding to FORMAT_CHAR,
+ potentially issuing a warning if the format char is
+ not supported in the C standard version we are checking
+ against.
+
+ Issue a warning and return NULL if it is not found.
+
+ Issue warnings about non-standard modifiers. */
+
+const format_char_info *
+argument_parser::find_format_char_info (char format_char)
+{
+ const format_char_info *fci = fki->conversion_specs;
+
+ while (fci->format_chars != 0
+ && strchr (fci->format_chars, format_char) == 0)
+ ++fci;
+ if (fci->format_chars == 0)
+ {
+ format_warning_at_char (format_string_loc, format_string_cst,
+ format_chars - orig_format_chars,
+ OPT_Wformat_,
+ "unknown conversion type character %qc in format",
+ format_char);
+ return NULL;
+ }
+
+ if (pedantic)
+ {
+ if (ADJ_STD (fci->std) > C_STD_VER)
+ format_warning_at_char (format_string_loc, format_string_cst,
+ format_chars - orig_format_chars,
+ OPT_Wformat_,
+ "%s does not support the %<%%%c%> %s format",
+ C_STD_NAME (fci->std), format_char, fki->name);
+ }
+
+ return fci;
+}
+
+/* Validate the pairs of flags used.
+ Issue warnings about incompatible combinations of flags. */
+
+void
+argument_parser::validate_flag_pairs (const format_char_info *fci,
+ char format_char)
+{
+ const format_flag_pair * const bad_flag_pairs = fki->bad_flag_pairs;
+
+ for (int i = 0; bad_flag_pairs[i].flag_char1 != 0; i++)
+ {
+ const format_flag_spec *s, *t;
+ if (!flag_chars.has_char_p (bad_flag_pairs[i].flag_char1))
+ continue;
+ if (!flag_chars.has_char_p (bad_flag_pairs[i].flag_char2))
+ continue;
+ if (bad_flag_pairs[i].predicate != 0
+ && strchr (fci->flags2, bad_flag_pairs[i].predicate) == 0)
+ continue;
+ s = get_flag_spec (flag_specs, bad_flag_pairs[i].flag_char1, NULL);
+ t = get_flag_spec (flag_specs, bad_flag_pairs[i].flag_char2, NULL);
+ if (bad_flag_pairs[i].ignored)
+ {
+ if (bad_flag_pairs[i].predicate != 0)
+ warning_at (format_string_loc, OPT_Wformat_,
+ "%s ignored with %s and %<%%%c%> %s format",
+ _(s->name), _(t->name), format_char,
+ fki->name);
+ else
+ warning_at (format_string_loc, OPT_Wformat_,
+ "%s ignored with %s in %s format",
+ _(s->name), _(t->name), fki->name);
+ }
+ else
+ {
+ if (bad_flag_pairs[i].predicate != 0)
+ warning_at (format_string_loc, OPT_Wformat_,
+ "use of %s and %s together with %<%%%c%> %s format",
+ _(s->name), _(t->name), format_char,
+ fki->name);
+ else
+ warning_at (format_string_loc, OPT_Wformat_,
+ "use of %s and %s together in %s format",
+ _(s->name), _(t->name), fki->name);
+ }
+ }
+}
+
+/* Give Y2K warnings. */
+
+void
+argument_parser::give_y2k_warnings (const format_char_info *fci,
+ char format_char)
+{
+ if (!warn_format_y2k)
+ return;
+
+ int y2k_level = 0;
+ if (strchr (fci->flags2, '4') != 0)
+ if (flag_chars.has_char_p ('E'))
+ y2k_level = 3;
+ else
+ y2k_level = 2;
+ else if (strchr (fci->flags2, '3') != 0)
+ y2k_level = 3;
+ else if (strchr (fci->flags2, '2') != 0)
+ y2k_level = 2;
+ if (y2k_level == 3)
+ warning_at (format_string_loc, OPT_Wformat_y2k,
+ "%<%%%c%> yields only last 2 digits of year in some locales", format_char);
+ else if (y2k_level == 2)
+ warning_at (format_string_loc, OPT_Wformat_y2k,
+ "%<%%%c%> yields only last 2 digits of year",
+ format_char);
+}
+
+/* Parse any "scan sets" enclosed in square brackets, e.g.
+ for scanf-style calls. */
+
+void
+argument_parser::parse_any_scan_set (const format_char_info *fci)
+{
+ if (strchr (fci->flags2, '[') == NULL)
+ return;
+
+ /* Skip over scan set, in case it happens to have '%' in it. */
+ if (*format_chars == '^')
+ ++format_chars;
+ /* Find closing bracket; if one is hit immediately, then
+ it's part of the scan set rather than a terminator. */
+ if (*format_chars == ']')
+ ++format_chars;
+ while (*format_chars && *format_chars != ']')
+ ++format_chars;
+ if (*format_chars != ']')
+ /* The end of the format string was reached. */
+ format_warning_at_char (format_string_loc, format_string_cst,
+ format_chars - orig_format_chars,
+ OPT_Wformat_,
+ "no closing %<]%> for %<%%[%> format");
+}
+
+/* Return true if this argument is to be continued to be parsed,
+ false to skip to next argument. */
+
+bool
+argument_parser::handle_conversions (const format_char_info *fci,
+ const length_modifier &len_modifier,
+ tree &wanted_type,
+ const char *&wanted_type_name,
+ unsigned HOST_WIDE_INT &arg_num,
+ tree &params,
+ char format_char)
+{
+ enum format_std_version wanted_type_std;
+
+ if (!(fki->flags & (int) FMT_FLAG_ARG_CONVERT))
+ return true;
+
+ wanted_type = (fci->types[len_modifier.val].type
+ ? *fci->types[len_modifier.val].type : 0);
+ wanted_type_name = fci->types[len_modifier.val].name;
+ wanted_type_std = fci->types[len_modifier.val].std;
+ if (wanted_type == 0)
+ {
+ format_warning_at_char (format_string_loc, format_string_cst,
+ format_chars - orig_format_chars,
+ OPT_Wformat_,
+ "use of %qs length modifier with %qc type character has either no effect or undefined behavior",
+ len_modifier.chars, format_char);
+ /* Heuristic: skip one argument when an invalid length/type
+ combination is encountered. */
+ arg_num++;
+ if (params != 0)
+ params = TREE_CHAIN (params);
+ return false;
+ }
+ else if (pedantic
+ /* Warn if non-standard, provided it is more non-standard
+ than the length and type characters that may already
+ have been warned for. */
+ && ADJ_STD (wanted_type_std) > ADJ_STD (len_modifier.std)
+ && ADJ_STD (wanted_type_std) > ADJ_STD (fci->std))
+ {
+ if (ADJ_STD (wanted_type_std) > C_STD_VER)
+ format_warning_at_char (format_string_loc, format_string_cst,
+ format_chars - orig_format_chars,
+ OPT_Wformat_,
+ "%s does not support the %<%%%s%c%> %s format",
+ C_STD_NAME (wanted_type_std),
+ len_modifier.chars,
+ format_char, fki->name);
+ }
+
+ return true;
+}
+
+/* Check type of argument against desired type.
+
+ Return true if format parsing is to continue, false otherwise. */
+
+bool
+argument_parser::
+check_argument_type (const format_char_info *fci,
+ const struct kernel_ext_fmt *kef,
+ const length_modifier &len_modifier,
+ tree &wanted_type,
+ const char *&wanted_type_name,
+ const bool suppressed,
+ unsigned HOST_WIDE_INT &arg_num,
+ tree &params,
+ const int alloc_flag,
+ const char * const format_start,
+ const char * const type_start,
+ location_t fmt_param_loc,
+ char conversion_char)
+{
+ if (info->first_arg_num == 0)
+ return true;
+
+ if ((fci->pointer_count == 0 && wanted_type == void_type_node)
+ || suppressed)
+ {
+ if (main_arg_num != 0)
+ {
+ if (suppressed)
+ warning_at (format_string_loc, OPT_Wformat_,
+ "operand number specified with suppressed assignment");
+ else
+ warning_at (format_string_loc, OPT_Wformat_,
+ "operand number specified for format taking no argument");
+ }
+ }
+ else
+ {
+ format_wanted_type *wanted_type_ptr;
+
+ if (main_arg_num != 0)
+ {
+ arg_num = main_arg_num;
+ params = main_arg_params;
+ }
+ else
+ {
+ ++arg_num;
+ if (has_operand_number > 0)
+ {
+ warning_at (format_string_loc, OPT_Wformat_,
+ "missing $ operand number in format");
+ return false;
+ }
+ else
+ has_operand_number = 0;
+ }
+
+ wanted_type_ptr = &main_wanted_type;
+ while (fci)
+ {
+ tree cur_param;
+ if (params == 0)
+ cur_param = NULL;
+ else
+ {
+ cur_param = TREE_VALUE (params);
+ params = TREE_CHAIN (params);
+ }
+
+ wanted_type_ptr->wanted_type = wanted_type;
+ wanted_type_ptr->wanted_type_name = wanted_type_name;
+ wanted_type_ptr->pointer_count = fci->pointer_count + alloc_flag;
+ wanted_type_ptr->char_lenient_flag = 0;
+ if (strchr (fci->flags2, 'c') != 0)
+ wanted_type_ptr->char_lenient_flag = 1;
+ wanted_type_ptr->scalar_identity_flag = 0;
+ if (len_modifier.scalar_identity_flag)
+ wanted_type_ptr->scalar_identity_flag = 1;
+ wanted_type_ptr->writing_in_flag = 0;
+ wanted_type_ptr->reading_from_flag = 0;
+ if (alloc_flag)
+ wanted_type_ptr->writing_in_flag = 1;
+ else
+ {
+ if (strchr (fci->flags2, 'W') != 0)
+ wanted_type_ptr->writing_in_flag = 1;
+ if (strchr (fci->flags2, 'R') != 0)
+ wanted_type_ptr->reading_from_flag = 1;
+ }
+ wanted_type_ptr->kind = CF_KIND_FORMAT;
+ wanted_type_ptr->param = cur_param;
+ wanted_type_ptr->arg_num = arg_num;
+ wanted_type_ptr->format_start = format_start;
+ wanted_type_ptr->format_length = format_chars - format_start;
+ wanted_type_ptr->offset_loc = format_chars - orig_format_chars;
+ wanted_type_ptr->next = NULL;
+ if (last_wanted_type != 0)
+ last_wanted_type->next = wanted_type_ptr;
+ if (first_wanted_type == 0)
+ first_wanted_type = wanted_type_ptr;
+ last_wanted_type = wanted_type_ptr;
+
+ fci = fci->chain;
+ if (fci)
+ {
+ wanted_type_ptr = fwt_pool.allocate ();
+ arg_num++;
+ wanted_type = *fci->types[len_modifier.val].type;
+ wanted_type_name = fci->types[len_modifier.val].name;
+ }
+ }
+ }
+
+ if (first_wanted_type != 0)
+ {
+ ptrdiff_t offset_to_format_start = (start_of_this_format - 1) - orig_format_chars;
+ ptrdiff_t offset_to_format_end = (format_chars - 1) - orig_format_chars;
+ /* By default, use the end of the range for the caret location. */
+ substring_loc fmt_loc (fmt_param_loc, TREE_TYPE (format_string_cst),
+ offset_to_format_end,
+ offset_to_format_start, offset_to_format_end);
+ ptrdiff_t offset_to_type_start = type_start - orig_format_chars;
+ check_format_types (fmt_loc, first_wanted_type, fki,
+ offset_to_type_start,
+ conversion_char, arglocs);
+
+ /* note printf extension type checks are *additional* - %p must always
+ * be pointer compatible, %d always int compatible.
+ */
+ if (first_wanted_type->kind != CF_KIND_FORMAT || !kef)
+ return true;
+
+ const struct kernel_ext_fmt *kef_now;
+ bool success;
+
+ for (kef_now = kef; kef_now->suffix && !strcmp (kef->suffix, kef_now->suffix); kef_now++)
+ {
+ success = check_kef_type (fmt_loc, kef_now,
+ first_wanted_type->arg_num,
+ first_wanted_type->param,
+ kef_now->type, fki, offset_to_type_start, conversion_char, arglocs);
+
+ if (success)
+ return true;
+ }
+
+ location_t param_loc;
+
+ if (EXPR_HAS_LOCATION (first_wanted_type->param))
+ param_loc = EXPR_LOCATION (first_wanted_type->param);
+ else if (arglocs)
+ {
+ /* arg_num is 1-based. */
+ gcc_assert (first_wanted_type->arg_num > 0);
+ param_loc = (*arglocs)[first_wanted_type->arg_num - 1];
+ }
+
+ format_type_warning (fmt_loc, param_loc, first_wanted_type,
+ kef->type, TREE_TYPE (first_wanted_type->param),
+ fki, offset_to_type_start, conversion_char);
+ }
+
+ return true;
+}
+
+/* Do the main part of checking a call to a format function. FORMAT_CHARS
+ is the NUL-terminated format string (which at this point may contain
+ internal NUL characters); FORMAT_LENGTH is its length (excluding the
+ terminating NUL character). ARG_NUM is one less than the number of
+ the first format argument to check; PARAMS points to that format
+ argument in the list of arguments. */
+
+static void
+check_format_info_main (format_check_results *res,
+ function_format_info *info, const char *format_chars,
+ location_t fmt_param_loc, tree format_string_cst,
+ int format_length, tree params,
+ unsigned HOST_WIDE_INT arg_num,
+ object_allocator <format_wanted_type> &fwt_pool,
+ vec<location_t> *arglocs)
+{
+ const char * const orig_format_chars = format_chars;
+ const tree first_fillin_param = params;
+
+ const format_kind_info * const fki = &format_types[info->format_type];
+ const format_flag_spec * const flag_specs = fki->flag_specs;
+ const location_t format_string_loc = res->format_string_loc;
+
+ /* -1 if no conversions taking an operand have been found; 0 if one has
+ and it didn't use $; 1 if $ formats are in use. */
+ int has_operand_number = -1;
+
+ /* Vector of pointers to opening quoting directives (like GCC "%<"). */
+ auto_vec<const char*> quotdirs;
+
+ /* Pointers to the most recent color directives (like GCC's "%r or %R").
+ A starting color directive much be terminated before the end of
+ the format string. A terminating directive makes no sense without
+ a prior starting directive. */
+ const char *color_begin = NULL;
+ const char *color_end = NULL;
+
+ init_dollar_format_checking (info->first_arg_num, first_fillin_param);
+
+ while (*format_chars != 0)
+ {
+ if (*format_chars++ != '%')
+ continue;
+ if (*format_chars == 0)
+ {
+ format_warning_at_char (format_string_loc, format_string_cst,
+ format_chars - orig_format_chars,
+ OPT_Wformat_,
+ "spurious trailing %<%%%> in format");
+ continue;
+ }
+ if (*format_chars == '%')
+ {
+ ++format_chars;
+ continue;
+ }
+
+ flag_chars_t flag_chars;
+ argument_parser arg_parser (info, format_chars, format_string_cst,
+ orig_format_chars, format_string_loc,
+ flag_chars, has_operand_number,
+ first_fillin_param, fwt_pool, arglocs);
+
+ if (!arg_parser.read_any_dollar ())
+ return;
+
+ if (!arg_parser.read_format_flags ())
+ return;
+
+ /* Read any format width, possibly * or *m$. */
+ if (!arg_parser.read_any_format_width (params, arg_num))
+ return;
+
+ /* Read any format left precision (must be a number, not *). */
+ arg_parser.read_any_format_left_precision ();
+
+ /* Read any format precision, possibly * or *m$. */
+ if (!arg_parser.read_any_format_precision (params, arg_num))
+ return;
+
+ const char *format_start = format_chars;
+
+ arg_parser.handle_alloc_chars ();
+
+ /* The rest of the conversion specification is the length modifier
+ (if any), and the conversion specifier, so this is where the
+ type information starts. If we need to issue a suggestion
+ about a type mismatch, then we should preserve everything up
+ to here. */
+ const char *type_start = format_chars;
+
+ /* Read any length modifier, if this kind of format has them. */
+ const length_modifier len_modifier
+ = arg_parser.read_any_length_modifier ();
+
+ /* Read any modifier (strftime E/O). */
+ arg_parser.read_any_other_modifier ();
+
+ char format_char = *format_chars;
+ if (format_char == 0
+ || (!(fki->flags & (int) FMT_FLAG_FANCY_PERCENT_OK)
+ && format_char == '%'))
+ {
+ format_warning_at_char (format_string_loc, format_string_cst,
+ format_chars - orig_format_chars,
+ OPT_Wformat_,
+ "conversion lacks type at end of format");
+ continue;
+ }
+ format_chars++;
+
+ const format_char_info * const fci
+ = arg_parser.find_format_char_info (format_char);
+ if (!fci)
+ continue;
+
+ struct kernel_ext_fmt *etab = fci->kernel_ext;
+
+ if (etab && format_chars[0] >= 'A' && format_chars[0] <= 'Z')
+ {
+ struct kernel_ext_fmt *etab_end = etab + ETAB_SZ;
+
+ for (; etab < etab_end && etab->suffix; etab++)
+ {
+ if (!strncmp (etab->suffix, format_chars, strlen (etab->suffix)))
+ break;
+ }
+
+ if (!etab->suffix || etab == etab_end)
+ {
+ format_warning_at_char (format_string_loc, format_string_cst,
+ format_chars - orig_format_chars + 1,
+ OPT_Wformat_,
+ "unrecognized printf extension suffix");
+ etab = NULL;
+ }
+ else
+ {
+ format_chars += strlen (etab->suffix);
+ }
+ }
+ else
+ etab = NULL;
+
+ flag_chars.validate (fki, fci, flag_specs, format_chars,
+ format_string_cst,
+ format_string_loc, orig_format_chars, format_char,
+ quotdirs.length () > 0);
+
+ const int alloc_flag = flag_chars.get_alloc_flag (fki);
+ const bool suppressed = flag_chars.assignment_suppression_p (fki);
+
+ /* Diagnose nested or unmatched quoting directives such as GCC's
+ "%<...%<" and "%>...%>". */
+ bool quot_begin_p = strchr (fci->flags2, '<');
+ bool quot_end_p = strchr (fci->flags2, '>');
+
+ if (quot_begin_p && !quot_end_p)
+ {
+ if (quotdirs.length ())
+ format_warning_at_char (format_string_loc, format_string_cst,
+ format_chars - orig_format_chars,
+ OPT_Wformat_,
+ "nested quoting directive");
+ quotdirs.safe_push (format_chars);
+ }
+ else if (!quot_begin_p && quot_end_p)
+ {
+ if (quotdirs.length ())
+ quotdirs.pop ();
+ else
+ format_warning_at_char (format_string_loc, format_string_cst,
+ format_chars - orig_format_chars,
+ OPT_Wformat_,
+ "unmatched quoting directive");
+ }
+
+ bool color_begin_p = strchr (fci->flags2, '/');
+ if (color_begin_p)
+ {
+ color_begin = format_chars;
+ color_end = NULL;
+ }
+ else if (strchr (fci->flags2, '\\'))
+ {
+ if (color_end)
+ format_warning_at_char (format_string_loc, format_string_cst,
+ format_chars - orig_format_chars,
+ OPT_Wformat_,
+ "%qc directive redundant after prior occurence of the same", format_char);
+ else if (!color_begin)
+ format_warning_at_char (format_string_loc, format_string_cst,
+ format_chars - orig_format_chars,
+ OPT_Wformat_,
+ "unmatched color reset directive");
+ color_end = format_chars;
+ }
+
+ /* Diagnose directives that shouldn't appear in a quoted sequence.
+ (They are denoted by a double quote in FLAGS2.) */
+ if (quotdirs.length ())
+ {
+ if (strchr (fci->flags2, '"'))
+ format_warning_at_char (format_string_loc, format_string_cst,
+ format_chars - orig_format_chars,
+ OPT_Wformat_,
+ "%qc conversion used within a quoted sequence",
+ format_char);
+ }
+
+ /* Validate the pairs of flags used. */
+ arg_parser.validate_flag_pairs (fci, format_char);
+
+ arg_parser.give_y2k_warnings (fci, format_char);
+
+ arg_parser.parse_any_scan_set (fci);
+
+ tree wanted_type = NULL;
+ const char *wanted_type_name = NULL;
+
+ if (!arg_parser.handle_conversions (fci, len_modifier,
+ wanted_type, wanted_type_name,
+ arg_num,
+ params,
+ format_char))
+ continue;
+
+ arg_parser.main_wanted_type.next = NULL;
+
+ /* Finally. . .check type of argument against desired type! */
+ if (!arg_parser.check_argument_type (fci, etab, len_modifier,
+ wanted_type, wanted_type_name,
+ suppressed,
+ arg_num, params,
+ alloc_flag,
+ format_start, type_start,
+ fmt_param_loc,
+ format_char))
+ return;
+ }
+
+ if (format_chars - orig_format_chars != format_length)
+ format_warning_at_char (format_string_loc, format_string_cst,
+ format_chars + 1 - orig_format_chars,
+ OPT_Wformat_contains_nul,
+ "embedded %<\\0%> in format");
+ if (info->first_arg_num != 0 && params != 0
+ && has_operand_number <= 0)
+ {
+ res->number_other--;
+ res->number_extra_args++;
+ }
+ if (has_operand_number > 0)
+ finish_dollar_format_checking (res, fki->flags & (int) FMT_FLAG_DOLLAR_GAP_POINTER_OK);
+
+ if (quotdirs.length ())
+ format_warning_at_char (format_string_loc, format_string_cst,
+ quotdirs.pop () - orig_format_chars,
+ OPT_Wformat_, "unterminated quoting directive");
+ if (color_begin && !color_end)
+ format_warning_at_char (format_string_loc, format_string_cst,
+ color_begin - orig_format_chars,
+ OPT_Wformat_, "unterminated color directive");
+}
+
+/* Check the argument types from a single format conversion (possibly
+ including width and precision arguments).
+
+ FMT_LOC is the location of the format conversion.
+
+ TYPES is a singly-linked list expressing the parts of the format
+ conversion that expect argument types, and the arguments they
+ correspond to.
+
+ OFFSET_TO_TYPE_START is the offset within the execution-charset encoded
+ format string to where type information begins for the conversion
+ (the length modifier and conversion specifier).
+
+ CONVERSION_CHAR is the user-provided conversion specifier.
+
+ For example, given:
+
+ sprintf (d, "before %-+*.*lld after", arg3, arg4, arg5);
+
+ then FMT_LOC covers this range:
+
+ sprintf (d, "before %-+*.*lld after", arg3, arg4, arg5);
+ ^^^^^^^^^
+
+ and TYPES in this case is a three-entry singly-linked list consisting of:
+ (1) the check for the field width here:
+ sprintf (d, "before %-+*.*lld after", arg3, arg4, arg5);
+ ^ ^^^^
+ against arg3, and
+ (2) the check for the field precision here:
+ sprintf (d, "before %-+*.*lld after", arg3, arg4, arg5);
+ ^^ ^^^^
+ against arg4, and
+ (3) the check for the length modifier and conversion char here:
+ sprintf (d, "before %-+*.*lld after", arg3, arg4, arg5);
+ ^^^ ^^^^
+ against arg5.
+
+ OFFSET_TO_TYPE_START is 13, the offset to the "lld" within the
+ STRING_CST:
+
+ 0000000000111111111122
+ 0123456789012345678901
+ sprintf (d, "before %-+*.*lld after", arg3, arg4, arg5);
+ ^ ^
+ | ` CONVERSION_CHAR: 'd'
+ type starts here. */
+tree type_normalize (tree type, tree *cousin, tree target = NULL)
+{
+ while (1)
+ {
+ if (TREE_CODE (type) == FUNCTION_TYPE || TREE_CODE (type) == POINTER_TYPE)
+ return type;
+ if (target)
+ /* Strip off any "const" etc. */
+ type = build_qualified_type (type, 0);
+ if (TREE_CODE (TYPE_NAME (type)) != TYPE_DECL)
+ return type;
+
+ if (target && (type == target || TYPE_NAME (type) == target))
+ return target;
+
+ struct type_special *t;
+ for (t = special_types; t->match; t++)
+ {
+ if (!*t->match)
+ continue;
+ if (TYPE_NAME (type) != *t->match)
+ continue;
+ if (t->cousin && *t->cousin)
+ *cousin = *t->cousin;
+ if (t->replace)
+ return *t->replace ? *t->replace : type;
+ return type;
+ }
+
+ tree orig = DECL_ORIGINAL_TYPE (TYPE_NAME (type));
+ if (!orig)
+ return type;
+
+ type = orig;
+ }
+ return type;
+}
+
+/* gcc-10 asserts when you give a TYPE_DECL instead of the actual TYPE */
+static tree
+decl_deref(tree typ)
+{
+ while (TREE_CODE (typ) == TYPE_DECL)
+ typ = DECL_ORIGINAL_TYPE (typ);
+
+ return typ;
+}
+
+static void
+check_format_types (const substring_loc &fmt_loc,
+ format_wanted_type *types, const format_kind_info *fki,
+ int offset_to_type_start,
+ char conversion_char,
+ vec<location_t> *arglocs)
+{
+ for (; types != 0; types = types->next)
+ {
+ tree cur_param;
+ tree cur_type;
+ tree cur_type_cousin = NULL;
+ tree orig_cur_type;
+ tree wanted_type;
+ int arg_num;
+ int i;
+ int char_type_flag;
+
+ wanted_type = types->wanted_type;
+ arg_num = types->arg_num;
+
+ wanted_type = decl_deref(wanted_type);
+
+ /* The following should not occur here. */
+ gcc_assert (wanted_type);
+ gcc_assert (wanted_type != void_type_node || types->pointer_count);
+
+ if (types->pointer_count == 0)
+ wanted_type = lang_hooks.types.type_promotes_to (wanted_type);
+
+ switch (TREE_CODE (wanted_type))
+ {
+ case IDENTIFIER_NODE:
+ break;
+ case TYPE_DECL:
+ wanted_type = TYPE_MAIN_VARIANT (DECL_ORIGINAL_TYPE (wanted_type));
+ break;
+ default:
+ wanted_type = TYPE_MAIN_VARIANT (wanted_type);
+ break;
+ }
+
+ cur_param = types->param;
+ if (!cur_param)
+ {
+ format_type_warning (fmt_loc, UNKNOWN_LOCATION, types, wanted_type,
+ NULL, fki, offset_to_type_start,
+ conversion_char);
+ continue;
+ }
+
+ cur_type = TREE_TYPE (cur_param);
+ if (cur_type == error_mark_node)
+ continue;
+ orig_cur_type = cur_type;
+ char_type_flag = 0;
+
+ location_t param_loc = UNKNOWN_LOCATION;
+ if (EXPR_HAS_LOCATION (cur_param))
+ param_loc = EXPR_LOCATION (cur_param);
+ else if (arglocs)
+ {
+ /* arg_num is 1-based. */
+ gcc_assert (types->arg_num > 0);
+ param_loc = (*arglocs)[types->arg_num - 1];
+ }
+
+ STRIP_NOPS (cur_param);
+
+ /* Check the types of any additional pointer arguments
+ that precede the "real" argument. */
+ for (i = 0; i < types->pointer_count; ++i)
+ {
+ if (TREE_CODE (cur_type) == POINTER_TYPE)
+ {
+ cur_type = TREE_TYPE (cur_type);
+ if (cur_type == error_mark_node)
+ break;
+
+ /* Check for writing through a NULL pointer. */
+ if (types->writing_in_flag
+ && i == 0
+ && cur_param != 0
+ && integer_zerop (cur_param))
+ warning (OPT_Wformat_, "writing through null pointer (argument %d)", arg_num);
+
+ /* Check for reading through a NULL pointer. */
+ if (types->reading_from_flag
+ && i == 0
+ && cur_param != 0
+ && integer_zerop (cur_param))
+ warning (OPT_Wformat_, "reading through null pointer (argument %d)", arg_num);
+
+ if (cur_param != 0 && TREE_CODE (cur_param) == ADDR_EXPR)
+ cur_param = TREE_OPERAND (cur_param, 0);
+ else
+ cur_param = 0;
+
+ /* See if this is an attempt to write into a const type with
+ scanf or with printf "%n". Note: the writing in happens
+ at the first indirection only, if for example
+ void * const * is passed to scanf %p; passing
+ const void ** is simply passing an incompatible type. */
+ if (types->writing_in_flag
+ && i == 0
+ && (TYPE_READONLY (cur_type)
+ || (cur_param != 0
+ && (CONSTANT_CLASS_P (cur_param)
+ || (DECL_P (cur_param)
+ && TREE_READONLY (cur_param))))))
+ warning (OPT_Wformat_, "writing into constant object (argument %d)", arg_num);
+
+ /* If there are extra type qualifiers beyond the first
+ indirection, then this makes the types technically
+ incompatible. */
+ if (i > 0
+ && pedantic
+ && (TYPE_READONLY (cur_type)
+ || TYPE_VOLATILE (cur_type)
+ || TYPE_ATOMIC (cur_type)
+ || TYPE_RESTRICT (cur_type)))
+ warning (OPT_Wformat_, "extra type qualifiers in format argument (argument %d)",
+ arg_num);
+
+ }
+ else
+ {
+ format_type_warning (fmt_loc, param_loc,
+ types, wanted_type, orig_cur_type, fki,
+ offset_to_type_start, conversion_char);
+ break;
+ }
+ }
+
+ if (i < types->pointer_count)
+ continue;
+
+ cur_type = type_normalize (cur_type, &cur_type_cousin);
+
+ /* Check whether the argument type is a character type. This leniency
+ only applies to certain formats, flagged with 'c'. */
+ if (types->char_lenient_flag)
+ char_type_flag = (cur_type == char_type_node
+ || cur_type == signed_char_type_node
+ || cur_type == unsigned_char_type_node);
+
+ int compat = lang_hooks.types_compatible_p (decl_deref (wanted_type), decl_deref (cur_type));
+ /* Check the type of the "real" argument, if there's a type we want. */
+ if ((TREE_CODE (wanted_type) != INTEGER_TYPE || types->pointer_count)
+ && compat)
+ continue;
+ if (TREE_CODE (wanted_type) == INTEGER_TYPE && !types->pointer_count
+ && compat)
+ {
+compat_inner:
+ if (TREE_CODE (cur_param) == INTEGER_CST)
+ continue;
+
+ if (TREE_CODE (types->wanted_type) == TYPE_DECL
+ && TREE_CODE (cur_type) == TYPE_DECL)
+ {
+ if (types->wanted_type == cur_type)
+ continue;
+ format_type_warning (fmt_loc, param_loc, types,
+ wanted_type, orig_cur_type, fki,
+ offset_to_type_start, conversion_char,
+ " (strict match required [A])");
+ continue;
+ }
+ else if (TREE_CODE (types->wanted_type) == TYPE_DECL)
+ {
+ if (types->wanted_type == TYPE_NAME(cur_type))
+ continue;
+ format_type_warning (fmt_loc, param_loc, types,
+ wanted_type, orig_cur_type, fki,
+ offset_to_type_start, conversion_char,
+ " (strict match required [B])");
+ continue;
+ }
+ else if (wanted_type == cur_type)
+ continue;
+ else if (cur_type_cousin)
+ {
+ format_type_warning (fmt_loc, param_loc, types,
+ wanted_type, orig_cur_type, fki,
+ offset_to_type_start, conversion_char,
+ " (strict match required [C])");
+ }
+
+ /*
+ format_type_warning (fmt_loc, param_loc, types,
+ wanted_type, orig_cur_type, fki,
+ offset_to_type_start, conversion_char,
+ " (ultra-pedantic mode)");
+ */
+ continue;
+ }
+
+ /* If we want 'void *', allow any pointer type.
+ (Anything else would already have got a warning.)
+ With -Wpedantic, only allow pointers to void and to character
+ types. */
+ if (wanted_type == void_type_node
+ && (!pedantic || (i == 1 && char_type_flag)))
+ continue;
+ /* Don't warn about differences merely in signedness, unless
+ -Wpedantic. With -Wpedantic, warn if the type is a pointer
+ target and not a character type, and for character types at
+ a second level of indirection. */
+ if (TREE_CODE (wanted_type) == INTEGER_TYPE
+ && TREE_CODE (cur_type) == INTEGER_TYPE
+ && ((!pedantic && !warn_format_signedness)
+ || (i == 0 && !warn_format_signedness)
+ || (i == 1 && char_type_flag))
+ && (TYPE_UNSIGNED (wanted_type)
+ ? wanted_type == c_common_unsigned_type (cur_type)
+ : wanted_type == c_common_signed_type (cur_type)))
+ {
+ if (cur_type_cousin)
+ {
+ if (TREE_CODE (types->wanted_type) == TYPE_DECL
+ && TREE_CODE (cur_type_cousin) == TYPE_DECL)
+ {
+ if (types->wanted_type == cur_type_cousin)
+ continue;
+ format_type_warning (fmt_loc, param_loc, types,
+ wanted_type, orig_cur_type, fki,
+ offset_to_type_start, conversion_char,
+ " (strict match required [X])");
+ continue;
+ }
+ else if (TREE_CODE (types->wanted_type) == TYPE_DECL)
+ {
+ if (types->wanted_type == TYPE_NAME(cur_type_cousin))
+ continue;
+ format_type_warning (fmt_loc, param_loc, types,
+ wanted_type, orig_cur_type, fki,
+ offset_to_type_start, conversion_char,
+ " (strict match required [Y])");
+ continue;
+ }
+ else if (wanted_type == cur_type_cousin)
+ continue;
+ else
+ {
+ format_type_warning (fmt_loc, param_loc, types,
+ wanted_type, orig_cur_type, fki,
+ offset_to_type_start, conversion_char,
+ " (strict match required [Z])");
+ }
+ }
+
+ goto compat_inner;
+ }
+ /* Don't warn about differences merely in signedness if we know
+ that the current type is integer-promoted and its original type
+ was unsigned such as that it is in the range of WANTED_TYPE. */
+ if (TREE_CODE (wanted_type) == INTEGER_TYPE
+ && TREE_CODE (cur_type) == INTEGER_TYPE
+ && warn_format_signedness
+ && TYPE_UNSIGNED (wanted_type)
+ && cur_param != NULL_TREE
+ && TREE_CODE (cur_param) == NOP_EXPR)
+ {
+ tree t = TREE_TYPE (TREE_OPERAND (cur_param, 0));
+ if (TYPE_UNSIGNED (t)
+ && cur_type == lang_hooks.types.type_promotes_to (t))
+ continue;
+ }
+ /* Likewise, "signed char", "unsigned char" and "char" are
+ equivalent but the above test won't consider them equivalent. */
+ if (wanted_type == char_type_node
+ && (!pedantic || i < 2)
+ && char_type_flag)
+ continue;
+ if (types->scalar_identity_flag
+ && (TREE_CODE (cur_type) == TREE_CODE (wanted_type)
+ || (INTEGRAL_TYPE_P (cur_type)
+ && INTEGRAL_TYPE_P (wanted_type)))
+ && TYPE_PRECISION (cur_type) == TYPE_PRECISION (wanted_type))
+ continue;
+ /* Now we have a type mismatch. */
+ format_type_warning (fmt_loc, param_loc, types,
+ wanted_type, orig_cur_type, fki,
+ offset_to_type_start, conversion_char);
+ }
+}
+
+static bool
+check_kef_type (const substring_loc &fmt_loc,
+ const struct kernel_ext_fmt *kef,
+ unsigned arg_num,
+ tree cur_param,
+ tree wanted_type,
+ const format_kind_info *fki,
+ int offset_to_type_start,
+ char conversion_char,
+ vec<location_t> *arglocs)
+{
+ tree cur_type;
+ bool ok = true;
+ int i;
+
+ /* The following should not occur here. */
+ gcc_assert (wanted_type);
+ gcc_assert (wanted_type != void_type_node || kef->ptrlevel);
+
+ if (TREE_CODE (wanted_type) == TYPE_DECL)
+ wanted_type = DECL_ORIGINAL_TYPE (wanted_type);
+
+ if (!cur_param)
+ return false;
+
+ cur_type = TREE_TYPE (cur_param);
+ if (cur_type == error_mark_node)
+ return false;
+
+ location_t param_loc = UNKNOWN_LOCATION;
+ if (EXPR_HAS_LOCATION (cur_param))
+ param_loc = EXPR_LOCATION (cur_param);
+ else if (arglocs)
+ {
+ /* arg_num is 1-based. */
+ gcc_assert (arg_num > 0);
+ param_loc = (*arglocs)[arg_num - 1];
+ }
+ (void)param_loc;
+
+ STRIP_NOPS (cur_param);
+
+ /* Check the types of any additional pointer arguments
+ that precede the "real" argument. */
+ for (i = 0; i < kef->ptrlevel; ++i)
+ {
+ if (TREE_CODE (cur_type) == POINTER_TYPE)
+ {
+ cur_type = TREE_TYPE (cur_type);
+ if (cur_type == error_mark_node)
+ break;
+
+ if (cur_param != 0 && TREE_CODE (cur_param) == ADDR_EXPR)
+ cur_param = TREE_OPERAND (cur_param, 0);
+ else
+ cur_param = 0;
+
+ /* If there are extra type qualifiers beyond the first
+ indirection, then this makes the types technically
+ incompatible. */
+ if (i > 0
+ && pedantic
+ && (TYPE_READONLY (cur_type)
+ || TYPE_VOLATILE (cur_type)
+ || TYPE_ATOMIC (cur_type)
+ || TYPE_RESTRICT (cur_type)))
+ warning (OPT_Wformat_, "extra type qualifiers in format argument (argument %d)",
+ arg_num);
+
+ }
+ else
+ {
+ ok = false;
+ break;
+ }
+ }
+
+ if (i < kef->ptrlevel)
+ return ok;
+
+ int compat = lang_hooks.types_compatible_p (wanted_type, cur_type);
+
+ if (!compat)
+ return false;
+
+ tree cousin;
+ tree normal_type;
+
+ normal_type = type_normalize (cur_type, &cousin, wanted_type);
+
+ return normal_type == wanted_type;
+}
+
+
+/* Given type TYPE, attempt to dereference the type N times
+ (e.g. from ("int ***", 2) to "int *")
+
+ Return the derefenced type, with any qualifiers
+ such as "const" stripped from the result, or
+ NULL if unsuccessful (e.g. TYPE is not a pointer type). */
+
+static tree
+deref_n_times (tree type, int n)
+{
+ gcc_assert (type);
+
+ for (int i = n; i > 0; i--)
+ {
+ if (TREE_CODE (type) != POINTER_TYPE)
+ return NULL_TREE;
+ type = TREE_TYPE (type);
+ }
+ /* Strip off any "const" etc. */
+ return build_qualified_type (type, 0);
+}
+
+/* Lookup the format code for FORMAT_LEN within FLI,
+ returning the string code for expressing it, or NULL
+ if it is not found. */
+
+static const char *
+get_modifier_for_format_len (const format_length_info *fli,
+ enum format_lengths format_len)
+{
+ for (; fli->name; fli++)
+ {
+ if (fli->index == format_len)
+ return fli->name;
+ if (fli->double_index == format_len)
+ return fli->double_name;
+ }
+ return NULL;
+}
+
+#if CHECKING_P
+
+namespace selftest {
+
+static void
+test_get_modifier_for_format_len ()
+{
+ ASSERT_STREQ ("h",
+ get_modifier_for_format_len (printf_length_specs, FMT_LEN_h));
+ ASSERT_STREQ ("hh",
+ get_modifier_for_format_len (printf_length_specs, FMT_LEN_hh));
+ ASSERT_STREQ ("L",
+ get_modifier_for_format_len (printf_length_specs, FMT_LEN_L));
+ ASSERT_EQ (NULL,
+ get_modifier_for_format_len (printf_length_specs, FMT_LEN_none));
+}
+
+} // namespace selftest
+
+#endif /* CHECKING_P */
+
+/* Determine if SPEC_TYPE and ARG_TYPE are sufficiently similar for a
+ format_type_detail using SPEC_TYPE to be offered as a suggestion for
+ Wformat type errors where the argument has type ARG_TYPE. */
+
+static bool
+matching_type_p (tree spec_type, tree arg_type)
+{
+ gcc_assert (spec_type);
+ gcc_assert (arg_type);
+
+ spec_type = decl_deref (spec_type);
+ arg_type = decl_deref (arg_type);
+
+ /* If any of the types requires structural equality, we can't compare
+ their canonical types. */
+ if (TYPE_STRUCTURAL_EQUALITY_P (spec_type)
+ || TYPE_STRUCTURAL_EQUALITY_P (arg_type))
+ return false;
+
+ spec_type = TYPE_CANONICAL (spec_type);
+ arg_type = TYPE_CANONICAL (arg_type);
+
+ if (TREE_CODE (spec_type) == INTEGER_TYPE
+ && TREE_CODE (arg_type) == INTEGER_TYPE
+ && (TYPE_UNSIGNED (spec_type)
+ ? spec_type == c_common_unsigned_type (arg_type)
+ : spec_type == c_common_signed_type (arg_type)))
+ return true;
+
+ return spec_type == arg_type;
+}
+
+/* Subroutine of get_format_for_type.
+
+ Generate a string containing the length modifier and conversion specifier
+ that should be used to format arguments of type ARG_TYPE within FKI
+ (effectively the inverse of the checking code).
+
+ If CONVERSION_CHAR is not zero (the first pass), the resulting suggestion
+ is required to use it, for correcting bogus length modifiers.
+ If CONVERSION_CHAR is zero (the second pass), then allow any suggestion
+ that matches ARG_TYPE.
+
+ If successful, returns a non-NULL string which should be freed
+ by the caller.
+ Otherwise, returns NULL. */
+
+static char *
+get_format_for_type_1 (const format_kind_info *fki, tree arg_type,
+ char conversion_char)
+{
+ gcc_assert (arg_type);
+
+ const format_char_info *spec;
+ for (spec = &fki->conversion_specs[0];
+ spec->format_chars;
+ spec++)
+ {
+ if (conversion_char)
+ if (!strchr (spec->format_chars, conversion_char))
+ continue;
+
+ tree effective_arg_type = deref_n_times (arg_type,
+ spec->pointer_count);
+ if (!effective_arg_type)
+ continue;
+ for (int i = 0; i < FMT_LEN_MAX; i++)
+ {
+ const format_type_detail *ftd = &spec->types[i];
+ if (!ftd->type)
+ continue;
+ if (matching_type_p (*ftd->type, effective_arg_type))
+ {
+ const char *len_modifier
+ = get_modifier_for_format_len (fki->length_char_specs,
+ (enum format_lengths)i);
+ if (!len_modifier)
+ len_modifier = "";
+
+ if (conversion_char)
+ /* We found a match, using the given conversion char - the
+ length modifier was incorrect (or absent).
+ Provide a suggestion using the conversion char with the
+ correct length modifier for the type. */
+ return xasprintf ("%s%c", len_modifier, conversion_char);
+ else
+ /* 2nd pass: no match was possible using the user-provided
+ conversion char, but we do have a match without using it.
+ Provide a suggestion using the first conversion char
+ listed for the given type. */
+ return xasprintf ("%s%c", len_modifier, spec->format_chars[0]);
+ }
+ }
+ }
+
+ return NULL;
+}
+
+/* Generate a string containing the length modifier and conversion specifier
+ that should be used to format arguments of type ARG_TYPE within FKI
+ (effectively the inverse of the checking code).
+
+ If successful, returns a non-NULL string which should be freed
+ by the caller.
+ Otherwise, returns NULL. */
+
+static char *
+get_format_for_type (const format_kind_info *fki, tree arg_type,
+ char conversion_char)
+{
+ gcc_assert (arg_type);
+ gcc_assert (conversion_char);
+
+ /* First pass: look for a format_char_info containing CONVERSION_CHAR
+ If we find one, then presumably the length modifier was incorrect
+ (or absent). */
+ char *result = get_format_for_type_1 (fki, arg_type, conversion_char);
+ if (result)
+ return result;
+
+ /* Second pass: we didn't find a match for CONVERSION_CHAR, so try
+ matching just on the type. */
+ return get_format_for_type_1 (fki, arg_type, '\0');
+}
+
+/* Attempt to get a string for use as a replacement fix-it hint for the
+ source range in FMT_LOC.
+
+ Preserve all of the text within the range of FMT_LOC up to
+ OFFSET_TO_TYPE_START, replacing the rest with an appropriate
+ length modifier and conversion specifier for ARG_TYPE, attempting
+ to keep the user-provided CONVERSION_CHAR if possible.
+
+ For example, given a long vs long long mismatch for arg5 here:
+
+ 000000000111111111122222222223333333333|
+ 123456789012345678901234567890123456789` column numbers
+ 0000000000111111111122|
+ 0123456789012345678901` string offsets
+ V~~~~~~~~ : range of FMT_LOC, from cols 23-31
+ sprintf (d, "before %-+*.*lld after", arg3, arg4, arg5);
+ ^ ^
+ | ` CONVERSION_CHAR: 'd'
+ type starts here
+
+ where OFFSET_TO_TYPE_START is 13 (the offset to the "lld" within the
+ STRING_CST), where the user provided:
+ %-+*.*lld
+ the result (assuming "long" argument 5) should be:
+ %-+*.*ld
+
+ If successful, returns a non-NULL string which should be freed
+ by the caller.
+ Otherwise, returns NULL. */
+
+static char *
+get_corrected_substring (const substring_loc &fmt_loc,
+ format_wanted_type *type, tree arg_type,
+ const format_kind_info *fki,
+ int offset_to_type_start, char conversion_char)
+{
+ /* Attempt to provide hints for argument types, but not for field widths
+ and precisions. */
+ if (!arg_type)
+ return NULL;
+ if (type->kind != CF_KIND_FORMAT)
+ return NULL;
+
+ /* Locate the current code within the source range, rejecting
+ any awkward cases where the format string occupies more than
+ one line.
+ Lookup the place where the type starts (including any length
+ modifiers), getting it as the caret location. */
+ substring_loc type_loc (fmt_loc);
+ type_loc.set_caret_index (offset_to_type_start);
+
+ location_t fmt_substring_loc;
+ const char *err = type_loc.get_location (&fmt_substring_loc);
+ if (err)
+ return NULL;
+
+ source_range fmt_substring_range
+ = get_range_from_loc (line_table, fmt_substring_loc);
+
+ expanded_location caret
+ = expand_location_to_spelling_point (fmt_substring_loc);
+ expanded_location start
+ = expand_location_to_spelling_point (fmt_substring_range.m_start);
+ expanded_location finish
+ = expand_location_to_spelling_point (fmt_substring_range.m_finish);
+ if (caret.file != start.file)
+ return NULL;
+ if (start.file != finish.file)
+ return NULL;
+ if (caret.line != start.line)
+ return NULL;
+ if (start.line != finish.line)
+ return NULL;
+ if (start.column > caret.column)
+ return NULL;
+ if (start.column > finish.column)
+ return NULL;
+ if (caret.column > finish.column)
+ return NULL;
+
+#if BUILDING_GCC_VERSION >= 9000
+ char_span line = location_get_source_line (start.file, start.line);
+ if (!line)
+ return NULL;
+
+ /* If we got this far, then we have the line containing the
+ existing conversion specification.
+
+ Generate a trimmed copy, containing the prefix part of the conversion
+ specification, up to the (but not including) the length modifier.
+ In the above example, this would be "%-+*.*". */
+ int length_up_to_type = caret.column - start.column;
+ char_span prefix_span = line.subspan (start.column - 1, length_up_to_type);
+ char *prefix = prefix_span.xstrdup ();
+#else
+ char *prefix = NULL;
+#endif
+
+ /* Now attempt to generate a suggestion for the rest of the specification
+ (length modifier and conversion char), based on ARG_TYPE and
+ CONVERSION_CHAR.
+ In the above example, this would be "ld". */
+ char *format_for_type = get_format_for_type (fki, arg_type, conversion_char);
+ if (!format_for_type)
+ {
+ free (prefix);
+ return NULL;
+ }
+
+ /* Success. Generate the resulting suggestion for the whole range of
+ FMT_LOC by concatenating the two strings.
+ In the above example, this would be "%-+*.*ld". */
+ char *result = concat (prefix, format_for_type, NULL);
+ free (format_for_type);
+ free (prefix);
+ return result;
+}
+
+/* Helper class for adding zero or more trailing '*' to types.
+
+ The format type and name exclude any '*' for pointers, so those
+ must be formatted manually. For all the types we currently have,
+ this is adequate, but formats taking pointers to functions or
+ arrays would require the full type to be built up in order to
+ print it with %T. */
+
+class indirection_suffix
+{
+ public:
+ indirection_suffix (int pointer_count) : m_pointer_count (pointer_count) {}
+
+ /* Determine the size of the buffer (including NUL-terminator). */
+
+ size_t get_buffer_size () const
+ {
+ return m_pointer_count + 2;
+ }
+
+ /* Write the '*' to DST and add a NUL-terminator. */
+
+ void fill_buffer (char *dst) const
+ {
+ if (m_pointer_count == 0)
+ dst[0] = 0;
+ else if (c_dialect_cxx ())
+ {
+ memset (dst, '*', m_pointer_count);
+ dst[m_pointer_count] = 0;
+ }
+ else
+ {
+ dst[0] = ' ';
+ memset (dst + 1, '*', m_pointer_count);
+ dst[m_pointer_count + 1] = 0;
+ }
+ }
+
+ private:
+ int m_pointer_count;
+};
+
+#if BUILDING_GCC_VERSION >= 9000
+/* not exported by GCC... need a local copy :( */
+class frr_range_label_for_type_mismatch : public range_label
+{
+ public:
+ frr_range_label_for_type_mismatch (tree labelled_type, tree other_type)
+ : m_labelled_type (labelled_type), m_other_type (other_type)
+ {
+ }
+
+ label_text get_text (unsigned range_idx) const override;
+
+ protected:
+ tree m_labelled_type;
+ tree m_other_type;
+};
+
+/* Print T to CPP. */
+
+static void
+print_type (c_pretty_printer *cpp, tree t, bool *quoted)
+{
+ gcc_assert (TYPE_P (t));
+ struct obstack *ob = pp_buffer (cpp)->obstack;
+ char *p = (char *) obstack_base (ob);
+ /* Remember the end of the initial dump. */
+ int len = obstack_object_size (ob);
+
+ tree name = TYPE_NAME (t);
+ if (name && TREE_CODE (name) == TYPE_DECL && DECL_NAME (name))
+ pp_identifier (cpp, lang_hooks.decl_printable_name (name, 2));
+ else
+ cpp->type_id (t);
+
+ /* If we're printing a type that involves typedefs, also print the
+ stripped version. But sometimes the stripped version looks
+ exactly the same, so we don't want it after all. To avoid
+ printing it in that case, we play ugly obstack games. */
+ if (TYPE_CANONICAL (t) && t != TYPE_CANONICAL (t))
+ {
+ c_pretty_printer cpp2;
+ /* Print the stripped version into a temporary printer. */
+ cpp2.type_id (TYPE_CANONICAL (t));
+ struct obstack *ob2 = cpp2.buffer->obstack;
+ /* Get the stripped version from the temporary printer. */
+ const char *aka = (char *) obstack_base (ob2);
+ int aka_len = obstack_object_size (ob2);
+ int type1_len = obstack_object_size (ob) - len;
+
+ /* If they are identical, bail out. */
+ if (aka_len == type1_len && memcmp (p + len, aka, aka_len) == 0)
+ return;
+
+ /* They're not, print the stripped version now. */
+ if (*quoted)
+ pp_end_quote (cpp, pp_show_color (cpp));
+ pp_c_whitespace (cpp);
+ pp_left_brace (cpp);
+ pp_c_ws_string (cpp, _("aka"));
+ pp_c_whitespace (cpp);
+ if (*quoted)
+ pp_begin_quote (cpp, pp_show_color (cpp));
+ cpp->type_id (TYPE_CANONICAL (t));
+ if (*quoted)
+ pp_end_quote (cpp, pp_show_color (cpp));
+ pp_right_brace (cpp);
+ /* No further closing quotes are needed. */
+ *quoted = false;
+ }
+}
+
+/* C-specific implementation of range_label::get_text () vfunc for
+ range_label_for_type_mismatch. */
+#if BUILDING_GCC_VERSION >= 10000
+#define label_borrow(text) label_text::borrow(text)
+#define label_take(text) label_text::take(text)
+#else
+#define label_borrow(text) label_text((char *)text, false)
+#define label_take(text) label_text(text, true)
+#endif
+
+label_text
+frr_range_label_for_type_mismatch::get_text (unsigned /*range_idx*/) const
+{
+ if (m_labelled_type == NULL_TREE)
+ return label_borrow("(null tree)");
+
+ c_pretty_printer cpp;
+ bool quoted = false;
+ print_type (&cpp, m_labelled_type, &quoted);
+ return label_take(xstrdup (pp_formatted_text (&cpp)));
+}
+
+#define range_label_for_type_mismatch frr_range_label_for_type_mismatch
+#endif
+
+/* Subclass of range_label for labelling the range in the format string
+ with the type in question, adding trailing '*' for pointer_count. */
+
+class range_label_for_format_type_mismatch
+ : public range_label_for_type_mismatch
+{
+ public:
+ range_label_for_format_type_mismatch (tree labelled_type, tree other_type,
+ int pointer_count)
+ : range_label_for_type_mismatch (labelled_type, other_type),
+ m_pointer_count (pointer_count)
+ {
+ }
+
+#if BUILDING_GCC_VERSION >= 13000
+#define text_get(text) text.get()
+#define text_return(text, result) return label_text::take(result)
+#else
+#define text_get(text) text.m_buffer
+#define text_return(text, result) text.maybe_free(); return label_take(result)
+#endif
+
+ label_text get_text (unsigned range_idx) const final override
+ {
+ label_text text = range_label_for_type_mismatch::get_text (range_idx);
+ if (text_get(text) == NULL)
+ return text;
+
+ indirection_suffix suffix (m_pointer_count);
+ char *p = (char *) alloca (suffix.get_buffer_size ());
+ suffix.fill_buffer (p);
+
+ char *result = concat (text_get(text), p, NULL);
+ text_return(text, result);
+ }
+
+ private:
+ int m_pointer_count;
+};
+
+/* Give a warning about a format argument of different type from that expected.
+ The range of the diagnostic is taken from WHOLE_FMT_LOC; the caret location
+ is based on the location of the char at TYPE->offset_loc.
+ PARAM_LOC is the location of the relevant argument, or UNKNOWN_LOCATION
+ if this is unavailable.
+ WANTED_TYPE is the type the argument should have,
+ possibly stripped of pointer dereferences. The description (such as "field
+ precision"), the placement in the format string, a possibly more
+ friendly name of WANTED_TYPE, and the number of pointer dereferences
+ are taken from TYPE. ARG_TYPE is the type of the actual argument,
+ or NULL if it is missing.
+
+ OFFSET_TO_TYPE_START is the offset within the execution-charset encoded
+ format string to where type information begins for the conversion
+ (the length modifier and conversion specifier).
+ CONVERSION_CHAR is the user-provided conversion specifier.
+
+ For example, given a type mismatch for argument 5 here:
+
+ 00000000011111111112222222222333333333344444444445555555555|
+ 12345678901234567890123456789012345678901234567890123456789` column numbers
+ 0000000000111111111122|
+ 0123456789012345678901` offsets within STRING_CST
+ V~~~~~~~~ : range of WHOLE_FMT_LOC, from cols 23-31
+ sprintf (d, "before %-+*.*lld after", int_expr, int_expr, long_expr);
+ ^ ^ ^~~~~~~~~
+ | ` CONVERSION_CHAR: 'd' PARAM_LOC
+ type starts here
+
+ OFFSET_TO_TYPE_START is 13, the offset to the "lld" within the
+ STRING_CST. */
+
+static void
+format_type_warning (const substring_loc &whole_fmt_loc,
+ location_t param_loc,
+ format_wanted_type *type,
+ tree wanted_type, tree arg_type,
+ const format_kind_info *fki,
+ int offset_to_type_start,
+ char conversion_char,
+ const char *extra)
+{
+ enum format_specifier_kind kind = type->kind;
+ const char *wanted_type_name = type->wanted_type_name;
+ const char *format_start = type->format_start;
+ int format_length = type->format_length;
+ int pointer_count = type->pointer_count;
+ int arg_num = type->arg_num;
+
+ if (!extra)
+ extra = "";
+
+ /* If ARG_TYPE is a typedef with a misleading name (for example,
+ size_t but not the standard size_t expected by printf %zu), avoid
+ printing the typedef name. */
+ if (wanted_type_name
+ && arg_type
+ && TYPE_NAME (arg_type)
+ && TREE_CODE (TYPE_NAME (arg_type)) == TYPE_DECL
+ && DECL_NAME (TYPE_NAME (arg_type))
+ && !strcmp (wanted_type_name,
+ lang_hooks.decl_printable_name (TYPE_NAME (arg_type), 2)))
+ arg_type = TYPE_MAIN_VARIANT (arg_type);
+
+ indirection_suffix suffix (pointer_count);
+ char *p = (char *) alloca (suffix.get_buffer_size ());
+ suffix.fill_buffer (p);
+
+ /* WHOLE_FMT_LOC has the caret at the end of the range.
+ Set the caret to be at the offset from TYPE. Subtract one
+ from the offset for the same reason as in format_warning_at_char. */
+ substring_loc fmt_loc (whole_fmt_loc);
+ fmt_loc.set_caret_index (type->offset_loc - 1);
+
+#if BUILDING_GCC_VERSION >= 9000
+ range_label_for_format_type_mismatch fmt_label (wanted_type, arg_type,
+ pointer_count);
+ range_label_for_type_mismatch param_label (arg_type, wanted_type);
+
+ /* Get a string for use as a replacement fix-it hint for the range in
+ fmt_loc, or NULL. */
+ char *corrected_substring
+ = get_corrected_substring (fmt_loc, type, arg_type, fki,
+ offset_to_type_start, conversion_char);
+ format_string_diagnostic_t diag (fmt_loc, &fmt_label, param_loc, &param_label,
+ corrected_substring);
+# define format_warning_at_substring(a,b,c,d,e,...) \
+ diag.emit_warning(__VA_ARGS__)
+#else
+# define format_warning_at_substring(a,b,c,d,...) \
+ format_warning_at_substring(a,c,__VA_ARGS__)
+ /* Get a string for use as a replacement fix-it hint for the range in
+ fmt_loc, or NULL. */
+ char *corrected_substring
+ = get_corrected_substring (fmt_loc, type, arg_type, fki,
+ offset_to_type_start, conversion_char);
+
+#endif
+
+ if (wanted_type_name)
+ {
+ if (arg_type)
+ format_warning_at_substring
+ (fmt_loc, &fmt_label, param_loc, &param_label,
+ corrected_substring, OPT_Wformat_,
+ "%s %<%s%.*s%> expects argument of type %<%s%s%>, but argument %d has type %qT%s",
+ gettext (kind_descriptions[kind]),
+ (kind == CF_KIND_FORMAT ? "%" : ""),
+ format_length, format_start,
+ wanted_type_name, p, arg_num, arg_type, extra);
+ else
+ format_warning_at_substring
+ (fmt_loc, &fmt_label, param_loc, &param_label,
+ corrected_substring, OPT_Wformat_,
+ "%s %<%s%.*s%> expects a matching %<%s%s%> argument%s",
+ gettext (kind_descriptions[kind]),
+ (kind == CF_KIND_FORMAT ? "%" : ""),
+ format_length, format_start, wanted_type_name, p, extra);
+ }
+ else
+ {
+ if (arg_type)
+ format_warning_at_substring
+ (fmt_loc, &fmt_label, param_loc, &param_label,
+ corrected_substring, OPT_Wformat_,
+ "%s %<%s%.*s%> expects argument of type %<%T%s%>, but argument %d has type %qT%s",
+ gettext (kind_descriptions[kind]),
+ (kind == CF_KIND_FORMAT ? "%" : ""),
+ format_length, format_start,
+ wanted_type, p, arg_num, arg_type, extra);
+ else
+ format_warning_at_substring
+ (fmt_loc, &fmt_label, param_loc, &param_label,
+ corrected_substring, OPT_Wformat_,
+ "%s %<%s%.*s%> expects a matching %<%T%s%> argument%s",
+ gettext (kind_descriptions[kind]),
+ (kind == CF_KIND_FORMAT ? "%" : ""),
+ format_length, format_start, wanted_type, p, extra);
+ }
+
+ free (corrected_substring);
+}
+
+
+#if 0
+/* Given a format_char_info array FCI, and a character C, this function
+ returns the index into the conversion_specs where that specifier's
+ data is located. The character must exist. */
+static unsigned int
+find_char_info_specifier_index (const format_char_info *fci, int c)
+{
+ unsigned i;
+
+ for (i = 0; fci->format_chars; i++, fci++)
+ if (strchr (fci->format_chars, c))
+ return i;
+
+ /* We shouldn't be looking for a non-existent specifier. */
+ gcc_unreachable ();
+}
+
+/* Given a format_length_info array FLI, and a character C, this
+ function returns the index into the conversion_specs where that
+ modifier's data is located. The character must exist. */
+static unsigned int
+find_length_info_modifier_index (const format_length_info *fli, int c)
+{
+ unsigned i;
+
+ for (i = 0; fli->name; i++, fli++)
+ if (strchr (fli->name, c))
+ return i;
+
+ /* We shouldn't be looking for a non-existent modifier. */
+ gcc_unreachable ();
+}
+#endif
+
+#ifdef TARGET_FORMAT_TYPES
+extern const format_kind_info TARGET_FORMAT_TYPES[];
+#endif
+
+#ifdef TARGET_OVERRIDES_FORMAT_ATTRIBUTES
+extern const target_ovr_attr TARGET_OVERRIDES_FORMAT_ATTRIBUTES[];
+#endif
+#ifdef TARGET_OVERRIDES_FORMAT_INIT
+ extern void TARGET_OVERRIDES_FORMAT_INIT (void);
+#endif
+
+/* Attributes such as "printf" are equivalent to those such as
+ "gnu_printf" unless this is overridden by a target. */
+static const target_ovr_attr gnu_target_overrides_format_attributes[] =
+{
+ { NULL, NULL }
+};
+
+/* Translate to unified attribute name. This is used in decode_format_type and
+ decode_format_attr. In attr_name the user specified argument is passed. It
+ returns the unified format name from TARGET_OVERRIDES_FORMAT_ATTRIBUTES
+ or the attr_name passed to this function, if there is no matching entry. */
+static const char *
+convert_format_name_to_system_name (const char *attr_name)
+{
+ int i;
+
+ if (attr_name == NULL || *attr_name == 0
+ || strncmp (attr_name, "gcc_", 4) == 0)
+ return attr_name;
+#ifdef TARGET_OVERRIDES_FORMAT_INIT
+ TARGET_OVERRIDES_FORMAT_INIT ();
+#endif
+
+#ifdef TARGET_OVERRIDES_FORMAT_ATTRIBUTES
+ /* Check if format attribute is overridden by target. */
+ if (TARGET_OVERRIDES_FORMAT_ATTRIBUTES != NULL
+ && TARGET_OVERRIDES_FORMAT_ATTRIBUTES_COUNT > 0)
+ {
+ for (i = 0; i < TARGET_OVERRIDES_FORMAT_ATTRIBUTES_COUNT; ++i)
+ {
+ if (cmp_attribs (TARGET_OVERRIDES_FORMAT_ATTRIBUTES[i].named_attr_src,
+ attr_name))
+ return attr_name;
+ if (cmp_attribs (TARGET_OVERRIDES_FORMAT_ATTRIBUTES[i].named_attr_dst,
+ attr_name))
+ return TARGET_OVERRIDES_FORMAT_ATTRIBUTES[i].named_attr_src;
+ }
+ }
+#endif
+ /* Otherwise default to gnu format. */
+ for (i = 0;
+ gnu_target_overrides_format_attributes[i].named_attr_src != NULL;
+ ++i)
+ {
+ if (cmp_attribs (gnu_target_overrides_format_attributes[i].named_attr_src,
+ attr_name))
+ return attr_name;
+ if (cmp_attribs (gnu_target_overrides_format_attributes[i].named_attr_dst,
+ attr_name))
+ return gnu_target_overrides_format_attributes[i].named_attr_src;
+ }
+
+ return attr_name;
+}
+
+/* Handle a "format" attribute; arguments as in
+ struct attribute_spec.handler. */
+tree
+handle_frr_format_attribute (tree *node, tree ARG_UNUSED (name), tree args,
+ int flags, bool *no_add_attrs)
+{
+ tree type = *node;
+ function_format_info info;
+
+ /* Canonicalize name of format function. */
+ if (TREE_CODE (TREE_VALUE (args)) == IDENTIFIER_NODE)
+ TREE_VALUE (args) = canonicalize_attr_name (TREE_VALUE (args));
+
+ if (!decode_format_attr (args, &info, 0))
+ {
+ *no_add_attrs = true;
+ return NULL_TREE;
+ }
+
+ if (prototype_p (type))
+ {
+ if (!check_format_string (type, info.format_num, flags,
+ no_add_attrs, info.format_type))
+ return NULL_TREE;
+
+ if (info.first_arg_num != 0)
+ {
+ unsigned HOST_WIDE_INT arg_num = 1;
+ function_args_iterator iter;
+ tree arg_type;
+
+ /* Verify that first_arg_num points to the last arg,
+ the ... */
+ FOREACH_FUNCTION_ARGS (type, arg_type, iter)
+ arg_num++;
+
+ if (arg_num != info.first_arg_num)
+ {
+ if (!(flags & (int) ATTR_FLAG_BUILT_IN))
+ error ("arguments to be formatted is not %<...%>");
+ *no_add_attrs = true;
+ return NULL_TREE;
+ }
+ }
+ }
+
+ /* Check if this is a strftime variant. Just for this variant
+ FMT_FLAG_ARG_CONVERT is not set. */
+ if ((format_types[info.format_type].flags & (int) FMT_FLAG_ARG_CONVERT) == 0
+ && info.first_arg_num != 0)
+ {
+ error ("strftime formats cannot format arguments");
+ *no_add_attrs = true;
+ return NULL_TREE;
+ }
+
+ return NULL_TREE;
+}
+
+#if CHECKING_P
+
+namespace selftest {
+
+/* Selftests of location handling. */
+
+/* Get the format_kind_info with the given name. */
+
+static const format_kind_info *
+get_info (const char *name)
+{
+ int idx = decode_format_type (name);
+ const format_kind_info *fki = &format_types[idx];
+ ASSERT_STREQ (fki->name, name);
+ return fki;
+}
+
+/* Verify that get_format_for_type (FKI, TYPE, CONVERSION_CHAR)
+ is EXPECTED_FORMAT. */
+
+static void
+assert_format_for_type_streq (const location &loc, const format_kind_info *fki,
+ const char *expected_format, tree type,
+ char conversion_char)
+{
+ gcc_assert (fki);
+ gcc_assert (expected_format);
+ gcc_assert (type);
+
+ char *actual_format = get_format_for_type (fki, type, conversion_char);
+ ASSERT_STREQ_AT (loc, expected_format, actual_format);
+ free (actual_format);
+}
+
+/* Selftests for get_format_for_type. */
+
+#define ASSERT_FORMAT_FOR_TYPE_STREQ(EXPECTED_FORMAT, TYPE, CONVERSION_CHAR) \
+ assert_format_for_type_streq (SELFTEST_LOCATION, (fki), (EXPECTED_FORMAT), \
+ (TYPE), (CONVERSION_CHAR))
+
+/* Selftest for get_format_for_type for "printf"-style functions. */
+
+static void
+test_get_format_for_type_printf ()
+{
+ const format_kind_info *fki = get_info ("gnu_printf");
+ ASSERT_NE (fki, NULL);
+
+ ASSERT_FORMAT_FOR_TYPE_STREQ ("f", double_type_node, 'i');
+ ASSERT_FORMAT_FOR_TYPE_STREQ ("Lf", long_double_type_node, 'i');
+ ASSERT_FORMAT_FOR_TYPE_STREQ ("f", double_type_node, 'o');
+ ASSERT_FORMAT_FOR_TYPE_STREQ ("Lf", long_double_type_node, 'o');
+ ASSERT_FORMAT_FOR_TYPE_STREQ ("f", double_type_node, 'x');
+ ASSERT_FORMAT_FOR_TYPE_STREQ ("Lf", long_double_type_node, 'x');
+ ASSERT_FORMAT_FOR_TYPE_STREQ ("f", double_type_node, 'X');
+ ASSERT_FORMAT_FOR_TYPE_STREQ ("Lf", long_double_type_node, 'X');
+ ASSERT_FORMAT_FOR_TYPE_STREQ ("d", integer_type_node, 'd');
+ ASSERT_FORMAT_FOR_TYPE_STREQ ("i", integer_type_node, 'i');
+ ASSERT_FORMAT_FOR_TYPE_STREQ ("o", integer_type_node, 'o');
+ ASSERT_FORMAT_FOR_TYPE_STREQ ("x", integer_type_node, 'x');
+ ASSERT_FORMAT_FOR_TYPE_STREQ ("X", integer_type_node, 'X');
+ ASSERT_FORMAT_FOR_TYPE_STREQ ("d", unsigned_type_node, 'd');
+ ASSERT_FORMAT_FOR_TYPE_STREQ ("i", unsigned_type_node, 'i');
+ ASSERT_FORMAT_FOR_TYPE_STREQ ("o", unsigned_type_node, 'o');
+ ASSERT_FORMAT_FOR_TYPE_STREQ ("x", unsigned_type_node, 'x');
+ ASSERT_FORMAT_FOR_TYPE_STREQ ("X", unsigned_type_node, 'X');
+ ASSERT_FORMAT_FOR_TYPE_STREQ ("ld", long_integer_type_node, 'd');
+ ASSERT_FORMAT_FOR_TYPE_STREQ ("li", long_integer_type_node, 'i');
+ ASSERT_FORMAT_FOR_TYPE_STREQ ("lx", long_integer_type_node, 'x');
+ ASSERT_FORMAT_FOR_TYPE_STREQ ("lo", long_unsigned_type_node, 'o');
+ ASSERT_FORMAT_FOR_TYPE_STREQ ("lx", long_unsigned_type_node, 'x');
+ ASSERT_FORMAT_FOR_TYPE_STREQ ("lld", long_long_integer_type_node, 'd');
+ ASSERT_FORMAT_FOR_TYPE_STREQ ("lli", long_long_integer_type_node, 'i');
+ ASSERT_FORMAT_FOR_TYPE_STREQ ("llo", long_long_unsigned_type_node, 'o');
+ ASSERT_FORMAT_FOR_TYPE_STREQ ("llx", long_long_unsigned_type_node, 'x');
+ ASSERT_FORMAT_FOR_TYPE_STREQ ("s", build_pointer_type (char_type_node), 'i');
+}
+
+/* Selftest for get_format_for_type for "scanf"-style functions. */
+
+static void
+test_get_format_for_type_scanf ()
+{
+ const format_kind_info *fki = get_info ("gnu_scanf");
+ ASSERT_NE (fki, NULL);
+ ASSERT_FORMAT_FOR_TYPE_STREQ ("d", build_pointer_type (integer_type_node), 'd');
+ ASSERT_FORMAT_FOR_TYPE_STREQ ("u", build_pointer_type (unsigned_type_node), 'u');
+ ASSERT_FORMAT_FOR_TYPE_STREQ ("ld",
+ build_pointer_type (long_integer_type_node), 'd');
+ ASSERT_FORMAT_FOR_TYPE_STREQ ("lu",
+ build_pointer_type (long_unsigned_type_node), 'u');
+ ASSERT_FORMAT_FOR_TYPE_STREQ
+ ("lld", build_pointer_type (long_long_integer_type_node), 'd');
+ ASSERT_FORMAT_FOR_TYPE_STREQ
+ ("llu", build_pointer_type (long_long_unsigned_type_node), 'u');
+ ASSERT_FORMAT_FOR_TYPE_STREQ ("e", build_pointer_type (float_type_node), 'e');
+ ASSERT_FORMAT_FOR_TYPE_STREQ ("le", build_pointer_type (double_type_node), 'e');
+}
+
+#undef ASSERT_FORMAT_FOR_TYPE_STREQ
+
+/* Exercise the type-printing label code, to give some coverage
+ under "make selftest-valgrind" (in particular, to ensure that
+ the label-printing machinery doesn't leak). */
+
+static void
+test_type_mismatch_range_labels ()
+{
+ /* Create a tempfile and write some text to it.
+ ....................0000000001 11111111 12 22222222
+ ....................1234567890 12345678 90 12345678. */
+ const char *content = " printf (\"msg: %i\\n\", msg);\n";
+ temp_source_file tmp (SELFTEST_LOCATION, ".c", content);
+ line_table_test ltt;
+
+ linemap_add (line_table, LC_ENTER, false, tmp.get_filename (), 1);
+
+ location_t c17 = linemap_position_for_column (line_table, 17);
+ ASSERT_EQ (LOCATION_COLUMN (c17), 17);
+ location_t c18 = linemap_position_for_column (line_table, 18);
+ location_t c24 = linemap_position_for_column (line_table, 24);
+ location_t c26 = linemap_position_for_column (line_table, 26);
+
+ /* Don't attempt to run the tests if column data might be unavailable. */
+ if (c26 > LINE_MAP_MAX_LOCATION_WITH_COLS)
+ return;
+
+ location_t fmt = make_location (c18, c17, c18);
+ ASSERT_EQ (LOCATION_COLUMN (fmt), 18);
+
+ location_t param = make_location (c24, c24, c26);
+ ASSERT_EQ (LOCATION_COLUMN (param), 24);
+
+ range_label_for_format_type_mismatch fmt_label (char_type_node,
+ integer_type_node, 1);
+ range_label_for_type_mismatch param_label (integer_type_node,
+ char_type_node);
+ gcc_rich_location richloc (fmt, &fmt_label);
+ richloc.add_range (param, SHOW_RANGE_WITHOUT_CARET, &param_label);
+
+ test_diagnostic_context dc;
+ diagnostic_show_locus (&dc, &richloc, DK_ERROR);
+ if (c_dialect_cxx ())
+ /* "char*", without a space. */
+ ASSERT_STREQ ("\n"
+ " printf (\"msg: %i\\n\", msg);\n"
+ " ~^ ~~~\n"
+ " | |\n"
+ " char* int\n",
+ pp_formatted_text (dc.printer));
+ else
+ /* "char *", with a space. */
+ ASSERT_STREQ ("\n"
+ " printf (\"msg: %i\\n\", msg);\n"
+ " ~^ ~~~\n"
+ " | |\n"
+ " | int\n"
+ " char *\n",
+ pp_formatted_text (dc.printer));
+}
+
+/* Run all of the selftests within this file. */
+
+void
+c_format_c_tests ()
+{
+ test_get_modifier_for_format_len ();
+ test_get_format_for_type_printf ();
+ test_get_format_for_type_scanf ();
+ test_type_mismatch_range_labels ();
+}
+
+} // namespace selftest
+
+#endif /* CHECKING_P */
+
+// include "gt-c-family-c-format.h"
+
+static const struct attribute_spec frr_format_attribute_table[] =
+{
+ /* { name, min_len, max_len, decl_req, type_req, fn_type_req,
+ affects_type_identity, handler, exclude } */
+ { "frr_format", 3, 3, false, true, true, false,
+ handle_frr_format_attribute, NULL },
+ { "frr_format_arg", 1, 1, false, true, true, false,
+ handle_frr_format_arg_attribute, NULL },
+ { NULL, 0, 0, false, false, false, false, NULL, NULL }
+};
+
+static void
+register_attributes (void *event_data, void *data)
+{
+ // warning (0, G_("Callback to register attributes"));
+ register_attribute (frr_format_attribute_table);
+}
+
+tree
+cb_walk_tree_fn (tree * tp, int * walk_subtrees, void * data ATTRIBUTE_UNUSED)
+{
+ if (TREE_CODE (*tp) != CALL_EXPR)
+ return NULL_TREE;
+
+ tree call_expr = *tp;
+
+ int nargs = call_expr_nargs(call_expr);
+ tree fn = CALL_EXPR_FN(call_expr);
+
+ if (!fn || TREE_CODE (fn) != ADDR_EXPR)
+ return NULL_TREE;
+
+ tree fndecl = TREE_OPERAND (fn, 0);
+ if (TREE_CODE (fndecl) != FUNCTION_DECL)
+ return NULL_TREE;
+
+#if 0
+ warning (0, G_("function call to %s, %d args"),
+ IDENTIFIER_POINTER (DECL_NAME (fndecl)),
+ nargs);
+#endif
+
+ tree *fargs = (tree *) alloca (nargs * sizeof (tree));
+
+ for (int j = 0; j < nargs; j++)
+ {
+ tree arg = CALL_EXPR_ARG(call_expr, j);
+
+ /* For -Wformat undo the implicit passing by hidden reference
+ done by convert_arg_to_ellipsis. */
+ if (TREE_CODE (arg) == ADDR_EXPR
+ && TREE_CODE (TREE_TYPE (arg)) == REFERENCE_TYPE)
+ fargs[j] = TREE_OPERAND (arg, 0);
+ else
+ fargs[j] = arg;
+ }
+
+ check_function_format (TYPE_ATTRIBUTES (TREE_TYPE (fndecl)), nargs, fargs, NULL);
+ return NULL_TREE;
+}
+
+static void
+setup_type (const char *name, tree *dst)
+{
+ tree tmp;
+
+ if (*dst && *dst != void_type_node)
+ return;
+
+ *dst = maybe_get_identifier (name);
+ if (!*dst)
+ return;
+
+ tmp = identifier_global_value (*dst);
+ if (tmp && TREE_CODE (tmp) != TYPE_DECL)
+ {
+ warning (0, "%qs is not defined as a type", name);
+ *dst = NULL;
+ return;
+ }
+ if (tmp && TREE_CODE (tmp) == TYPE_DECL)
+ *dst = tmp;
+ else
+ *dst = NULL;
+}
+
+static void
+handle_finish_parse (void *event_data, void *data)
+{
+ tree fndecl = (tree) event_data;
+ gcc_assert (TREE_CODE (fndecl) == FUNCTION_DECL);
+
+ setup_type ("uint64_t", &local_uint64_t_node);
+ setup_type ("int64_t", &local_int64_t_node);
+
+ setup_type ("size_t", &local_size_t_node);
+ setup_type ("ssize_t", &local_ssize_t_node);
+ setup_type ("atomic_size_t", &local_atomic_size_t_node);
+ setup_type ("atomic_ssize_t", &local_atomic_ssize_t_node);
+ setup_type ("ptrdiff_t", &local_ptrdiff_t_node);
+
+ setup_type ("pid_t", &local_pid_t_node);
+ setup_type ("uid_t", &local_uid_t_node);
+ setup_type ("gid_t", &local_gid_t_node);
+ setup_type ("time_t", &local_time_t_node);
+
+ setup_type ("socklen_t", &local_socklen_t_node);
+ setup_type ("in_addr_t", &local_in_addr_t_node);
+
+ const format_char_info *fci;
+
+ for (fci = print_char_table; fci->format_chars; fci++)
+ {
+ if (!fci->kernel_ext)
+ continue;
+
+ struct kernel_ext_fmt *etab = fci->kernel_ext;
+ struct kernel_ext_fmt *etab_end = etab + ETAB_SZ;
+
+ for (; etab->suffix && etab < etab_end; etab++)
+ {
+ tree identifier, node;
+
+ if (etab->type && etab->type != void_type_node)
+ continue;
+
+ identifier = maybe_get_identifier (etab->type_str);
+
+ if (!identifier || identifier == error_mark_node)
+ continue;
+
+ if (etab->type_code)
+ {
+ node = identifier_global_tag (identifier);
+ if (!node)
+ continue;
+
+ if (node->base.code != etab->type_code)
+ {
+ if (!etab->warned)
+ {
+ warning (0, "%qs tag category (struct/union/enum) mismatch", etab->type_str);
+ etab->warned = true;
+ }
+ continue;
+ }
+ }
+ else
+ {
+ node = identifier_global_value (identifier);
+ if (!node)
+ continue;
+
+ if (TREE_CODE (node) != TYPE_DECL)
+ {
+ if (!etab->warned)
+ {
+ warning (0, "%qs is defined as a non-type", etab->type_str);
+ etab->warned = true;
+ }
+ continue;
+ }
+ node = TREE_TYPE (node);
+
+ if (etab->t_unsigned)
+ node = c_common_unsigned_type (node);
+ else if (etab->t_signed)
+ node = c_common_signed_type (node);
+ }
+
+ etab->type = node;
+ }
+ }
+
+ walk_tree (&DECL_SAVED_TREE (fndecl), cb_walk_tree_fn, NULL, NULL);
+}
+
+static void
+handle_pragma_printfrr_ext (cpp_reader *dummy)
+{
+ tree token = 0;
+ location_t loc;
+ enum cpp_ttype ttype;
+
+ ttype = pragma_lex (&token, &loc);
+ if (ttype != CPP_STRING)
+ {
+ error_at (loc, "%<#pragma FRR printfrr_ext%> requires string argument");
+ return;
+ }
+
+ const char *s = TREE_STRING_POINTER (token);
+
+ if (s[0] != '%')
+ {
+ error_at (loc, "%<#pragma FRR printfrr_ext%>: invalid format string, needs to start with '%%'");
+ return;
+ }
+
+ switch (s[1])
+ {
+ case 'p':
+ case 'd':
+ case 'i':
+ break;
+ default:
+ error_at (loc, "%<#pragma FRR printfrr_ext%>: invalid format string, needs to be %%p, %%d or %%i");
+ return;
+ }
+
+ const format_char_info *fci;
+
+ for (fci = print_char_table; fci->format_chars; fci++)
+ if (strchr (fci->format_chars, s[1]))
+ break;
+
+ gcc_assert (fci->format_chars);
+ gcc_assert (fci->kernel_ext);
+
+ struct kernel_ext_fmt *etab = fci->kernel_ext;
+ struct kernel_ext_fmt *etab_end = etab + ETAB_SZ;
+
+ switch (s[2])
+ {
+ case 'A' ... 'Z':
+ break;
+
+ default:
+ error_at (loc, "%<#pragma FRR printfrr_ext%>: invalid format string, suffix must start with an uppercase letter");
+ return;
+ }
+
+ /* -2 -- need to keep the sentinel at the end */
+ if (etab[ETAB_SZ - 2].suffix)
+ {
+ error_at (loc, "%<#pragma FRR printfrr_ext%>: out of space for format suffixes");
+ return;
+ }
+
+ for (; etab->suffix && etab < etab_end; etab++)
+ {
+ if (!strcmp(s + 2, etab->suffix))
+ {
+ memmove (etab + 1, etab, (etab_end - etab - 1) * sizeof (*etab));
+
+ if (0)
+ {
+ warning_at (loc, OPT_Wformat_,
+ "%<#pragma FRR printfrr_ext%>: duplicate printf format suffix %qs", s);
+ warning_at (etab->origin_loc, OPT_Wformat_,
+ "%<#pragma FRR printfrr_ext%>: previous definition was here");
+ return;
+ }
+
+ break;
+ }
+
+ if (!strncmp(s + 2, etab->suffix, MIN(strlen(s + 2), strlen(etab->suffix))))
+ {
+ warning_at (loc, OPT_Wformat_,
+ "%<#pragma FRR printfrr_ext%>: overlapping printf format suffix %qs", s);
+ warning_at (etab->origin_loc, OPT_Wformat_,
+ "%<#pragma FRR printfrr_ext%>: previous definition for %<%%%c%s%> was here", s[1], etab->suffix);
+ return;
+ }
+ }
+
+ gcc_assert (etab < etab_end);
+
+ memset (etab, 0, sizeof (*etab));
+ etab->suffix = xstrdup(s + 2);
+ etab->origin_loc = loc;
+ etab->type = void_type_node;
+
+ ttype = pragma_lex (&token, &loc);
+ if (ttype != CPP_OPEN_PAREN)
+ {
+ error_at (loc, "%<#pragma FRR printfrr_ext%> expected %<(%>");
+ goto out_drop;
+ }
+
+ ttype = pragma_lex (&token, &loc);
+
+ /* qualifiers */
+ while (ttype == CPP_NAME)
+ {
+ if (!strcmp (IDENTIFIER_POINTER (token), "const"))
+ etab->t_const = true;
+ else if (!strcmp (IDENTIFIER_POINTER (token), "signed"))
+ etab->t_signed = true;
+ else if (!strcmp (IDENTIFIER_POINTER (token), "unsigned"))
+ etab->t_unsigned = true;
+ else
+ break;
+
+ ttype = pragma_lex (&token, &loc);
+ }
+
+ /* tagged types */
+ if (ttype == CPP_NAME && !strcmp (IDENTIFIER_POINTER (token), "struct"))
+ {
+ etab->type_code = RECORD_TYPE;
+ ttype = pragma_lex (&token, &loc);
+ }
+ else if (ttype == CPP_NAME && !strcmp (IDENTIFIER_POINTER (token), "union"))
+ {
+ etab->type_code = UNION_TYPE;
+ ttype = pragma_lex (&token, &loc);
+ }
+ else if (ttype == CPP_NAME && !strcmp (IDENTIFIER_POINTER (token), "enum"))
+ {
+ etab->type_code = ENUMERAL_TYPE;
+ ttype = pragma_lex (&token, &loc);
+ }
+
+ /* type name */
+ if (ttype != CPP_NAME)
+ {
+ error_at (loc, "%<#pragma FRR printfrr_ext%>: expected typename identifier");
+ goto out_drop;
+ }
+
+ etab->type_str = xstrdup (IDENTIFIER_POINTER (token));
+
+ while ((ttype = pragma_lex (&token, &loc)) != CPP_CLOSE_PAREN)
+ {
+ switch (ttype) {
+ case CPP_NAME:
+ error_at (loc, "%<#pragma FRR printfrr_ext%>: unexpected identifier. Note the only supported qualifier is %<const%>");
+ goto out_drop;
+
+ case CPP_MULT:
+ etab->ptrlevel++;
+ break;
+
+ case CPP_EOF:
+ error_at (loc, "%<#pragma FRR printfrr_ext%>: premature end of line, missing %<)%>");
+ goto out_drop;
+
+ default:
+ error_at (loc, "%<#pragma FRR printfrr_ext%>: unsupported token");
+ goto out_drop;
+ }
+ }
+
+ ttype = pragma_lex (&token, &loc);
+ if (ttype != CPP_EOF)
+ warning_at (loc, OPT_Wformat_,
+ "%<#pragma FRR printfrr_ext%>: garbage at end of line");
+
+ return;
+
+out_drop:
+ memset (etab, 0, sizeof (*etab));
+}
+
+static void
+register_pragma_printfrr_ext (void *event_data, void *data)
+{
+ c_register_pragma_with_expansion ("FRR", "printfrr_ext", handle_pragma_printfrr_ext);
+}
+
+static void
+define_vars (void *gcc_data, void *user_data)
+{
+ cpp_define (parse_in, "_FRR_ATTRIBUTE_PRINTFRR=0x10000");
+}
+
+#ifndef __visible
+#define __visible __attribute__((visibility("default")))
+#endif
+
+__visible int plugin_is_GPL_compatible;
+
+__visible int
+plugin_init (struct plugin_name_args *plugin_info,
+ struct plugin_gcc_version *version)
+{
+ const char *plugin_name = plugin_info->base_name;
+
+ if (!plugin_default_version_check(version, &gcc_version))
+ {
+ error(G_("incompatible gcc/plugin versions"));
+ return 1;
+ }
+
+ memset (ext_p, 0, sizeof (ext_p));
+ memset (ext_d, 0, sizeof (ext_d));
+
+ register_callback (plugin_name, PLUGIN_FINISH_PARSE_FUNCTION, handle_finish_parse, NULL);
+ register_callback (plugin_name, PLUGIN_ATTRIBUTES, register_attributes, NULL);
+ register_callback (plugin_name, PLUGIN_START_UNIT, define_vars, NULL);
+ register_callback (plugin_name, PLUGIN_PRAGMAS, register_pragma_printfrr_ext, NULL);
+ return 0;
+}
diff --git a/tools/gcc-plugins/frr-format.h b/tools/gcc-plugins/frr-format.h
new file mode 100644
index 0000000..373ef07
--- /dev/null
+++ b/tools/gcc-plugins/frr-format.h
@@ -0,0 +1,352 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+/* Check calls to formatted I/O functions (-Wformat).
+ Copyright (C) 1992-2018 Free Software Foundation, Inc.
+ */
+
+#ifndef GCC_C_FORMAT_H
+#define GCC_C_FORMAT_H
+
+/* The meaningfully distinct length modifiers for format checking recognized
+ by GCC. */
+enum format_lengths
+{
+ FMT_LEN_none,
+ FMT_LEN_hh,
+ FMT_LEN_h,
+ FMT_LEN_l,
+ FMT_LEN_ll,
+ FMT_LEN_L,
+ FMT_LEN_z,
+ FMT_LEN_t,
+ FMT_LEN_j,
+ FMT_LEN_H,
+ FMT_LEN_D,
+ FMT_LEN_DD,
+ FMT_LEN_MAX
+};
+
+
+/* The standard versions in which various format features appeared. */
+enum format_std_version
+{
+ STD_C89,
+ STD_C94,
+ STD_C9L, /* C99, but treat as C89 if -Wno-long-long. */
+ STD_C99,
+ STD_EXT
+};
+
+/* Flags that may apply to a particular kind of format checked by GCC. */
+enum
+{
+ /* This format converts arguments of types determined by the
+ format string. */
+ FMT_FLAG_ARG_CONVERT = 1,
+ /* The scanf allocation 'a' kludge applies to this format kind. */
+ FMT_FLAG_SCANF_A_KLUDGE = 2,
+ /* A % during parsing a specifier is allowed to be a modified % rather
+ that indicating the format is broken and we are out-of-sync. */
+ FMT_FLAG_FANCY_PERCENT_OK = 4,
+ /* With $ operand numbers, it is OK to reference the same argument more
+ than once. */
+ FMT_FLAG_DOLLAR_MULTIPLE = 8,
+ /* This format type uses $ operand numbers (strfmon doesn't). */
+ FMT_FLAG_USE_DOLLAR = 16,
+ /* Zero width is bad in this type of format (scanf). */
+ FMT_FLAG_ZERO_WIDTH_BAD = 32,
+ /* Empty precision specification is OK in this type of format (printf). */
+ FMT_FLAG_EMPTY_PREC_OK = 64,
+ /* Gaps are allowed in the arguments with $ operand numbers if all
+ arguments are pointers (scanf). */
+ FMT_FLAG_DOLLAR_GAP_POINTER_OK = 128,
+ /* The format arg is an opaque object that will be parsed by an external
+ facility. */
+ FMT_FLAG_PARSE_ARG_CONVERT_EXTERNAL = 256
+ /* Not included here: details of whether width or precision may occur
+ (controlled by width_char and precision_char); details of whether
+ '*' can be used for these (width_type and precision_type); details
+ of whether length modifiers can occur (length_char_specs). */
+};
+
+/* Structure describing a length modifier supported in format checking, and
+ possibly a doubled version such as "hh". */
+struct format_length_info
+{
+ /* Name of the single-character length modifier. If prefixed by
+ a zero character, it describes a multi character length
+ modifier, like I64, I32, etc. */
+ const char *name;
+ /* Index into a format_char_info.types array. */
+ enum format_lengths index;
+ /* Standard version this length appears in. */
+ enum format_std_version std;
+ /* Same, if the modifier can be repeated, or NULL if it can't. */
+ const char *double_name;
+ enum format_lengths double_index;
+ enum format_std_version double_std;
+
+ /* If this flag is set, just scalar width identity is checked, and
+ not the type identity itself. */
+ int scalar_identity_flag;
+};
+
+
+struct kernel_ext_fmt
+{
+ const char *suffix;
+
+ /* RECORD_TYPE, UNION_TYPE, ENUMERAL_TYPE, or NULL for typedef */
+ tree_code type_code;
+ int ptrlevel;
+ bool t_const;
+ bool t_unsigned;
+ bool t_signed;
+ bool warned;
+
+ const char *type_str;
+ GTY(()) tree type;
+
+ location_t origin_loc;
+};
+
+
+/* Structure describing the combination of a conversion specifier
+ (or a set of specifiers which act identically) and a length modifier. */
+struct format_type_detail
+{
+ /* The standard version this combination of length and type appeared in.
+ This is only relevant if greater than those for length and type
+ individually; otherwise it is ignored. */
+ enum format_std_version std;
+ /* The name to use for the type, if different from that generated internally
+ (e.g., "signed size_t"). */
+ const char *name;
+ /* The type itself. */
+ tree *type;
+};
+
+
+/* Macros to fill out tables of these. */
+#define NOARGUMENTS { T89_V, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN }
+#define BADLEN { STD_C89, NULL, NULL }
+#define NOLENGTHS { BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN }
+
+
+/* Structure describing a format conversion specifier (or a set of specifiers
+ which act identically), and the length modifiers used with it. */
+struct format_char_info
+{
+ const char *format_chars;
+ int pointer_count;
+ enum format_std_version std;
+ /* Types accepted for each length modifier. */
+ format_type_detail types[FMT_LEN_MAX];
+ /* List of other modifier characters allowed with these specifiers.
+ This lists flags, and additionally "w" for width, "p" for precision
+ (right precision, for strfmon), "#" for left precision (strfmon),
+ "a" for scanf "a" allocation extension (not applicable in C99 mode),
+ "*" for scanf suppression, and "E" and "O" for those strftime
+ modifiers. */
+ const char *flag_chars;
+ /* List of additional flags describing these conversion specifiers.
+ "c" for generic character pointers being allowed, "2" for strftime
+ two digit year formats, "3" for strftime formats giving two digit
+ years in some locales, "4" for "2" which becomes "3" with an "E" modifier,
+ "o" if use of strftime "O" is a GNU extension beyond C99,
+ "W" if the argument is a pointer which is dereferenced and written into,
+ "R" if the argument is a pointer which is dereferenced and read from,
+ "i" for printf integer formats where the '0' flag is ignored with
+ precision, and "[" for the starting character of a scanf scanset,
+ "<" if the specifier introduces a quoted sequence (such as "%<"),
+ ">" if the specifier terminates a quoted sequence (such as "%>"),
+ "[" if the specifier introduces a color sequence (such as "%r"),
+ "]" if the specifier terminates a color sequence (such as "%R"),
+ "'" (single quote) if the specifier is expected to be quoted when
+ it appears outside a quoted sequence and unquoted otherwise (such
+ as the GCC internal printf format directive "%T"), and
+ "\"" (double quote) if the specifier is not expected to appear in
+ a quoted sequence (such as the GCC internal format directive "%K". */
+ const char *flags2;
+ /* If this format conversion character consumes more than one argument,
+ CHAIN points to information about the next argument. For later
+ arguments, only POINTER_COUNT, TYPES, and the "c", "R", and "W" flags
+ in FLAGS2 are used. */
+ const struct format_char_info *chain;
+
+ struct kernel_ext_fmt *kernel_ext;
+};
+
+
+/* Structure describing a flag accepted by some kind of format. */
+struct format_flag_spec
+{
+ /* The flag character in question (0 for end of array). */
+ int flag_char;
+ /* Zero if this entry describes the flag character in general, or a
+ nonzero character that may be found in flags2 if it describes the
+ flag when used with certain formats only. If the latter, only
+ the first such entry found that applies to the current conversion
+ specifier is used; the values of 'name' and 'long_name' it supplies
+ will be used, if non-NULL and the standard version is higher than
+ the unpredicated one, for any pedantic warning. For example, 'o'
+ for strftime formats (meaning 'O' is an extension over C99). */
+ int predicate;
+ /* Nonzero if the next character after this flag in the format should
+ be skipped ('=' in strfmon), zero otherwise. */
+ int skip_next_char;
+ /* True if the flag introduces quoting (as in GCC's %qE). */
+ bool quoting;
+ /* The name to use for this flag in diagnostic messages. For example,
+ N_("'0' flag"), N_("field width"). */
+ const char *name;
+ /* Long name for this flag in diagnostic messages; currently only used for
+ "ISO C does not support ...". For example, N_("the 'I' printf flag"). */
+ const char *long_name;
+ /* The standard version in which it appeared. */
+ enum format_std_version std;
+};
+
+
+/* Structure describing a combination of flags that is bad for some kind
+ of format. */
+struct format_flag_pair
+{
+ /* The first flag character in question (0 for end of array). */
+ int flag_char1;
+ /* The second flag character. */
+ int flag_char2;
+ /* Nonzero if the message should say that the first flag is ignored with
+ the second, zero if the combination should simply be objected to. */
+ int ignored;
+ /* Zero if this entry applies whenever this flag combination occurs,
+ a nonzero character from flags2 if it only applies in some
+ circumstances (e.g. 'i' for printf formats ignoring 0 with precision). */
+ int predicate;
+};
+
+
+/* Structure describing a particular kind of format processed by GCC. */
+struct format_kind_info
+{
+ /* The name of this kind of format, for use in diagnostics. Also
+ the name of the attribute (without preceding and following __). */
+ const char *name;
+ /* Specifications of the length modifiers accepted; possibly NULL. */
+ const format_length_info *length_char_specs;
+ /* Details of the conversion specification characters accepted. */
+ const format_char_info *conversion_specs;
+ /* String listing the flag characters that are accepted. */
+ const char *flag_chars;
+ /* String listing modifier characters (strftime) accepted. May be NULL. */
+ const char *modifier_chars;
+ /* Details of the flag characters, including pseudo-flags. */
+ const format_flag_spec *flag_specs;
+ /* Details of bad combinations of flags. */
+ const format_flag_pair *bad_flag_pairs;
+ /* Flags applicable to this kind of format. */
+ int flags;
+ /* Flag character to treat a width as, or 0 if width not used. */
+ int width_char;
+ /* Flag character to treat a left precision (strfmon) as,
+ or 0 if left precision not used. */
+ int left_precision_char;
+ /* Flag character to treat a precision (for strfmon, right precision) as,
+ or 0 if precision not used. */
+ int precision_char;
+ /* If a flag character has the effect of suppressing the conversion of
+ an argument ('*' in scanf), that flag character, otherwise 0. */
+ int suppression_char;
+ /* Flag character to treat a length modifier as (ignored if length
+ modifiers not used). Need not be placed in flag_chars for conversion
+ specifiers, but is used to check for bad combinations such as length
+ modifier with assignment suppression in scanf. */
+ int length_code_char;
+ /* Assignment-allocation flag character ('m' in scanf), otherwise 0. */
+ int alloc_char;
+ /* Pointer to type of argument expected if '*' is used for a width,
+ or NULL if '*' not used for widths. */
+ tree *width_type;
+ /* Pointer to type of argument expected if '*' is used for a precision,
+ or NULL if '*' not used for precisions. */
+ tree *precision_type;
+};
+
+#define T_I &integer_type_node
+#define T89_I { STD_C89, NULL, T_I }
+#define T_L &long_integer_type_node
+#define T89_L { STD_C89, NULL, T_L }
+#define T_LL &long_long_integer_type_node
+#define T9L_LL { STD_C9L, NULL, T_LL }
+#define TEX_LL { STD_EXT, NULL, T_LL }
+#define T_U64 &local_uint64_t_node
+#define TEX_U64 { STD_EXT, "uint64_t", T_U64 }
+#define T_S64 &local_int64_t_node
+#define TEX_S64 { STD_EXT, "int64_t", T_S64 }
+#define T_S &short_integer_type_node
+#define T89_S { STD_C89, NULL, T_S }
+#define T_UI &unsigned_type_node
+#define T89_UI { STD_C89, NULL, T_UI }
+#define T_UL &long_unsigned_type_node
+#define T89_UL { STD_C89, NULL, T_UL }
+#define T_ULL &long_long_unsigned_type_node
+#define T9L_ULL { STD_C9L, NULL, T_ULL }
+#define TEX_ULL { STD_EXT, NULL, T_ULL }
+#define T_US &short_unsigned_type_node
+#define T89_US { STD_C89, NULL, T_US }
+#define T_F &float_type_node
+#define T89_F { STD_C89, NULL, T_F }
+#define T99_F { STD_C99, NULL, T_F }
+#define T_D &double_type_node
+#define T89_D { STD_C89, NULL, T_D }
+#define T99_D { STD_C99, NULL, T_D }
+#define T_LD &long_double_type_node
+#define T89_LD { STD_C89, NULL, T_LD }
+#define T99_LD { STD_C99, NULL, T_LD }
+#define T_C &char_type_node
+#define T89_C { STD_C89, NULL, T_C }
+#define T_SC &signed_char_type_node
+#define T99_SC { STD_C99, NULL, T_SC }
+#define T_UC &unsigned_char_type_node
+#define T99_UC { STD_C99, NULL, T_UC }
+#define T_V &void_type_node
+#define T89_G { STD_C89, NULL, &local_gimple_ptr_node }
+#define T89_T { STD_C89, NULL, &local_tree_type_node }
+#define T89_V { STD_C89, NULL, T_V }
+#define T_W &wchar_type_node
+#define T94_W { STD_C94, "wchar_t", T_W }
+#define TEX_W { STD_EXT, "wchar_t", T_W }
+#define T_WI &wint_type_node
+#define T94_WI { STD_C94, "wint_t", T_WI }
+#define TEX_WI { STD_EXT, "wint_t", T_WI }
+#define T_ST &local_size_t_node
+#define T99_ST { STD_C99, "size_t", T_ST }
+#define T_SST &local_ssize_t_node
+#define T99_SST { STD_C99, "ssize_t", T_SST }
+#define T_PD &ptrdiff_type_node
+#define T99_PD { STD_C99, "ptrdiff_t", T_PD }
+#define T_UPD &unsigned_ptrdiff_type_node
+#define T99_UPD { STD_C99, "unsigned ptrdiff_t", T_UPD }
+#define T_IM &intmax_type_node
+#define T99_IM { STD_C99, "intmax_t", T_IM }
+#define T_UIM &uintmax_type_node
+#define T99_UIM { STD_C99, "uintmax_t", T_UIM }
+#define T_D32 &dfloat32_type_node
+#define TEX_D32 { STD_EXT, "_Decimal32", T_D32 }
+#define T_D64 &dfloat64_type_node
+#define TEX_D64 { STD_EXT, "_Decimal64", T_D64 }
+#define T_D128 &dfloat128_type_node
+#define TEX_D128 { STD_EXT, "_Decimal128", T_D128 }
+
+/* Structure describing how format attributes such as "printf" are
+ interpreted as "gnu_printf" or "ms_printf" on a particular system.
+ TARGET_OVERRIDES_FORMAT_ATTRIBUTES is used to specify target-specific
+ defaults. */
+struct target_ovr_attr
+{
+ /* The name of the to be copied format attribute. */
+ const char *named_attr_src;
+ /* The name of the to be overridden format attribute. */
+ const char *named_attr_dst;
+};
+
+#endif /* GCC_C_FORMAT_H */
diff --git a/tools/gcc-plugins/gcc-common.h b/tools/gcc-plugins/gcc-common.h
new file mode 100644
index 0000000..6eaea9b
--- /dev/null
+++ b/tools/gcc-plugins/gcc-common.h
@@ -0,0 +1,994 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+
+/* FRR: imported from Linux kernel on 2020-07-14 */
+
+#ifndef GCC_COMMON_H_INCLUDED
+#define GCC_COMMON_H_INCLUDED
+
+#include "bversion.h"
+#if BUILDING_GCC_VERSION >= 6000
+#include "gcc-plugin.h"
+#else
+#include "plugin.h"
+#endif
+#include "plugin-version.h"
+#include "config.h"
+#include "system.h"
+#include "coretypes.h"
+#include "tm.h"
+#include "line-map.h"
+#include "input.h"
+#include "tree.h"
+
+#include "tree-inline.h"
+#include "version.h"
+#include "rtl.h"
+#include "tm_p.h"
+#include "flags.h"
+#include "hard-reg-set.h"
+#include "output.h"
+#include "except.h"
+#include "function.h"
+#include "toplev.h"
+#if BUILDING_GCC_VERSION >= 5000
+#include "expr.h"
+#endif
+#include "basic-block.h"
+#include "intl.h"
+#include "ggc.h"
+#include "timevar.h"
+
+#if BUILDING_GCC_VERSION < 10000
+#include "params.h"
+#endif
+
+#if BUILDING_GCC_VERSION <= 4009
+#include "pointer-set.h"
+#else
+#include "hash-map.h"
+#endif
+
+#if BUILDING_GCC_VERSION >= 7000
+#include "memmodel.h"
+#endif
+#include "emit-rtl.h"
+#include "debug.h"
+#include "target.h"
+#include "langhooks.h"
+#include "cfgloop.h"
+#include "cgraph.h"
+#include "opts.h"
+
+#if BUILDING_GCC_VERSION == 4005
+#include <sys/mman.h>
+#endif
+
+#if BUILDING_GCC_VERSION >= 4007
+#include "tree-pretty-print.h"
+#include "gimple-pretty-print.h"
+#endif
+
+#if BUILDING_GCC_VERSION >= 4006
+/*
+ * The c-family headers were moved into a subdirectory in GCC version
+ * 4.7, but most plugin-building users of GCC 4.6 are using the Debian
+ * or Ubuntu package, which has an out-of-tree patch to move this to the
+ * same location as found in 4.7 and later:
+ * https://sources.debian.net/src/gcc-4.6/4.6.3-14/debian/patches/pr45078.diff/
+ */
+#include "c-family/c-common.h"
+#else
+#include "c-common.h"
+#endif
+
+#if BUILDING_GCC_VERSION <= 4008
+#include "tree-flow.h"
+#else
+#include "tree-cfgcleanup.h"
+#include "tree-ssa-operands.h"
+#include "tree-into-ssa.h"
+#endif
+
+#if BUILDING_GCC_VERSION >= 4008
+#include "is-a.h"
+#endif
+
+#include "diagnostic.h"
+#include "tree-dump.h"
+#include "tree-pass.h"
+#if BUILDING_GCC_VERSION >= 4009
+#include "pass_manager.h"
+#endif
+#include "predict.h"
+#include "ipa-utils.h"
+
+#if BUILDING_GCC_VERSION >= 8000
+#include "stringpool.h"
+#endif
+
+#if BUILDING_GCC_VERSION >= 4009
+#include "attribs.h"
+#include "varasm.h"
+#include "stor-layout.h"
+#include "internal-fn.h"
+#if BUILDING_GCC_VERSION >= 13000
+#include "gimple.h"
+#include "gimple-iterator.h"
+#endif
+#include "gimple-expr.h"
+#include "gimple-fold.h"
+#include "context.h"
+#include "tree-ssa-alias.h"
+#include "tree-ssa.h"
+#include "stringpool.h"
+#if BUILDING_GCC_VERSION >= 7000
+#include "tree-vrp.h"
+#endif
+#include "tree-ssanames.h"
+#include "print-tree.h"
+#include "tree-eh.h"
+#include "stmt.h"
+#include "gimplify.h"
+#endif
+
+#include "gimple.h"
+
+#if BUILDING_GCC_VERSION >= 4009
+#include "tree-ssa-operands.h"
+#include "tree-phinodes.h"
+#include "tree-cfg.h"
+#include "gimple-iterator.h"
+#include "gimple-ssa.h"
+#include "ssa-iterators.h"
+#endif
+
+#if BUILDING_GCC_VERSION >= 5000
+#include "builtins.h"
+#endif
+
+/* missing from basic_block.h... */
+void debug_dominance_info(enum cdi_direction dir);
+void debug_dominance_tree(enum cdi_direction dir, basic_block root);
+
+#if BUILDING_GCC_VERSION == 4006
+void debug_gimple_stmt(gimple);
+void debug_gimple_seq(gimple_seq);
+void print_gimple_seq(FILE *, gimple_seq, int, int);
+void print_gimple_stmt(FILE *, gimple, int, int);
+void print_gimple_expr(FILE *, gimple, int, int);
+void dump_gimple_stmt(pretty_printer *, gimple, int, int);
+#endif
+
+#ifndef __unused
+#define __unused __attribute__((__unused__))
+#endif
+#ifndef __visible
+#define __visible __attribute__((visibility("default")))
+#endif
+
+#define DECL_NAME_POINTER(node) IDENTIFIER_POINTER(DECL_NAME(node))
+#define DECL_NAME_LENGTH(node) IDENTIFIER_LENGTH(DECL_NAME(node))
+#define TYPE_NAME_POINTER(node) IDENTIFIER_POINTER(TYPE_NAME(node))
+#define TYPE_NAME_LENGTH(node) IDENTIFIER_LENGTH(TYPE_NAME(node))
+
+#if BUILDING_GCC_VERSION < 9000
+/* should come from c-tree.h if only it were installed for gcc 4.5... */
+#define C_TYPE_FIELDS_READONLY(TYPE) TREE_LANG_FLAG_1(TYPE)
+#endif
+
+static inline tree build_const_char_string(int len, const char *str)
+{
+ tree cstr, elem, index, type;
+
+ cstr = build_string(len, str);
+ elem = build_type_variant(char_type_node, 1, 0);
+ index = build_index_type(size_int(len - 1));
+ type = build_array_type(elem, index);
+ TREE_TYPE(cstr) = type;
+ TREE_CONSTANT(cstr) = 1;
+ TREE_READONLY(cstr) = 1;
+ TREE_STATIC(cstr) = 1;
+ return cstr;
+}
+
+#define PASS_INFO(NAME, REF, ID, POS) \
+struct register_pass_info NAME##_pass_info = { \
+ .pass = make_##NAME##_pass(), \
+ .reference_pass_name = REF, \
+ .ref_pass_instance_number = ID, \
+ .pos_op = POS, \
+}
+
+#if BUILDING_GCC_VERSION == 4005
+#define FOR_EACH_LOCAL_DECL(FUN, I, D) \
+ for (tree vars = (FUN)->local_decls, (I) = 0; \
+ vars && ((D) = TREE_VALUE(vars)); \
+ vars = TREE_CHAIN(vars), (I)++)
+#define DECL_CHAIN(NODE) (TREE_CHAIN(DECL_MINIMAL_CHECK(NODE)))
+#define FOR_EACH_VEC_ELT(T, V, I, P) \
+ for (I = 0; VEC_iterate(T, (V), (I), (P)); ++(I))
+#define TODO_rebuild_cgraph_edges 0
+#define SCOPE_FILE_SCOPE_P(EXP) (!(EXP))
+
+#ifndef O_BINARY
+#define O_BINARY 0
+#endif
+
+typedef struct varpool_node *varpool_node_ptr;
+
+static inline bool gimple_call_builtin_p(gimple stmt, enum built_in_function code)
+{
+ tree fndecl;
+
+ if (!is_gimple_call(stmt))
+ return false;
+ fndecl = gimple_call_fndecl(stmt);
+ if (!fndecl || DECL_BUILT_IN_CLASS(fndecl) != BUILT_IN_NORMAL)
+ return false;
+ return DECL_FUNCTION_CODE(fndecl) == code;
+}
+
+static inline bool is_simple_builtin(tree decl)
+{
+ if (decl && DECL_BUILT_IN_CLASS(decl) != BUILT_IN_NORMAL)
+ return false;
+
+ switch (DECL_FUNCTION_CODE(decl)) {
+ /* Builtins that expand to constants. */
+ case BUILT_IN_CONSTANT_P:
+ case BUILT_IN_EXPECT:
+ case BUILT_IN_OBJECT_SIZE:
+ case BUILT_IN_UNREACHABLE:
+ /* Simple register moves or loads from stack. */
+ case BUILT_IN_RETURN_ADDRESS:
+ case BUILT_IN_EXTRACT_RETURN_ADDR:
+ case BUILT_IN_FROB_RETURN_ADDR:
+ case BUILT_IN_RETURN:
+ case BUILT_IN_AGGREGATE_INCOMING_ADDRESS:
+ case BUILT_IN_FRAME_ADDRESS:
+ case BUILT_IN_VA_END:
+ case BUILT_IN_STACK_SAVE:
+ case BUILT_IN_STACK_RESTORE:
+ /* Exception state returns or moves registers around. */
+ case BUILT_IN_EH_FILTER:
+ case BUILT_IN_EH_POINTER:
+ case BUILT_IN_EH_COPY_VALUES:
+ return true;
+
+ default:
+ return false;
+ }
+}
+
+static inline void add_local_decl(struct function *fun, tree d)
+{
+ gcc_assert(TREE_CODE(d) == VAR_DECL);
+ fun->local_decls = tree_cons(NULL_TREE, d, fun->local_decls);
+}
+#endif
+
+#if BUILDING_GCC_VERSION <= 4006
+#define ANY_RETURN_P(rtx) (GET_CODE(rtx) == RETURN)
+#define C_DECL_REGISTER(EXP) DECL_LANG_FLAG_4(EXP)
+#define EDGE_PRESERVE 0ULL
+#define HOST_WIDE_INT_PRINT_HEX_PURE "%" HOST_WIDE_INT_PRINT "x"
+#define flag_fat_lto_objects true
+
+#define get_random_seed(noinit) ({ \
+ unsigned HOST_WIDE_INT seed; \
+ sscanf(get_random_seed(noinit), "%" HOST_WIDE_INT_PRINT "x", &seed); \
+ seed * seed; })
+
+#define int_const_binop(code, arg1, arg2) \
+ int_const_binop((code), (arg1), (arg2), 0)
+
+static inline bool gimple_clobber_p(gimple s __unused)
+{
+ return false;
+}
+
+static inline bool gimple_asm_clobbers_memory_p(const_gimple stmt)
+{
+ unsigned i;
+
+ for (i = 0; i < gimple_asm_nclobbers(stmt); i++) {
+ tree op = gimple_asm_clobber_op(stmt, i);
+
+ if (!strcmp(TREE_STRING_POINTER(TREE_VALUE(op)), "memory"))
+ return true;
+ }
+
+ return false;
+}
+
+static inline tree builtin_decl_implicit(enum built_in_function fncode)
+{
+ return implicit_built_in_decls[fncode];
+}
+
+static inline int ipa_reverse_postorder(struct cgraph_node **order)
+{
+ return cgraph_postorder(order);
+}
+
+static inline struct cgraph_node *cgraph_create_node(tree decl)
+{
+ return cgraph_node(decl);
+}
+
+static inline struct cgraph_node *cgraph_get_create_node(tree decl)
+{
+ struct cgraph_node *node = cgraph_get_node(decl);
+
+ return node ? node : cgraph_node(decl);
+}
+
+static inline bool cgraph_function_with_gimple_body_p(struct cgraph_node *node)
+{
+ return node->analyzed && !node->thunk.thunk_p && !node->alias;
+}
+
+static inline struct cgraph_node *cgraph_first_function_with_gimple_body(void)
+{
+ struct cgraph_node *node;
+
+ for (node = cgraph_nodes; node; node = node->next)
+ if (cgraph_function_with_gimple_body_p(node))
+ return node;
+ return NULL;
+}
+
+static inline struct cgraph_node *cgraph_next_function_with_gimple_body(struct cgraph_node *node)
+{
+ for (node = node->next; node; node = node->next)
+ if (cgraph_function_with_gimple_body_p(node))
+ return node;
+ return NULL;
+}
+
+static inline bool cgraph_for_node_and_aliases(cgraph_node_ptr node, bool (*callback)(cgraph_node_ptr, void *), void *data, bool include_overwritable)
+{
+ cgraph_node_ptr alias;
+
+ if (callback(node, data))
+ return true;
+
+ for (alias = node->same_body; alias; alias = alias->next) {
+ if (include_overwritable || cgraph_function_body_availability(alias) > AVAIL_OVERWRITABLE)
+ if (cgraph_for_node_and_aliases(alias, callback, data, include_overwritable))
+ return true;
+ }
+
+ return false;
+}
+
+#define FOR_EACH_FUNCTION_WITH_GIMPLE_BODY(node) \
+ for ((node) = cgraph_first_function_with_gimple_body(); (node); \
+ (node) = cgraph_next_function_with_gimple_body(node))
+
+static inline void varpool_add_new_variable(tree decl)
+{
+ varpool_finalize_decl(decl);
+}
+#endif
+
+#if BUILDING_GCC_VERSION <= 4007
+#define FOR_EACH_FUNCTION(node) \
+ for (node = cgraph_nodes; node; node = node->next)
+#define FOR_EACH_VARIABLE(node) \
+ for (node = varpool_nodes; node; node = node->next)
+#define PROP_loops 0
+#define NODE_SYMBOL(node) (node)
+#define NODE_DECL(node) (node)->decl
+#define INSN_LOCATION(INSN) RTL_LOCATION(INSN)
+#define vNULL NULL
+
+static inline int bb_loop_depth(const_basic_block bb)
+{
+ return bb->loop_father ? loop_depth(bb->loop_father) : 0;
+}
+
+static inline bool gimple_store_p(gimple gs)
+{
+ tree lhs = gimple_get_lhs(gs);
+
+ return lhs && !is_gimple_reg(lhs);
+}
+
+static inline void gimple_init_singleton(gimple g __unused)
+{
+}
+#endif
+
+#if BUILDING_GCC_VERSION == 4007 || BUILDING_GCC_VERSION == 4008
+static inline struct cgraph_node *cgraph_alias_target(struct cgraph_node *n)
+{
+ return cgraph_alias_aliased_node(n);
+}
+#endif
+
+#if BUILDING_GCC_VERSION <= 4008
+#define ENTRY_BLOCK_PTR_FOR_FN(FN) ENTRY_BLOCK_PTR_FOR_FUNCTION(FN)
+#define EXIT_BLOCK_PTR_FOR_FN(FN) EXIT_BLOCK_PTR_FOR_FUNCTION(FN)
+#define basic_block_info_for_fn(FN) ((FN)->cfg->x_basic_block_info)
+#define n_basic_blocks_for_fn(FN) ((FN)->cfg->x_n_basic_blocks)
+#define n_edges_for_fn(FN) ((FN)->cfg->x_n_edges)
+#define last_basic_block_for_fn(FN) ((FN)->cfg->x_last_basic_block)
+#define label_to_block_map_for_fn(FN) ((FN)->cfg->x_label_to_block_map)
+#define profile_status_for_fn(FN) ((FN)->cfg->x_profile_status)
+#define BASIC_BLOCK_FOR_FN(FN, N) BASIC_BLOCK_FOR_FUNCTION((FN), (N))
+#define NODE_IMPLICIT_ALIAS(node) (node)->same_body_alias
+#define VAR_P(NODE) (TREE_CODE(NODE) == VAR_DECL)
+
+static inline bool tree_fits_shwi_p(const_tree t)
+{
+ if (t == NULL_TREE || TREE_CODE(t) != INTEGER_CST)
+ return false;
+
+ if (TREE_INT_CST_HIGH(t) == 0 && (HOST_WIDE_INT)TREE_INT_CST_LOW(t) >= 0)
+ return true;
+
+ if (TREE_INT_CST_HIGH(t) == -1 && (HOST_WIDE_INT)TREE_INT_CST_LOW(t) < 0 && !TYPE_UNSIGNED(TREE_TYPE(t)))
+ return true;
+
+ return false;
+}
+
+static inline bool tree_fits_uhwi_p(const_tree t)
+{
+ if (t == NULL_TREE || TREE_CODE(t) != INTEGER_CST)
+ return false;
+
+ return TREE_INT_CST_HIGH(t) == 0;
+}
+
+static inline HOST_WIDE_INT tree_to_shwi(const_tree t)
+{
+ gcc_assert(tree_fits_shwi_p(t));
+ return TREE_INT_CST_LOW(t);
+}
+
+static inline unsigned HOST_WIDE_INT tree_to_uhwi(const_tree t)
+{
+ gcc_assert(tree_fits_uhwi_p(t));
+ return TREE_INT_CST_LOW(t);
+}
+
+static inline const char *get_tree_code_name(enum tree_code code)
+{
+ gcc_assert(code < MAX_TREE_CODES);
+ return tree_code_name[code];
+}
+
+#define ipa_remove_stmt_references(cnode, stmt)
+
+typedef union gimple_statement_d gasm;
+typedef union gimple_statement_d gassign;
+typedef union gimple_statement_d gcall;
+typedef union gimple_statement_d gcond;
+typedef union gimple_statement_d gdebug;
+typedef union gimple_statement_d ggoto;
+typedef union gimple_statement_d gphi;
+typedef union gimple_statement_d greturn;
+
+static inline gasm *as_a_gasm(gimple stmt)
+{
+ return stmt;
+}
+
+static inline const gasm *as_a_const_gasm(const_gimple stmt)
+{
+ return stmt;
+}
+
+static inline gassign *as_a_gassign(gimple stmt)
+{
+ return stmt;
+}
+
+static inline const gassign *as_a_const_gassign(const_gimple stmt)
+{
+ return stmt;
+}
+
+static inline gcall *as_a_gcall(gimple stmt)
+{
+ return stmt;
+}
+
+static inline const gcall *as_a_const_gcall(const_gimple stmt)
+{
+ return stmt;
+}
+
+static inline gcond *as_a_gcond(gimple stmt)
+{
+ return stmt;
+}
+
+static inline const gcond *as_a_const_gcond(const_gimple stmt)
+{
+ return stmt;
+}
+
+static inline gdebug *as_a_gdebug(gimple stmt)
+{
+ return stmt;
+}
+
+static inline const gdebug *as_a_const_gdebug(const_gimple stmt)
+{
+ return stmt;
+}
+
+static inline ggoto *as_a_ggoto(gimple stmt)
+{
+ return stmt;
+}
+
+static inline const ggoto *as_a_const_ggoto(const_gimple stmt)
+{
+ return stmt;
+}
+
+static inline gphi *as_a_gphi(gimple stmt)
+{
+ return stmt;
+}
+
+static inline const gphi *as_a_const_gphi(const_gimple stmt)
+{
+ return stmt;
+}
+
+static inline greturn *as_a_greturn(gimple stmt)
+{
+ return stmt;
+}
+
+static inline const greturn *as_a_const_greturn(const_gimple stmt)
+{
+ return stmt;
+}
+#endif
+
+#if BUILDING_GCC_VERSION == 4008
+#define NODE_SYMBOL(node) (&(node)->symbol)
+#define NODE_DECL(node) (node)->symbol.decl
+#endif
+
+#if BUILDING_GCC_VERSION >= 4008
+#define add_referenced_var(var)
+#define mark_sym_for_renaming(var)
+#define varpool_mark_needed_node(node)
+#define create_var_ann(var)
+#define TODO_dump_func 0
+#define TODO_dump_cgraph 0
+#endif
+
+#if BUILDING_GCC_VERSION <= 4009
+#define TODO_verify_il 0
+#define AVAIL_INTERPOSABLE AVAIL_OVERWRITABLE
+
+#define section_name_prefix LTO_SECTION_NAME_PREFIX
+#define fatal_error(loc, gmsgid, ...) fatal_error((gmsgid), __VA_ARGS__)
+
+rtx emit_move_insn(rtx x, rtx y);
+
+typedef struct rtx_def rtx_insn;
+
+static inline const char *get_decl_section_name(const_tree decl)
+{
+ if (DECL_SECTION_NAME(decl) == NULL_TREE)
+ return NULL;
+
+ return TREE_STRING_POINTER(DECL_SECTION_NAME(decl));
+}
+
+static inline void set_decl_section_name(tree node, const char *value)
+{
+ if (value)
+ DECL_SECTION_NAME(node) = build_string(strlen(value) + 1, value);
+ else
+ DECL_SECTION_NAME(node) = NULL;
+}
+#endif
+
+#if BUILDING_GCC_VERSION == 4009
+typedef struct gimple_statement_asm gasm;
+typedef struct gimple_statement_base gassign;
+typedef struct gimple_statement_call gcall;
+typedef struct gimple_statement_base gcond;
+typedef struct gimple_statement_base gdebug;
+typedef struct gimple_statement_base ggoto;
+typedef struct gimple_statement_phi gphi;
+typedef struct gimple_statement_base greturn;
+
+static inline gasm *as_a_gasm(gimple stmt)
+{
+ return as_a<gasm>(stmt);
+}
+
+static inline const gasm *as_a_const_gasm(const_gimple stmt)
+{
+ return as_a<const gasm>(stmt);
+}
+
+static inline gassign *as_a_gassign(gimple stmt)
+{
+ return stmt;
+}
+
+static inline const gassign *as_a_const_gassign(const_gimple stmt)
+{
+ return stmt;
+}
+
+static inline gcall *as_a_gcall(gimple stmt)
+{
+ return as_a<gcall>(stmt);
+}
+
+static inline const gcall *as_a_const_gcall(const_gimple stmt)
+{
+ return as_a<const gcall>(stmt);
+}
+
+static inline gcond *as_a_gcond(gimple stmt)
+{
+ return stmt;
+}
+
+static inline const gcond *as_a_const_gcond(const_gimple stmt)
+{
+ return stmt;
+}
+
+static inline gdebug *as_a_gdebug(gimple stmt)
+{
+ return stmt;
+}
+
+static inline const gdebug *as_a_const_gdebug(const_gimple stmt)
+{
+ return stmt;
+}
+
+static inline ggoto *as_a_ggoto(gimple stmt)
+{
+ return stmt;
+}
+
+static inline const ggoto *as_a_const_ggoto(const_gimple stmt)
+{
+ return stmt;
+}
+
+static inline gphi *as_a_gphi(gimple stmt)
+{
+ return as_a<gphi>(stmt);
+}
+
+static inline const gphi *as_a_const_gphi(const_gimple stmt)
+{
+ return as_a<const gphi>(stmt);
+}
+
+static inline greturn *as_a_greturn(gimple stmt)
+{
+ return stmt;
+}
+
+static inline const greturn *as_a_const_greturn(const_gimple stmt)
+{
+ return stmt;
+}
+#endif
+
+#if BUILDING_GCC_VERSION >= 4009
+#define TODO_ggc_collect 0
+#define NODE_SYMBOL(node) (node)
+#define NODE_DECL(node) (node)->decl
+#define cgraph_node_name(node) (node)->name()
+#define NODE_IMPLICIT_ALIAS(node) (node)->cpp_implicit_alias
+
+static inline opt_pass *get_pass_for_id(int id)
+{
+ return g->get_passes()->get_pass_for_id(id);
+}
+#endif
+
+#if BUILDING_GCC_VERSION >= 5000 && BUILDING_GCC_VERSION < 6000
+/* gimple related */
+template <>
+template <>
+inline bool is_a_helper<const gassign *>::test(const_gimple gs)
+{
+ return gs->code == GIMPLE_ASSIGN;
+}
+#endif
+
+#if BUILDING_GCC_VERSION >= 5000
+#define TODO_verify_ssa TODO_verify_il
+#define TODO_verify_flow TODO_verify_il
+#define TODO_verify_stmts TODO_verify_il
+#define TODO_verify_rtl_sharing TODO_verify_il
+
+#define INSN_DELETED_P(insn) (insn)->deleted()
+
+static inline const char *get_decl_section_name(const_tree decl)
+{
+ return DECL_SECTION_NAME(decl);
+}
+
+/* symtab/cgraph related */
+#define debug_cgraph_node(node) (node)->debug()
+#define cgraph_get_node(decl) cgraph_node::get(decl)
+#define cgraph_get_create_node(decl) cgraph_node::get_create(decl)
+#define cgraph_create_node(decl) cgraph_node::create(decl)
+#define cgraph_n_nodes symtab->cgraph_count
+#define cgraph_max_uid symtab->cgraph_max_uid
+#define varpool_get_node(decl) varpool_node::get(decl)
+#define dump_varpool_node(file, node) (node)->dump(file)
+
+#if BUILDING_GCC_VERSION >= 8000
+#define cgraph_create_edge(caller, callee, call_stmt, count, freq) \
+ (caller)->create_edge((callee), (call_stmt), (count))
+
+#define cgraph_create_edge_including_clones(caller, callee, \
+ old_call_stmt, call_stmt, count, freq, reason) \
+ (caller)->create_edge_including_clones((callee), \
+ (old_call_stmt), (call_stmt), (count), (reason))
+#else
+#define cgraph_create_edge(caller, callee, call_stmt, count, freq) \
+ (caller)->create_edge((callee), (call_stmt), (count), (freq))
+
+#define cgraph_create_edge_including_clones(caller, callee, \
+ old_call_stmt, call_stmt, count, freq, reason) \
+ (caller)->create_edge_including_clones((callee), \
+ (old_call_stmt), (call_stmt), (count), (freq), (reason))
+#endif
+
+typedef struct cgraph_node *cgraph_node_ptr;
+typedef struct cgraph_edge *cgraph_edge_p;
+typedef struct varpool_node *varpool_node_ptr;
+
+static inline void change_decl_assembler_name(tree decl, tree name)
+{
+ symtab->change_decl_assembler_name(decl, name);
+}
+
+static inline void varpool_finalize_decl(tree decl)
+{
+ varpool_node::finalize_decl(decl);
+}
+
+static inline void varpool_add_new_variable(tree decl)
+{
+ varpool_node::add(decl);
+}
+
+static inline unsigned int rebuild_cgraph_edges(void)
+{
+ return cgraph_edge::rebuild_edges();
+}
+
+static inline cgraph_node_ptr cgraph_function_node(cgraph_node_ptr node, enum availability *availability)
+{
+ return node->function_symbol(availability);
+}
+
+static inline cgraph_node_ptr cgraph_function_or_thunk_node(cgraph_node_ptr node, enum availability *availability = NULL)
+{
+ return node->ultimate_alias_target(availability);
+}
+
+static inline bool cgraph_only_called_directly_p(cgraph_node_ptr node)
+{
+ return node->only_called_directly_p();
+}
+
+static inline enum availability cgraph_function_body_availability(cgraph_node_ptr node)
+{
+ return node->get_availability();
+}
+
+static inline cgraph_node_ptr cgraph_alias_target(cgraph_node_ptr node)
+{
+ return node->get_alias_target();
+}
+
+static inline bool cgraph_for_node_and_aliases(cgraph_node_ptr node, bool (*callback)(cgraph_node_ptr, void *), void *data, bool include_overwritable)
+{
+ return node->call_for_symbol_thunks_and_aliases(callback, data, include_overwritable);
+}
+
+static inline struct cgraph_node_hook_list *cgraph_add_function_insertion_hook(cgraph_node_hook hook, void *data)
+{
+ return symtab->add_cgraph_insertion_hook(hook, data);
+}
+
+static inline void cgraph_remove_function_insertion_hook(struct cgraph_node_hook_list *entry)
+{
+ symtab->remove_cgraph_insertion_hook(entry);
+}
+
+static inline struct cgraph_node_hook_list *cgraph_add_node_removal_hook(cgraph_node_hook hook, void *data)
+{
+ return symtab->add_cgraph_removal_hook(hook, data);
+}
+
+static inline void cgraph_remove_node_removal_hook(struct cgraph_node_hook_list *entry)
+{
+ symtab->remove_cgraph_removal_hook(entry);
+}
+
+static inline struct cgraph_2node_hook_list *cgraph_add_node_duplication_hook(cgraph_2node_hook hook, void *data)
+{
+ return symtab->add_cgraph_duplication_hook(hook, data);
+}
+
+static inline void cgraph_remove_node_duplication_hook(struct cgraph_2node_hook_list *entry)
+{
+ symtab->remove_cgraph_duplication_hook(entry);
+}
+
+static inline void cgraph_call_node_duplication_hooks(cgraph_node_ptr node, cgraph_node_ptr node2)
+{
+ symtab->call_cgraph_duplication_hooks(node, node2);
+}
+
+static inline void cgraph_call_edge_duplication_hooks(cgraph_edge *cs1, cgraph_edge *cs2)
+{
+ symtab->call_edge_duplication_hooks(cs1, cs2);
+}
+
+#if BUILDING_GCC_VERSION >= 6000
+typedef gimple *gimple_ptr;
+typedef const gimple *const_gimple_ptr;
+#define gimple gimple_ptr
+#define const_gimple const_gimple_ptr
+#undef CONST_CAST_GIMPLE
+#define CONST_CAST_GIMPLE(X) CONST_CAST(gimple, (X))
+#endif
+
+/* gimple related */
+static inline gimple gimple_build_assign_with_ops(enum tree_code subcode, tree lhs, tree op1, tree op2 MEM_STAT_DECL)
+{
+ return gimple_build_assign(lhs, subcode, op1, op2 PASS_MEM_STAT);
+}
+
+#if BUILDING_GCC_VERSION < 10000
+template <>
+template <>
+inline bool is_a_helper<const ggoto *>::test(const_gimple gs)
+{
+ return gs->code == GIMPLE_GOTO;
+}
+
+template <>
+template <>
+inline bool is_a_helper<const greturn *>::test(const_gimple gs)
+{
+ return gs->code == GIMPLE_RETURN;
+}
+#endif
+
+static inline gasm *as_a_gasm(gimple stmt)
+{
+ return as_a<gasm *>(stmt);
+}
+
+static inline const gasm *as_a_const_gasm(const_gimple stmt)
+{
+ return as_a<const gasm *>(stmt);
+}
+
+static inline gassign *as_a_gassign(gimple stmt)
+{
+ return as_a<gassign *>(stmt);
+}
+
+static inline const gassign *as_a_const_gassign(const_gimple stmt)
+{
+ return as_a<const gassign *>(stmt);
+}
+
+static inline gcall *as_a_gcall(gimple stmt)
+{
+ return as_a<gcall *>(stmt);
+}
+
+static inline const gcall *as_a_const_gcall(const_gimple stmt)
+{
+ return as_a<const gcall *>(stmt);
+}
+
+static inline ggoto *as_a_ggoto(gimple stmt)
+{
+ return as_a<ggoto *>(stmt);
+}
+
+static inline const ggoto *as_a_const_ggoto(const_gimple stmt)
+{
+ return as_a<const ggoto *>(stmt);
+}
+
+static inline gphi *as_a_gphi(gimple stmt)
+{
+ return as_a<gphi *>(stmt);
+}
+
+static inline const gphi *as_a_const_gphi(const_gimple stmt)
+{
+ return as_a<const gphi *>(stmt);
+}
+
+static inline greturn *as_a_greturn(gimple stmt)
+{
+ return as_a<greturn *>(stmt);
+}
+
+static inline const greturn *as_a_const_greturn(const_gimple stmt)
+{
+ return as_a<const greturn *>(stmt);
+}
+
+/* IPA/LTO related */
+#define ipa_ref_list_referring_iterate(L, I, P) \
+ (L)->referring.iterate((I), &(P))
+#define ipa_ref_list_reference_iterate(L, I, P) \
+ (L)->reference.iterate((I), &(P))
+
+static inline cgraph_node_ptr ipa_ref_referring_node(struct ipa_ref *ref)
+{
+ return dyn_cast<cgraph_node_ptr>(ref->referring);
+}
+
+static inline void ipa_remove_stmt_references(symtab_node *referring_node, gimple stmt)
+{
+ referring_node->remove_stmt_references(stmt);
+}
+#endif
+
+#if BUILDING_GCC_VERSION < 6000
+#define get_inner_reference(exp, pbitsize, pbitpos, poffset, pmode, punsignedp, preversep, pvolatilep, keep_aligning) \
+ get_inner_reference(exp, pbitsize, pbitpos, poffset, pmode, punsignedp, pvolatilep, keep_aligning)
+#define gen_rtx_set(ARG0, ARG1) gen_rtx_SET(VOIDmode, (ARG0), (ARG1))
+#endif
+
+#if BUILDING_GCC_VERSION >= 6000
+#define gen_rtx_set(ARG0, ARG1) gen_rtx_SET((ARG0), (ARG1))
+#endif
+
+#ifdef __cplusplus
+static inline void debug_tree(const_tree t)
+{
+ debug_tree(CONST_CAST_TREE(t));
+}
+
+static inline void debug_gimple_stmt(const_gimple s)
+{
+ debug_gimple_stmt(CONST_CAST_GIMPLE(s));
+}
+#else
+#define debug_tree(t) debug_tree(CONST_CAST_TREE(t))
+#define debug_gimple_stmt(s) debug_gimple_stmt(CONST_CAST_GIMPLE(s))
+#endif
+
+#if BUILDING_GCC_VERSION >= 7000
+#define get_inner_reference(exp, pbitsize, pbitpos, poffset, pmode, punsignedp, preversep, pvolatilep, keep_aligning) \
+ get_inner_reference(exp, pbitsize, pbitpos, poffset, pmode, punsignedp, preversep, pvolatilep)
+#endif
+
+#if BUILDING_GCC_VERSION < 7000
+#define SET_DECL_ALIGN(decl, align) DECL_ALIGN(decl) = (align)
+#define SET_DECL_MODE(decl, mode) DECL_MODE(decl) = (mode)
+#endif
+
+#if BUILDING_GCC_VERSION < 12000
+#define check_function_arguments_recurse(arg, ctx, tree, num, opt) \
+ check_function_arguments_recurse(arg, ctx, tree, num)
+#endif
+
+#endif
diff --git a/tools/gcc-plugins/gcc-retain-typeinfo.patch b/tools/gcc-plugins/gcc-retain-typeinfo.patch
new file mode 100644
index 0000000..ec51f0b
--- /dev/null
+++ b/tools/gcc-plugins/gcc-retain-typeinfo.patch
@@ -0,0 +1,11 @@
+--- a/src/gcc/c/c-typeck.c
++++ b/src/gcc/c/c-typeck.c
+@@ -5716,8 +5716,6 @@ build_c_cast (location_t loc, tree type, tree expr)
+ if (objc_is_object_ptr (type) && objc_is_object_ptr (TREE_TYPE (expr)))
+ return build1 (NOP_EXPR, type, expr);
+
+- type = TYPE_MAIN_VARIANT (type);
+-
+ if (TREE_CODE (type) == ARRAY_TYPE)
+ {
+ error_at (loc, "cast specifies array type");
diff --git a/tools/gen_northbound_callbacks.c b/tools/gen_northbound_callbacks.c
new file mode 100644
index 0000000..5b778a1
--- /dev/null
+++ b/tools/gen_northbound_callbacks.c
@@ -0,0 +1,390 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (C) 2018 NetDEF, Inc.
+ * Renato Westphal
+ */
+
+#define REALLY_NEED_PLAIN_GETOPT 1
+
+#include <zebra.h>
+
+#include <unistd.h>
+
+#include "yang.h"
+#include "northbound.h"
+
+static bool static_cbs;
+
+static void __attribute__((noreturn)) usage(int status)
+{
+ extern const char *__progname;
+ fprintf(stderr, "usage: %s [-h] [-s] [-p path] MODULE\n", __progname);
+ exit(status);
+}
+
+static struct nb_callback_info {
+ int operation;
+ bool optional;
+ char return_type[32];
+ char return_value[32];
+ char arguments[128];
+} nb_callbacks[] = {
+ {
+ .operation = NB_OP_CREATE,
+ .return_type = "int ",
+ .return_value = "NB_OK",
+ .arguments = "struct nb_cb_create_args *args",
+ },
+ {
+ .operation = NB_OP_MODIFY,
+ .return_type = "int ",
+ .return_value = "NB_OK",
+ .arguments = "struct nb_cb_modify_args *args",
+ },
+ {
+ .operation = NB_OP_DESTROY,
+ .return_type = "int ",
+ .return_value = "NB_OK",
+ .arguments = "struct nb_cb_destroy_args *args",
+ },
+ {
+ .operation = NB_OP_MOVE,
+ .return_type = "int ",
+ .return_value = "NB_OK",
+ .arguments = "struct nb_cb_move_args *args",
+ },
+ {
+ .operation = NB_OP_APPLY_FINISH,
+ .optional = true,
+ .return_type = "void ",
+ .return_value = "",
+ .arguments = "struct nb_cb_apply_finish_args *args",
+ },
+ {
+ .operation = NB_OP_GET_ELEM,
+ .return_type = "struct yang_data *",
+ .return_value = "NULL",
+ .arguments = "struct nb_cb_get_elem_args *args",
+ },
+ {
+ .operation = NB_OP_GET_NEXT,
+ .return_type = "const void *",
+ .return_value = "NULL",
+ .arguments = "struct nb_cb_get_next_args *args",
+ },
+ {
+ .operation = NB_OP_GET_KEYS,
+ .return_type = "int ",
+ .return_value = "NB_OK",
+ .arguments = "struct nb_cb_get_keys_args *args",
+ },
+ {
+ .operation = NB_OP_LOOKUP_ENTRY,
+ .return_type = "const void *",
+ .return_value = "NULL",
+ .arguments = "struct nb_cb_lookup_entry_args *args",
+ },
+ {
+ .operation = NB_OP_RPC,
+ .return_type = "int ",
+ .return_value = "NB_OK",
+ .arguments = "struct nb_cb_rpc_args *args",
+ },
+ {
+ /* sentinel */
+ .operation = -1,
+ },
+};
+
+static void replace_hyphens_by_underscores(char *str)
+{
+ char *p;
+
+ p = str;
+ while ((p = strchr(p, '-')) != NULL)
+ *p++ = '_';
+}
+
+static void generate_callback_name(const struct lysc_node *snode,
+ enum nb_operation operation, char *buffer,
+ size_t size)
+{
+ struct list *snodes;
+ struct listnode *ln;
+
+ snodes = list_new();
+ for (; snode; snode = snode->parent) {
+ /* Skip schema-only snodes. */
+ if (CHECK_FLAG(snode->nodetype, LYS_USES | LYS_CHOICE | LYS_CASE
+ | LYS_INPUT
+ | LYS_OUTPUT))
+ continue;
+
+ listnode_add_head(snodes, (void *)snode);
+ }
+
+ memset(buffer, 0, size);
+ for (ALL_LIST_ELEMENTS_RO(snodes, ln, snode)) {
+ strlcat(buffer, snode->name, size);
+ strlcat(buffer, "_", size);
+ }
+ strlcat(buffer, nb_operation_name(operation), size);
+ list_delete(&snodes);
+
+ replace_hyphens_by_underscores(buffer);
+}
+
+static void generate_prototype(const struct nb_callback_info *ncinfo,
+ const char *cb_name)
+{
+ printf("%s%s(%s);\n", ncinfo->return_type, cb_name, ncinfo->arguments);
+}
+
+static int generate_prototypes(const struct lysc_node *snode, void *arg)
+{
+ switch (snode->nodetype) {
+ case LYS_CONTAINER:
+ case LYS_LEAF:
+ case LYS_LEAFLIST:
+ case LYS_LIST:
+ case LYS_NOTIF:
+ case LYS_RPC:
+ break;
+ default:
+ return YANG_ITER_CONTINUE;
+ }
+
+ for (struct nb_callback_info *cb = &nb_callbacks[0];
+ cb->operation != -1; cb++) {
+ char cb_name[BUFSIZ];
+
+ if (cb->optional
+ || !nb_operation_is_valid(cb->operation, snode))
+ continue;
+
+ generate_callback_name(snode, cb->operation, cb_name,
+ sizeof(cb_name));
+ generate_prototype(cb, cb_name);
+ }
+
+ return YANG_ITER_CONTINUE;
+}
+
+static void generate_callback(const struct nb_callback_info *ncinfo,
+ const char *cb_name)
+{
+ printf("%s%s%s(%s)\n{\n", static_cbs ? "static " : "",
+ ncinfo->return_type, cb_name, ncinfo->arguments);
+
+ switch (ncinfo->operation) {
+ case NB_OP_CREATE:
+ case NB_OP_MODIFY:
+ case NB_OP_DESTROY:
+ case NB_OP_MOVE:
+ printf("\tswitch (args->event) {\n"
+ "\tcase NB_EV_VALIDATE:\n"
+ "\tcase NB_EV_PREPARE:\n"
+ "\tcase NB_EV_ABORT:\n"
+ "\tcase NB_EV_APPLY:\n"
+ "\t\t/* TODO: implement me. */\n"
+ "\t\tbreak;\n"
+ "\t}\n\n"
+ );
+ break;
+
+ default:
+ printf("\t/* TODO: implement me. */\n");
+ break;
+ }
+
+ printf("\treturn %s;\n}\n\n", ncinfo->return_value);
+}
+
+static int generate_callbacks(const struct lysc_node *snode, void *arg)
+{
+ bool first = true;
+
+ switch (snode->nodetype) {
+ case LYS_CONTAINER:
+ case LYS_LEAF:
+ case LYS_LEAFLIST:
+ case LYS_LIST:
+ case LYS_NOTIF:
+ case LYS_RPC:
+ break;
+ default:
+ return YANG_ITER_CONTINUE;
+ }
+
+ for (struct nb_callback_info *cb = &nb_callbacks[0];
+ cb->operation != -1; cb++) {
+ char cb_name[BUFSIZ];
+
+ if (cb->optional
+ || !nb_operation_is_valid(cb->operation, snode))
+ continue;
+
+ if (first) {
+ char xpath[XPATH_MAXLEN];
+
+ yang_snode_get_path(snode, YANG_PATH_DATA, xpath,
+ sizeof(xpath));
+
+ printf("/*\n"
+ " * XPath: %s\n"
+ " */\n",
+ xpath);
+ first = false;
+ }
+
+ generate_callback_name(snode, cb->operation, cb_name,
+ sizeof(cb_name));
+ generate_callback(cb, cb_name);
+ }
+
+ return YANG_ITER_CONTINUE;
+}
+
+static int generate_nb_nodes(const struct lysc_node *snode, void *arg)
+{
+ bool first = true;
+
+ switch (snode->nodetype) {
+ case LYS_CONTAINER:
+ case LYS_LEAF:
+ case LYS_LEAFLIST:
+ case LYS_LIST:
+ case LYS_NOTIF:
+ case LYS_RPC:
+ break;
+ default:
+ return YANG_ITER_CONTINUE;
+ }
+
+ for (struct nb_callback_info *cb = &nb_callbacks[0];
+ cb->operation != -1; cb++) {
+ char cb_name[BUFSIZ];
+
+ if (cb->optional
+ || !nb_operation_is_valid(cb->operation, snode))
+ continue;
+
+ if (first) {
+ char xpath[XPATH_MAXLEN];
+
+ yang_snode_get_path(snode, YANG_PATH_DATA, xpath,
+ sizeof(xpath));
+
+ printf("\t\t{\n"
+ "\t\t\t.xpath = \"%s\",\n",
+ xpath);
+ printf("\t\t\t.cbs = {\n");
+ first = false;
+ }
+
+ generate_callback_name(snode, cb->operation, cb_name,
+ sizeof(cb_name));
+ printf("\t\t\t\t.%s = %s,\n", nb_operation_name(cb->operation),
+ cb_name);
+ }
+
+ if (!first) {
+ printf("\t\t\t}\n");
+ printf("\t\t},\n");
+ }
+
+ return YANG_ITER_CONTINUE;
+}
+
+int main(int argc, char *argv[])
+{
+ const char *search_path = NULL;
+ struct yang_module *module;
+ char module_name_underscores[64];
+ struct stat st;
+ int opt;
+
+ while ((opt = getopt(argc, argv, "hp:s")) != -1) {
+ switch (opt) {
+ case 'h':
+ usage(EXIT_SUCCESS);
+ /* NOTREACHED */
+ case 'p':
+ if (stat(optarg, &st) == -1) {
+ fprintf(stderr,
+ "error: invalid search path '%s': %s\n",
+ optarg, strerror(errno));
+ exit(EXIT_FAILURE);
+ }
+ if (S_ISDIR(st.st_mode) == 0) {
+ fprintf(stderr,
+ "error: search path is not directory");
+ exit(EXIT_FAILURE);
+ }
+
+ search_path = optarg;
+ break;
+ case 's':
+ static_cbs = true;
+ break;
+ default:
+ usage(EXIT_FAILURE);
+ /* NOTREACHED */
+ }
+ }
+ argc -= optind;
+ argv += optind;
+ if (argc != 1)
+ usage(EXIT_FAILURE);
+
+ yang_init(false, true);
+
+ if (search_path)
+ ly_ctx_set_searchdir(ly_native_ctx, search_path);
+
+ /* Load all FRR native models to ensure all augmentations are loaded. */
+ yang_module_load_all();
+
+ module = yang_module_find(argv[0]);
+ if (!module)
+ /* Non-native FRR module (e.g. modules from unit tests). */
+ module = yang_module_load(argv[0]);
+
+ yang_init_loading_complete();
+
+ /* Create a nb_node for all YANG schema nodes. */
+ nb_nodes_create();
+
+ /* Generate callback prototypes. */
+ if (!static_cbs) {
+ printf("/* prototypes */\n");
+ yang_snodes_iterate(module->info, generate_prototypes, 0, NULL);
+ printf("\n");
+ }
+
+ /* Generate callback functions. */
+ yang_snodes_iterate(module->info, generate_callbacks, 0, NULL);
+
+ strlcpy(module_name_underscores, module->name,
+ sizeof(module_name_underscores));
+ replace_hyphens_by_underscores(module_name_underscores);
+
+ /* Generate frr_yang_module_info array. */
+ printf("/* clang-format off */\n"
+ "const struct frr_yang_module_info %s_info = {\n"
+ "\t.name = \"%s\",\n"
+ "\t.nodes = {\n",
+ module_name_underscores, module->name);
+ yang_snodes_iterate(module->info, generate_nb_nodes, 0, NULL);
+ printf("\t\t{\n"
+ "\t\t\t.xpath = NULL,\n"
+ "\t\t},\n");
+ printf("\t}\n"
+ "};\n");
+
+ /* Cleanup and exit. */
+ nb_nodes_delete();
+ yang_terminate();
+
+ return 0;
+}
diff --git a/tools/gen_yang_deviations.c b/tools/gen_yang_deviations.c
new file mode 100644
index 0000000..fc9f55b
--- /dev/null
+++ b/tools/gen_yang_deviations.c
@@ -0,0 +1,67 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (C) 2018 NetDEF, Inc.
+ * Renato Westphal
+ */
+
+#define REALLY_NEED_PLAIN_GETOPT 1
+
+#include <zebra.h>
+
+#include <unistd.h>
+
+#include "yang.h"
+#include "northbound.h"
+
+static void __attribute__((noreturn)) usage(int status)
+{
+ fprintf(stderr, "usage: gen_yang_deviations [-h] MODULE\n");
+ exit(status);
+}
+
+static int generate_yang_deviation(const struct lysc_node *snode, void *arg)
+{
+ char xpath[XPATH_MAXLEN];
+
+ yang_snode_get_path(snode, YANG_PATH_SCHEMA, xpath, sizeof(xpath));
+
+ printf(" deviation \"%s\" {\n", xpath);
+ printf(" deviate not-supported;\n");
+ printf(" }\n\n");
+
+ return YANG_ITER_CONTINUE;
+}
+
+int main(int argc, char *argv[])
+{
+ struct yang_module *module;
+ int opt;
+
+ while ((opt = getopt(argc, argv, "h")) != -1) {
+ switch (opt) {
+ case 'h':
+ usage(EXIT_SUCCESS);
+ /* NOTREACHED */
+ default:
+ usage(EXIT_FAILURE);
+ /* NOTREACHED */
+ }
+ }
+ argc -= optind;
+ argv += optind;
+ if (argc != 1)
+ usage(EXIT_FAILURE);
+
+ yang_init(false, false);
+
+ /* Load YANG module. */
+ module = yang_module_load(argv[0]);
+
+ /* Generate deviations. */
+ yang_snodes_iterate(module->info, generate_yang_deviation, 0, NULL);
+
+ /* Cleanup and exit. */
+ yang_terminate();
+
+ return 0;
+}
diff --git a/tools/generate_support_bundle.py b/tools/generate_support_bundle.py
new file mode 100755
index 0000000..04a374d
--- /dev/null
+++ b/tools/generate_support_bundle.py
@@ -0,0 +1,86 @@
+#!/usr/bin/env python3
+# SPDX-License-Identifier: GPL-2.0-or-later
+#
+# Copyright (c) 2021, LabN Consulting, L.L.C.
+#
+
+########################################################
+### Python Script to generate the FRR support bundle ###
+########################################################
+import argparse
+import logging
+import os
+import subprocess
+import tempfile
+
+
+def open_with_backup(path):
+ if os.path.exists(path):
+ print("Making backup of " + path)
+ subprocess.check_call("mv {0} {0}.prev".format(path), shell=True)
+ return open(path, "w")
+
+
+def main():
+ parser = argparse.ArgumentParser()
+ parser.add_argument(
+ "-c",
+ "--config",
+ default="/etc/frr/support_bundle_commands.conf",
+ help="input config",
+ )
+ parser.add_argument(
+ "-l", "--log-dir", default="/var/log/frr", help="directory for logfiles"
+ )
+ args = parser.parse_args()
+
+ collecting = False # file format has sentinels (seem superfluous)
+ proc_cmds = {}
+ proc = None
+ temp = None
+
+ # Collect all the commands for each daemon
+ try:
+ for line in open(args.config):
+ line = line.rstrip()
+ if len(line) == 0 or line[0] == "#":
+ continue
+
+ cmd_line = line.split(":")
+ if cmd_line[0] == "PROC_NAME":
+ proc = cmd_line[1]
+ temp = tempfile.NamedTemporaryFile("w+")
+ collecting = False
+ elif cmd_line[0] == "CMD_LIST_START":
+ collecting = True
+ elif cmd_line[0] == "CMD_LIST_END":
+ collecting = False
+ temp.flush()
+ proc_cmds[proc] = open(temp.name)
+ temp.close()
+ elif collecting:
+ temp.write(line + "\n")
+ else:
+ print("Ignoring unexpected input " + line.rstrip())
+ except IOError as error:
+ logging.fatal("Cannot read config file: %s: %s", args.config, str(error))
+ return
+
+ # Spawn a vtysh to fetch each set of commands
+ procs = []
+ for proc in proc_cmds:
+ ofn = os.path.join(args.log_dir, proc + "_support_bundle.log")
+ p = subprocess.Popen(
+ ["/usr/bin/env", "vtysh", "-t"],
+ stdin=proc_cmds[proc],
+ stdout=open_with_backup(ofn),
+ stderr=subprocess.STDOUT,
+ )
+ procs.append(p)
+
+ for p in procs:
+ p.wait()
+
+
+if __name__ == "__main__":
+ main()
diff --git a/tools/git-reindent-branch.py b/tools/git-reindent-branch.py
new file mode 100644
index 0000000..5f821b0
--- /dev/null
+++ b/tools/git-reindent-branch.py
@@ -0,0 +1,104 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+import sys, os
+import subprocess, argparse, tempfile
+import indent
+
+
+def run(cmd):
+ proc = subprocess.Popen(cmd, stdout=subprocess.PIPE)
+ rv = proc.communicate("")[0].decode("UTF-8")
+ proc.wait()
+ return rv
+
+
+clangfmt = run(["git", "show", "master:.clang-format"])
+
+argp = argparse.ArgumentParser(description="git whitespace-fixing tool")
+argp.add_argument("branch", metavar="BRANCH", type=str, nargs="?", default="HEAD")
+args = argp.parse_args()
+
+branch = args.branch
+commit = run(["git", "rev-list", "-n", "1", branch, "--"]).strip()
+
+# frr-3.1-dev = first commit that is on master but not on stable/3.0
+masterid = run(["git", "rev-list", "-n", "1", "frr-3.1-dev", "--"]).strip()
+masterbase = run(["git", "merge-base", commit, masterid]).strip()
+
+if masterbase == masterid:
+ refbranch = "master"
+else:
+ refbranch = "3.0"
+
+sys.stderr.write("autodetected base: %s (can be 3.0 or master)\n" % refbranch)
+
+beforeid = run(
+ ["git", "rev-list", "-n", "1", "reindent-%s-before" % refbranch, "--"]
+).strip()
+afterid = run(
+ ["git", "rev-list", "-n", "1", "reindent-%s-after" % refbranch, "--"]
+).strip()
+
+beforebase = run(["git", "merge-base", commit, beforeid]).strip()
+afterbase = run(["git", "merge-base", commit, afterid]).strip()
+
+if afterbase == afterid:
+ sys.stderr.write("this branch was already rebased\n")
+ sys.exit(1)
+
+if beforebase != beforeid:
+ sys.stderr.write(
+ 'you need to rebase your branch onto the tag "reindent-%s-before"\n' % refbranch
+ )
+ sys.exit(1)
+
+revs = (
+ run(["git", "rev-list", "reindent-%s-before..%s" % (refbranch, commit)])
+ .strip()
+ .split("\n")
+)
+revs.reverse()
+
+srcdir = os.getcwd()
+tmpdir = tempfile.mkdtemp("frrindent")
+os.chdir(tmpdir)
+
+sys.stderr.write("using temporary directory %s; %d revisions\n" % (tmpdir, len(revs)))
+run(["git", "clone", "-s", "-b", "reindent-%s-after" % refbranch, srcdir, "repo"])
+os.chdir("repo")
+
+with open(".clang-format", "w") as fd:
+ fd.write(clangfmt)
+
+prev = beforeid
+for rev in revs:
+ filestat = (
+ run(["git", "diff", "-z", "--name-status", prev, rev]).rstrip("\0").split("\0")
+ )
+ changes = zip(filestat[0::2], filestat[1::2])
+ sys.stderr.write("%s: %d files\n" % (rev, len(changes)))
+
+ for typ, name in changes:
+ if typ == "D":
+ run(["git", "rm", name])
+ elif typ in ["A", "M"]:
+ run(["git", "checkout", rev, "--", name])
+ if name.endswith(".c") or name.endswith(".h"):
+ for d in ["babeld/", "ldpd/", "nhrpd/"]:
+ if name.startswith(d):
+ break
+ else:
+ sys.stderr.write("\t%s\n" % name)
+ indent.wrap_file(name)
+ run(["git", "add", name])
+
+ run(["git", "commit", "-C", rev])
+ prev = rev
+
+run(["git", "push", "origin", "HEAD:refs/heads/reindented-branch"])
+sys.stderr.write('\n\n"reindented-branch" should now be OK.\n')
+sys.stderr.write(
+ 'you could use "git reset --hard reindented-branch" to set your current branch to the reindented output\n'
+)
+sys.stderr.write("\033[31;1mplease always double-check the output\033[m\n")
diff --git a/tools/indent.py b/tools/indent.py
new file mode 100755
index 0000000..fe9eb7c
--- /dev/null
+++ b/tools/indent.py
@@ -0,0 +1,53 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+# SPDX-License-Identifier: NONE
+# 2017 by David Lamparter, placed in public domain
+
+import sys, re, subprocess, os
+
+# find all DEFUNs
+defun_re = re.compile(
+ r"^((DEF(UN(|_ATTR|_CMD_(ELEMENT|FUNC_(DECL|TEXT))|_DEPRECATED|_NOSH|_HIDDEN|SH(|_ATTR|_DEPRECATED|_HIDDEN))?|PY|PY_ATTR|PY_HIDDEN)|ALIAS)\s*\(.*?)^(?=\s*\{)",
+ re.M | re.S,
+)
+define_re = re.compile(r"((^#\s*define[^\n]+[^\\]\n)+)", re.M | re.S)
+# find clang-format control that we just inserted
+clean_re = re.compile(
+ r"^.*/\* \$FRR indent\$ \*/\s*\n\s*/\* clang-format (on|off) \*/\s*\n", re.M
+)
+
+
+def wrap_file(fn):
+ with open(fn, "r") as fd:
+ text = fd.read()
+
+ repl = (
+ r"/* $FRR indent$ */\n/* clang-format off */\n"
+ + r"\1"
+ + r"/* $FRR indent$ */\n/* clang-format on */\n"
+ )
+
+ # around each DEFUN, insert an indent-on/off comment
+ text = defun_re.sub(repl, text)
+ text = define_re.sub(repl, text)
+
+ ci = subprocess.Popen(
+ ["clang-format"], stdin=subprocess.PIPE, stdout=subprocess.PIPE
+ )
+ stdout, ign = ci.communicate(text)
+ ci.wait()
+ if ci.returncode != 0:
+ raise IOError("clang-format returned %d" % (ci.returncode))
+
+ # remove the bits we inserted above
+ final = clean_re.sub("", stdout)
+
+ tmpname = fn + ".indent"
+ with open(tmpname, "w") as ofd:
+ ofd.write(final)
+ os.rename(tmpname, fn)
+
+
+if __name__ == "__main__":
+ for fn in sys.argv[1:]:
+ wrap_file(fn)
diff --git a/tools/lua.scr b/tools/lua.scr
new file mode 100644
index 0000000..db6bf7d
--- /dev/null
+++ b/tools/lua.scr
@@ -0,0 +1,29 @@
+-- Route map functionality
+--
+-- There are no parameters passed in.
+-- Currently we set two global tables
+-- prefix
+-- .family = The v4 or v6 family we are working in
+-- .route = the A.B.C.D/X or Z:A:B::D/X string
+-- nexthop
+-- .metric = The metric we are going to use
+-- .ifindex = The outgoing interface
+-- .aspath = The aspath of the route
+-- .localpref = The localpref value
+--
+-- Valid Return Codes:
+-- 0 - Some sort of processing failure
+-- This turns into a implicit DENY
+-- 1 - No match was found, turns into a DENY
+-- 2 - Match found, turns into a PERMIT
+-- 3 - Match found and data structures changed, turns into a PERMIT
+-- and a reread of the prefix and nexthop tables
+function mooey ()
+ zlog_debug(string.format("Family: %d: %s %d ifindex: %d aspath: %s localpref: %d",
+ prefix.family, prefix.route,
+ nexthop.metric, nexthop.ifindex, nexthop.aspath, nexthop.localpref))
+
+ nexthop.metric = 33
+ nexthop.localpref = 13
+ return 3
+end
diff --git a/tools/mrlg.txt b/tools/mrlg.txt
new file mode 100644
index 0000000..0ebf7ee
--- /dev/null
+++ b/tools/mrlg.txt
@@ -0,0 +1,5 @@
+The Multi-Router Looking Glass (MRLG) CGI script now lives at:
+
+ http://mrlg.op-sec.us/
+
+Please obtain the latest version from there.
diff --git a/tools/multiple-bgpd.sh b/tools/multiple-bgpd.sh
new file mode 100644
index 0000000..6dee1ba
--- /dev/null
+++ b/tools/multiple-bgpd.sh
@@ -0,0 +1,84 @@
+#!/bin/bash
+
+# Public domain, not copyrighted..
+
+NUM=5
+VTYBASE=2610
+ASBASE=64560
+BGPD=/path/to/bgpd
+PREFIX=192.168.145.
+#PREFIX=3ffe:123:456::
+ADDRPLEN=32
+CONFBASE=/tmp
+PIDBASE=/var/run/frr
+CHOWNSTR=frr:frr
+
+for H in `seq 1 ${NUM}` ; do
+ CONF="${CONFBASE}"/bgpd${H}.conf
+ ADDR=${PREFIX}${H}
+
+ if [ ! -e "$CONF" ] ; then
+ # This sets up a ring of bgpd peerings
+ NEXT=$(( ($H % ${NUM}) + 1 ))
+ PREV=$(( (($H + $NUM - 2) % ${NUM}) + 1 ))
+ NEXTADDR="${PREFIX}${NEXT}"
+ NEXTAS=$((${ASBASE} + $NEXT))
+ PREVADDR="${PREFIX}${PREV}"
+ PREVAS=$((${ASBASE} + $PREV))
+ ASN=$((64560+${H}))
+
+ # Edit config to suit.
+ cat > "$CONF" <<- EOF
+ password whatever
+ service advanced-vty
+ !
+ router bgp ${ASN}
+ bgp router-id ${ADDR}
+ network 10.${H}.1.0/24 pathlimit 1
+ network 10.${H}.2.0/24 pathlimit 2
+ network 10.${H}.3.0/24 pathlimit 3
+ neighbor default peer-group
+ neighbor default update-source ${ADDR}
+ neighbor default capability orf prefix-list both
+ neighbor default soft-reconfiguration inbound
+ neighbor default route-map test out
+ neighbor ${NEXTADDR} remote-as ${NEXTAS}
+ neighbor ${NEXTADDR} peer-group default
+ neighbor ${PREVADDR} remote-as ${PREVAS}
+ neighbor ${PREVADDR} peer-group default
+ !
+ address-family ipv6
+ network 3ffe:${H}::/48
+ network 3ffe:${H}:1::/48 pathlimit 1
+ network 3ffe:${H}:2::/48 pathlimit 3
+ network 3ffe:${H}:3::/48 pathlimit 3
+ neighbor default activate
+ neighbor default capability orf prefix-list both
+ neighbor default default-originate
+ neighbor default route-map test out
+ neighbor ${NEXTADDR} peer-group default
+ neighbor ${PREVADDR} peer-group default
+ exit-address-family
+ !
+ ! bgpd still has problems with extcommunity rt/soo
+ route-map test permit 10
+ set extcommunity rt ${ASN}:1
+ set extcommunity soo ${ASN}:2
+ set community ${ASN}:1
+ line vty
+ !
+ end
+ EOF
+ chown ${CHOWNSTR} "$CONF"
+ fi
+ # You may want to automatically add configure a local address
+ # on a loop interface.
+ #
+ # Solaris: ifconfig vni${H} plumb ${ADDR}/${ADDRPLEN} up
+ # Linux: ip address add ${ADDR}/${ADDRPLEN} dev lo 2> /dev/null
+ ${BGPD} -i "${PIDBASE}"/bgpd${H}.pid \
+ -l ${ADDR} \
+ -f "${CONF}" \
+ -P $((${VTYBASE}+${H})) \
+ -d
+done
diff --git a/tools/nhrpd-event-handler.sh b/tools/nhrpd-event-handler.sh
new file mode 100755
index 0000000..e7eaf46
--- /dev/null
+++ b/tools/nhrpd-event-handler.sh
@@ -0,0 +1,203 @@
+#!/bin/bash
+# SPDX-License-Identifier: GPL-2.0-or-later
+
+# Author: Joe Maimon
+# Released to public domain
+#
+
+PROGNAME=`basename $0`
+VERSION="0.0.6"
+#api fields
+EV_ID="eventid"
+EV_TYPE="type"
+EV_OTYPE="old_type"
+EV_NUMNHS="num_nhs"
+EV_INT="interface"
+EV_LADDR="local_address"
+EV_VCINIT="vc_initiated"
+EV_LNBMA="local_nbma"
+EV_LCERT="local_cert"
+EV_RADDR="remote_addr"
+EV_RNBMA="remote_nbma"
+EV_RCERT="remote_cert"
+
+usage()
+{
+ echo "Usage: $PROGNAME [-s nhrp-sock] [-d] [-i interface-name] [-t table] [-e execute-cmd] [-u user] [-g group] [-r] [-l logfile]"
+ echo ""
+ echo "-s nhrp-sock file"
+ echo "-i interface-name to execute on, may be repeated multiple times"
+ echo "-t tableid to execute on for immdiate preceeding interface"
+ echo "-e execute command for immmediate preceeding interface"
+ echo " The command will be passed the following arguments $EV_ID $EV_TYPE $EV_INT $EV_LNMBA $EV_RADDR $EV_RNBMA int_table"
+ echo "-u user to own the sock"
+ echo "-g group to own the sock"
+ echo "-r send rejection (testing)"
+ echo "-l logfile to record conversation with nhrpd"
+ echo "-d daemonize"
+
+ exit 1
+}
+
+declare -A EXECARR
+declare -A TABLEARR
+declare -Ag NHRPEVENT
+SOCK="/var/run/frr/nhrp.sock"
+USER="frr"
+GROUP="frr"
+DAEMON=0
+j=0
+RESULT="accept"
+
+while getopts rds:i:u:g:l:t:e: opt; do
+ case "$opt" in
+ d)
+ DAEMON=1
+ ;;
+ s)
+ SOCK="$OPTARG"
+ ;;
+ i)
+ INTARR[((j++))]="$OPTARG"
+ ;;
+ e)
+ if [[ "$j" == "0" ]] || [[ "${INTARR[((j-1))]}" == "" ]]; then
+ echo "execute argument must follow interface argument"
+ usage
+ fi
+ EXECARR["${INTARR[((j-1))]}"]="$OPTARG"
+ ;;
+ t)
+ if [[ "$j" == "0" ]] || [[ "${INTARR[((j-1))]}" == "" ]]; then
+ echo "execute argument must follow interface argument"
+ usage
+ fi
+ TABLEARR["${INTARR[((j-1))]}"]="$OPTARG"
+ ;;
+ u)
+ USER="$OPTARG"
+ ;;
+ g)
+ GROUP="$OPTARG"
+ ;;
+ r)
+ RESULT="reject"
+ ;;
+ l)
+ EVLOGFILE="${OPTARG}"
+ ;;
+ esac;
+done
+
+if [[ "$EVLOGFILE" != "" ]]; then
+ if [[ ! -w "${EVLOGFILE}" ]]; then
+ touch "$EVLOGFILE" || ( echo "Cannot write to logfile $EVLOGFILE" ; usage )
+ fi
+ echo -e "PROG: $0 Startup\nPROG: Arguments $*" >> $EVLOGFILE
+fi
+
+
+function mainloop()
+{
+
+if [[ "$EVLOGFILE" != "" ]]; then
+ echo -e "PROG: `date -R`\nPROG: Starting mainloop" >> $EVLOGFILE
+fi
+
+coproc socat - UNIX-LISTEN:$SOCK,unlink-early,setuid-early=$USER,unlink-close=0 || exit 1
+test -S $SOCK && chown $USER:$GROUP $SOCK
+
+OLDIFS="$IFS"
+
+TABLE="table "
+
+while read -r S; do
+ if [[ "$EVLOGFILE" != "" ]]; then
+ echo "IN: $S" >> $EVLOGFILE
+ fi
+ if [[ "$S" == "" ]]; then
+ if [[ "${NHRPEVENT[$EV_ID]}" != "" ]]; then
+ OUTMSG="eventid=${NHRPEVENT[$EV_ID]}\nresult=$RESULT\n"
+ echo -e "$OUTMSG" >&"${COPROC[1]}"
+ if [[ "$EVLOGFILE" != "" ]]; then
+ echo -e "OUT:\n${OUTMSG}" >> $EVLOGFILE;
+ fi
+ fi
+
+
+ for((i=0;i<${#INTARR[@]};i++)); do
+ if [[ "${NHRPEVENT[$EV_INT]}" == "" ]]; then break; fi
+ if [[ "${INTARR[$i]}" != "${NHRPEVENT[$EV_INT]}" ]]; then continue; fi
+ EVINT="${NHRPEVENT[$EV_INT]}"
+ if [[ "${NHRPEVENT[$EV_RADDR]}" == "" ]]; then break; fi
+ if [[ "${NHRPEVENT[$EV_RNBMA]}" == "" ]]; then break; fi
+ if [[ "${NHRPEVENT[$EV_TYPE]}" != "dynamic" ]]; then break; fi
+
+ INTEXEC=${EXECARR["$EVINT"]}
+ INTABLE=${TABLEARR["$EVINT"]}
+
+ unset CMD
+ unset CMDEND
+ CMDADD="ip neigh add "
+ CMDREPL="ip neigh replace"
+ CMDBEG="$CMDADD"
+ if [[ "$INTEXEC" != "" ]]; then
+ CMD="$INTEXEC ${NHRPEVENT[$EV_ID]:-nil}"
+ CMD="$CMD ${NHRPEVENT[$EV_TYPE]:-nil}"
+ CMD="$CMD ${NHRPEVENT[$EV_INT]:-nil}"
+ CMD="$CMD ${NHRPEVENT[$EV_LNBMA]:-nil}"
+ CMD="$CMD ${NHRPEVENT[$EV_RADDR]:-nil}"
+ CMD="$CMD ${NHRPEVENT[$EV_RNBMA]:-nil}"
+ CMD="$CMD ${INTABLE:-nil}"
+ unset CMDBEG
+ else
+ CMDTAB="${INTABLE:+${TABLE}${INTABLE}}"
+ CMDEND="$CMDEND ${NHRPEVENT[$EV_RADDR]} dev $EVINT lladdr ${NHRPEVENT[$EV_RNBMA]} nud noarp"
+ CMD="$CMDEND"
+ fi
+ unset CMDTAB
+ for ((k=0;k<2;k++)); do
+ for ((l=0;l<2;l++)); do
+ if [[ "$EVLOGFILE" != "" ]]; then
+ echo "PROG: Executing $CMD" >> $EVLOGFILE
+ CMDOUT=`$CMDBEG $CMD $CMDTAB 2>&1`
+ CMDRET="$?"
+ if [[ "$CMDOUT" != "" ]]; then
+ echo "PROG: Execution output: $CMDOUT" >> $EVLOGFILE
+ fi
+ else
+ $CMDBEG $CMD $CMDTAB
+ fi
+ if [[ "$CMDTAB" == "" ]] || [[ "$INTEXEC" != "" ]]; then break; fi
+ done
+ if [[ "$INTEXEC" != "" ]] || [[ "$CMDRET" == "0" ]]; then
+ break
+ fi
+ CMDBEG="$CMDREPL"
+ done
+ break
+ done
+
+ unset NHRPEVENT
+ declare -Ag NHRPEVENT
+ continue
+ continue;
+ fi
+ IFS="${IFS}="
+ SA=($S)
+ IFS="$OLDIFS"
+ eval NHRPEVENT[${SA[0]}]="\"${SA[1]}\""
+
+done <&"${COPROC[0]}"
+
+if [[ "$COPROC_PID" != "" ]]; then kill "$COPROC_PID"; fi
+
+}
+
+while true; do
+ mainloop $*
+ if [[ "$DAEMON" == "0" ]]; then
+ break;
+ fi
+ sleep 10
+done
diff --git a/tools/permutations.c b/tools/permutations.c
new file mode 100644
index 0000000..f3883b1
--- /dev/null
+++ b/tools/permutations.c
@@ -0,0 +1,96 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Generates all possible matching inputs for a command string.
+ * --
+ * Copyright (C) 2016 Cumulus Networks, Inc.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "command.h"
+#include "graph.h"
+#include "vector.h"
+
+#define USAGE "usage: permutations <cmdstr>"
+
+void permute(struct graph_node *);
+void pretty_print_graph(struct graph_node *start, int level);
+
+int main(int argc, char *argv[])
+{
+ if (argc < 2) {
+ fprintf(stdout, USAGE "\n");
+ exit(EXIT_SUCCESS);
+ }
+ struct cmd_element *cmd = XCALLOC(MTYPE_TMP,
+ sizeof(struct cmd_element));
+ cmd->string = strdup(argv[1]);
+
+ struct graph *graph = graph_new();
+ struct cmd_token *token =
+ cmd_token_new(START_TKN, cmd->attr, NULL, NULL);
+ graph_new_node(graph, token, NULL);
+ cmd_graph_parse(graph, cmd);
+
+ permute(vector_slot(graph->nodes, 0));
+}
+
+void permute(struct graph_node *start)
+{
+ static struct list *position = NULL;
+ if (!position)
+ position = list_new();
+
+ struct cmd_token *stok = start->data;
+ struct graph_node *gnn;
+ struct listnode *ln;
+ bool is_neg = false;
+
+ // recursive dfs
+ listnode_add(position, start);
+
+ for (ALL_LIST_ELEMENTS_RO(position, ln, gnn)) {
+ struct cmd_token *tok = gnn->data;
+
+ if (tok->type == WORD_TKN && !strcmp(tok->text, "no")) {
+ is_neg = true;
+ break;
+ }
+ if (tok->type < SPECIAL_TKN)
+ break;
+ }
+
+ for (unsigned int i = 0; i < vector_active(start->to); i++) {
+ struct graph_node *gn = vector_slot(start->to, i);
+ struct cmd_token *tok = gn->data;
+ if (tok->attr & CMD_ATTR_HIDDEN)
+ continue;
+ else if (tok->type == END_TKN || gn == start) {
+ fprintf(stdout, " ");
+ for (ALL_LIST_ELEMENTS_RO(position, ln, gnn)) {
+ struct cmd_token *tt = gnn->data;
+ if (tt->type < SPECIAL_TKN)
+ fprintf(stdout, " %s", tt->text);
+ }
+ if (gn == start)
+ fprintf(stdout, "...");
+ fprintf(stdout, "\n");
+ } else {
+ bool skip = false;
+
+ if (tok->type == NEG_ONLY_TKN && !is_neg)
+ continue;
+ if (stok->type == FORK_TKN && tok->type != FORK_TKN)
+ for (ALL_LIST_ELEMENTS_RO(position, ln, gnn))
+ if (gnn == gn) {
+ skip = true;
+ break;
+ }
+ if (!skip)
+ permute(gn);
+ }
+ }
+ list_delete_node(position, listtail(position));
+}
diff --git a/tools/release_notes.py b/tools/release_notes.py
new file mode 100755
index 0000000..973215b
--- /dev/null
+++ b/tools/release_notes.py
@@ -0,0 +1,94 @@
+#!/usr/bin/python3
+#
+# 2021 Jafar Al-Gharaibeh, ATCorp
+#
+# Generate a draft FRR release notes
+#
+
+import sys
+import os
+import getopt
+import subprocess
+
+
+def run(cmd):
+ proc = subprocess.Popen(cmd, stdout=subprocess.PIPE)
+ rv = proc.communicate("")[0].decode("UTF-8")
+ proc.wait()
+ return rv
+
+
+def usage(n):
+ print(os.path.basename(__file__), " [-b <branch>] [-t <tag> ]")
+ print(" Generate one line logs for non merge commits")
+ print(" -branch: branch name to use, default to HEAD")
+ print(" -tag : generate logs up to this tag, default to latest tag")
+ sys.exit(n)
+
+
+def main(argv):
+ branch = tag = None
+ try:
+ opts, args = getopt.getopt(argv, "hb:t:", ["branch=", "tag="])
+ except getopt.GetoptError:
+ usage(2)
+ for opt, arg in opts:
+ if opt == "-h":
+ usage(0)
+ elif opt in ("-b", "--branch"):
+ branch = arg
+ elif opt in ("-t", "--tag"):
+ tag = arg
+
+ if branch is None:
+ branch = "HEAD"
+ if tag is None:
+ tag = run(["git", "describe", "--abbrev=0"]).strip("\n")
+
+ chnglog = run(
+ ["git", "log", "--no-merges", "--pretty=format:'%s'", tag + ".." + branch]
+ )
+ chnglog = chnglog.split("\n")
+
+ chnglist = []
+ daemons = [
+ "babel",
+ "bgp",
+ "eigrp",
+ "nhrp",
+ "ospf",
+ "ospf6",
+ "pbr",
+ "pim",
+ "rip",
+ "ripng",
+ "sharp",
+ "vrrp",
+ "zebra",
+ ]
+
+ for line in chnglog:
+ line = line.strip("'")
+ colon = line.partition(":")
+ label = colon[0].strip().lower()
+ if label in daemons:
+ label = label + "d"
+ comment = colon[2].strip().capitalize()
+ chnglist.append(label + ":" + comment)
+
+ chnglist.sort()
+ lastlabel = ""
+ for line in chnglist:
+ colon = line.partition(":")
+ label = colon[0]
+ comment = colon[2]
+ if label != lastlabel:
+ print("")
+ print(label)
+ lastlabel = label
+
+ print(" ", comment)
+
+
+if __name__ == "__main__":
+ main(sys.argv[1:])
diff --git a/tools/releasedate.py b/tools/releasedate.py
new file mode 100755
index 0000000..3df1ea4
--- /dev/null
+++ b/tools/releasedate.py
@@ -0,0 +1,61 @@
+#!/usr/bin/python3
+#
+# print FRR release schedule dates
+
+from datetime import datetime, date, timedelta
+
+w2 = timedelta(days=14)
+
+
+def year_gen(year):
+ for month in [3, 7, 11]:
+ d = date(year, month, 1)
+ if d.weekday() == 0:
+ d += timedelta(days=1)
+ elif d.weekday() >= 2:
+ d += timedelta(days=8 - d.weekday())
+ yield d
+
+
+def calc(refdate):
+ year = refdate.year
+
+ prev = list(year_gen(year - 1))[-1]
+ releases = list(year_gen(year)) + list(year_gen(year + 1))
+
+ while refdate > releases[0]:
+ prev = releases.pop(0)
+
+ return (prev, releases)
+
+
+if __name__ == "__main__":
+ now = datetime.now().date()
+ last, upcoming = calc(now)
+
+ print("Last release was (scheduled) on %s" % last.isoformat())
+
+ rel = upcoming.pop(0)
+ freeze, stabilization, rc = rel - w2 * 3, rel - w2 * 2, rel - w2
+
+ if now == rel:
+ print("It's release day! 🎉")
+ elif now >= rc:
+ print(
+ "%d days until release! (RC since %s)" % ((rel - now).days, rc.isoformat())
+ )
+ elif now >= stabilization:
+ print(
+ "%d days until RC. (stabilization branch created since %s)"
+ % ((rc - now).days, stabilization.isoformat())
+ )
+ elif now >= freeze:
+ print(
+ "%d days until stabilization branch, master is frozen since %s"
+ % ((stabilization - now).days, freeze.isoformat())
+ )
+ else:
+ print(
+ "%d days of hacking time left! (Freeze on %s)"
+ % ((freeze - now).days, freeze.isoformat())
+ )
diff --git a/tools/render_md.py b/tools/render_md.py
new file mode 100644
index 0000000..80e26f3
--- /dev/null
+++ b/tools/render_md.py
@@ -0,0 +1,31 @@
+#!/usr/bin/env python
+# SPDX-License-Identifier: NONE
+# written 2016 by David Lamparter, placed in Public Domain.
+import sys, markdown
+
+template = """<html><head><meta charset="UTF-8"><style type="text/css">
+body { max-width: 45em; margin: auto; margin-top: 2em; margin-bottom: 2em;
+ font-family:Fira Sans,sans-serif; text-align: justify;
+ counter-reset: ch2; }
+pre, code { font-family:Fira Mono,monospace; }
+pre > code { display: block; padding:0.5em; border:1px solid black;
+ background-color:#eee; color:#000; }
+h2:before { content: counter(ch2) ". "; counter-increment: ch2; }
+h2 { clear: both; margin-top: 3em; text-decoration: underline; counter-reset: ch3; }
+h3:before { content: counter(ch2) "." counter(ch3) ". "; counter-increment: ch3; }
+h3 { clear: both; margin-top: 2em; font-weight: normal; font-style: italic; }
+h4 { font-weight: normal; font-style: italic; }
+img[alt~="float-right"] { float:right; margin-left:2em; margin-bottom:2em; }
+</style></head><body>
+%s
+</body></html>
+"""
+
+md = markdown.Markdown(extensions=["extra", "toc"])
+
+for fn in sys.argv[1:]:
+ with open(fn, "r") as ifd:
+ with open("%s.html" % (fn), "w") as ofd:
+ ofd.write(
+ (template % (md.convert(ifd.read().decode("UTF-8")))).encode("UTF-8")
+ )
diff --git a/tools/rrcheck.pl b/tools/rrcheck.pl
new file mode 100644
index 0000000..279bca8
--- /dev/null
+++ b/tools/rrcheck.pl
@@ -0,0 +1,135 @@
+#!/usr/bin/env perl
+##
+## Read BGPd logfile and lookup RR's whois database.
+##
+## Copyright (c) 1997 Kunihiro Ishiguro
+##
+use Socket;
+
+## Configuration variables
+$whois_host = "whois.jpix.ad.jp";
+
+#$logfile = "/usr/local/sbin/logfile"
+$logfile = shift || die "Please specify filename";
+
+## mail routine
+{
+ local ($prefix, $origin);
+
+ open (LOG, $logfile) || die "can't open $logfile";
+
+ $index = '';
+ while ($index) {
+ $index = <LOG>;
+ if ($index =~ /[bgpd]/) {
+ break;
+ }
+ }
+
+ while (<LOG>) {
+ if (/([\d\.\/]+)\s+([\d\.]+)\s+(\d+)\s+(\d+)\s+([\d ]+)\s+[ie\?]/) {
+ $prefix = $1;
+ $nexthop = $2;
+ $med = $3;
+ $dummy = $4;
+ $aspath = $5;
+ ($origin) = ($aspath =~ /([\d]+)$/);
+
+ print "$nexthop [$origin] $prefix $aspath ";
+
+ $ret = &whois_check ($prefix, $origin);
+ if ($ret == 0) {
+ print "Check OK\n";
+ } elsif ($ret == 1){
+ print "AS orgin mismatch\n";
+ } else {
+ print "prefix doesn't exist \n";
+ }
+ }
+ }
+}
+
+sub whois_check
+{
+ local ($prefix, $origin) = @_;
+ local ($rr_prefix, $rr_origin) = ();
+ local (@result);
+
+ $origin = "AS" . $origin;
+
+ @result = &whois ($prefix);
+
+ $prefix_match = 0;
+ foreach (@result) {
+ if (/^route:.*\s([\d\.\/]+)$/) {
+ $rr_prefix = $1;
+ }
+ if (/^origin:.*\s(AS[\d]+)$/) {
+ $rr_origin = $1;
+
+ if ($prefix eq $rr_prefix and $origin eq $rr_origin) {
+ return 0;
+ } elsif ($prefix eq $rr_prefix) {
+ $prefix_match = 1;
+ }
+ }
+ }
+# alarm_mail ($prefix, $origin, @result);
+ if ($prefix_match) {
+ return 1;
+ } else {
+ return 2;
+ }
+}
+
+## get port of whois
+sub get_whois_port
+{
+ local ($name, $aliases, $port, $proto) = getservbyname ("whois", "tcp");
+ return ($port, $proto);
+}
+
+## whois lookup
+sub whois
+{
+ local ($query) = @_;
+ local ($port, $proto) = &get_whois_port;
+ local (@result);
+
+ if ($whois_host=~ /^\s*\d+\.\d+\.\d+\.\d+\s*$/) {
+ $address = pack ("C4",split(/\./,$host));
+ } else {
+ $address = (gethostbyname ($whois_host))[4];
+ }
+
+ socket (SOCKET, PF_INET, SOCK_STREAM, $proto);
+
+ if (connect (SOCKET, sockaddr_in ($port, $address))) {
+ local ($oldhandle) = select (SOCKET);
+ $| = 1;
+ select($oldhandle);
+
+ print SOCKET "$query\r\n";
+
+ @result = <SOCKET>;
+ return @result;
+ }
+}
+
+##
+sub alarm_mail
+{
+ local ($prefix, $origin, @result) = @_;
+
+ open (MAIL, "|$mailer -t $mail_address") || die "can't open $mailer";
+
+ print MAIL "From: root\@rr1.jpix.ad.jp\n";
+ print MAIL "Subject: RR $origin $prefix\n";
+ print MAIL "MIME-Version: 1.0\n";
+ print MAIL "Content-Type: text/plain; charset=us-ascii \n\n";
+ print MAIL "RR Lookup Error Report\n";
+ print MAIL "======================\n";
+ print MAIL "Announced route : $prefix from $origin\n\n";
+ print MAIL "@result";
+ close MAIL;
+}
diff --git a/tools/rrlookup.pl b/tools/rrlookup.pl
new file mode 100644
index 0000000..84410e8
--- /dev/null
+++ b/tools/rrlookup.pl
@@ -0,0 +1,123 @@
+#!/usr/bin/env perl
+##
+## Read BGPd logfile and lookup RR's whois database.
+##
+## Copyright (c) 1997 Kunihiro Ishiguro
+##
+use Socket;
+
+## Configuration variables
+$whois_host = "whois.jpix.ad.jp";
+
+#$mail_address = "toshio\@iri.co.jp";
+$mail_address = "kunihiro\@zebra.org";
+$mailer = "/usr/sbin/sendmail -oi";
+
+#$logfile = "/usr/local/sbin/logfile"
+$logfile = "logfile";
+$lookuplog = "lookuplog";
+
+## mail routine
+{
+ local ($prefix, $origin);
+
+ open (LOG, $logfile) || die "can't open $logfile";
+ open (LOOKUP, ">$lookuplog") || die "can't open $lookuplog";
+
+ for (;;) {
+ while (<LOG>) {
+ if (/Update\S+ ([\d\.\/]+) .* (\d+) [ie\?]/) {
+ $prefix = $1;
+ $origin = $2;
+ $ret = &whois_check ($prefix, $origin);
+ if ($ret) {
+ print LOOKUP "$prefix AS$origin : Check OK\n";
+ } else {
+ print LOOKUP "$prefix AS$origin : Error\n";
+ }
+# fflush (LOOKUP);
+ }
+ }
+ sleep (3);
+ }
+}
+
+sub whois_check
+{
+ local ($prefix, $origin) = @_;
+ local ($rr_prefix, $rr_origin) = ();
+ local (@result);
+
+ $origin = "AS" . $origin;
+
+# print "$prefix $origin\n";
+
+ @result = &whois ($prefix);
+
+ foreach (@result) {
+ if (/^route:.*\s([\d\.\/]+)$/) {
+ $rr_prefix = $1;
+ }
+ if (/^origin:.*\s(AS[\d]+)$/) {
+ $rr_origin = $1;
+
+ if ($prefix eq $rr_prefix and $origin eq $rr_origin) {
+ return 1;
+ }
+ }
+ }
+ alarm_mail ($prefix, $origin, @result);
+ return 0;
+}
+
+## get port of whois
+sub get_whois_port
+{
+ local ($name, $aliases, $port, $proto) = getservbyname ("whois", "tcp");
+ return ($port, $proto);
+}
+
+## whois lookup
+sub whois
+{
+ local ($query) = @_;
+ local ($port, $proto) = &get_whois_port;
+ local (@result);
+
+ if ($whois_host=~ /^\s*\d+\.\d+\.\d+\.\d+\s*$/) {
+ $address = pack ("C4",split(/\./,$host));
+ } else {
+ $address = (gethostbyname ($whois_host))[4];
+ }
+
+ socket (SOCKET, PF_INET, SOCK_STREAM, $proto);
+
+ if (connect (SOCKET, sockaddr_in ($port, $address))) {
+ local ($oldhandle) = select (SOCKET);
+ $| = 1;
+ select($oldhandle);
+
+ print SOCKET "$query\r\n";
+
+ @result = <SOCKET>;
+ return @result;
+ }
+}
+
+##
+sub alarm_mail
+{
+ local ($prefix, $origin, @result) = @_;
+
+ open (MAIL, "|$mailer -t $mail_address") || die "can't open $mailer";
+
+ print MAIL "From: root\@rr1.jpix.ad.jp\n";
+ print MAIL "Subject: RR $origin $prefix\n";
+ print MAIL "MIME-Version: 1.0\n";
+ print MAIL "Content-Type: text/plain; charset=us-ascii \n\n";
+ print MAIL "RR Lookup Error Report\n";
+ print MAIL "======================\n";
+ print MAIL "Announced route : $prefix from $origin\n\n";
+ print MAIL "@result";
+ close MAIL;
+}
diff --git a/tools/start-stop-daemon.c b/tools/start-stop-daemon.c
new file mode 100644
index 0000000..9f566bd
--- /dev/null
+++ b/tools/start-stop-daemon.c
@@ -0,0 +1,1062 @@
+// SPDX-License-Identifier: NONE
+/*
+ * A rewrite of the original Debian's start-stop-daemon Perl script
+ * in C (faster - it is executed many times during system startup).
+ *
+ * Written by Marek Michalkiewicz <marekm@i17linuxb.ists.pwr.wroc.pl>,
+ * public domain. Based conceptually on start-stop-daemon.pl, by Ian
+ * Jackson <ijackson@gnu.ai.mit.edu>. May be used and distributed
+ * freely for any purpose. Changes by Christian Schwarz
+ * <schwarz@monet.m.isar.de>, to make output conform to the Debian
+ * Console Message Standard, also placed in public domain. Minor
+ * changes by Klee Dienes <klee@debian.org>, also placed in the Public
+ * Domain.
+ *
+ * Changes by Ben Collins <bcollins@debian.org>, added --chuid, --background
+ * and --make-pidfile options, placed in public domain aswell.
+ *
+ * Port to OpenBSD by Sontri Tomo Huynh <huynh.29@osu.edu>
+ * and Andreas Schuldei <andreas@schuldei.org>
+ *
+ * Changes by Ian Jackson: added --retry (and associated rearrangements).
+ *
+ * Modified for Gentoo rc-scripts by Donny Davies <woodchip@gentoo.org>:
+ * I removed the BSD/Hurd/OtherOS stuff, added #include <stddef.h>
+ * and stuck in a #define VERSION "1.9.18". Now it compiles without
+ * the whole automake/config.h dance.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#ifdef HAVE_LXC
+#define _GNU_SOURCE
+#include <sched.h>
+#endif /* HAVE_LXC */
+
+#include <stddef.h>
+#undef VERSION
+#define VERSION "1.9.18"
+
+#define MIN_POLL_INTERVAL 20000 /*us*/
+
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stdarg.h>
+#include <signal.h>
+#include <sys/stat.h>
+#include <dirent.h>
+#include <sys/time.h>
+#include <sys/queue.h>
+#include <unistd.h>
+#include <getopt.h>
+#include <pwd.h>
+#include <grp.h>
+#include <sys/ioctl.h>
+#include <sys/types.h>
+#include <termios.h>
+#include <fcntl.h>
+#include <limits.h>
+#include <assert.h>
+#include <ctype.h>
+#ifdef linux
+#include <linux/sched.h>
+#endif
+
+static int testmode = 0;
+static int quietmode = 0;
+static int exitnodo = 1;
+static int start = 0;
+static int stop = 0;
+static int background = 0;
+static int mpidfile = 0;
+static int signal_nr = 15;
+static const char *signal_str = NULL;
+static int user_id = -1;
+static int runas_uid = -1;
+static int runas_gid = -1;
+static const char *userspec = NULL;
+static char *changeuser = NULL;
+static const char *changegroup = NULL;
+static char *changeroot = NULL;
+static const char *cmdname = NULL;
+static char *execname = NULL;
+static char *startas = NULL;
+static const char *pidfile = NULL;
+static char what_stop[1024];
+static const char *schedule_str = NULL;
+static const char *progname = "";
+static int nicelevel = 0;
+
+static struct stat exec_stat;
+
+struct pid_list {
+ struct pid_list *next;
+ pid_t pid;
+};
+
+static struct pid_list *found = NULL;
+static struct pid_list *killed = NULL;
+
+struct schedule_item {
+ enum { sched_timeout, sched_signal, sched_goto, sched_forever } type;
+ int value; /* seconds, signal no., or index into array */
+ /* sched_forever is only seen within parse_schedule and callees */
+};
+
+static int schedule_length;
+static struct schedule_item *schedule = NULL;
+
+LIST_HEAD(namespace_head, namespace);
+
+struct namespace
+{
+ LIST_ENTRY(namespace) list;
+ const char *path;
+ int nstype;
+};
+
+static struct namespace_head namespace_head;
+
+static void *xmalloc(int size);
+static void push(struct pid_list **list, pid_t pid);
+static void do_help(void);
+static void parse_options(int argc, char *const *argv);
+static int pid_is_user(pid_t pid, uid_t uid);
+static int pid_is_cmd(pid_t pid, const char *name);
+static void check(pid_t pid);
+static void do_pidfile(const char *name);
+static void do_stop(int signal_nr, int quietmode, int *n_killed,
+ int *n_notkilled, int retry_nr);
+static int pid_is_exec(pid_t pid, const struct stat *esb);
+
+#ifdef __GNUC__
+static void fatal(const char *format, ...)
+ __attribute__((noreturn, format(printf, 1, 2)));
+static void badusage(const char *msg) __attribute__((noreturn));
+#else
+static void fatal(const char *format, ...);
+static void badusage(const char *msg);
+#endif
+
+/* This next part serves only to construct the TVCALC macro, which
+ * is used for doing arithmetic on struct timeval's. It works like this:
+ * TVCALC(result, expression);
+ * where result is a struct timeval (and must be an lvalue) and
+ * expression is the single expression for both components. In this
+ * expression you can use the special values TVELEM, which when fed a
+ * const struct timeval* gives you the relevant component, and
+ * TVADJUST. TVADJUST is necessary when subtracting timevals, to make
+ * it easier to renormalise. Whenver you subtract timeval elements,
+ * you must make sure that TVADJUST is added to the result of the
+ * subtraction (before any resulting multiplication or what have you).
+ * TVELEM must be linear in TVADJUST.
+ */
+typedef long tvselector(const struct timeval *);
+static long tvselector_sec(const struct timeval *tv)
+{
+ return tv->tv_sec;
+}
+static long tvselector_usec(const struct timeval *tv)
+{
+ return tv->tv_usec;
+}
+#define TVCALC_ELEM(result, expr, sec, adj) \
+ { \
+ const long TVADJUST = adj; \
+ long (*const TVELEM)(const struct timeval *) = \
+ tvselector_##sec; \
+ (result).tv_##sec = (expr); \
+ }
+#define TVCALC(result, expr) \
+ do { \
+ TVCALC_ELEM(result, expr, sec, (-1)); \
+ TVCALC_ELEM(result, expr, usec, (+1000000)); \
+ (result).tv_sec += (result).tv_usec / 1000000; \
+ (result).tv_usec %= 1000000; \
+ } while (0)
+
+
+static void fatal(const char *format, ...)
+{
+ va_list arglist;
+
+ fprintf(stderr, "%s: ", progname);
+ va_start(arglist, format);
+ vfprintf(stderr, format, arglist);
+ va_end(arglist);
+ putc('\n', stderr);
+ exit(2);
+}
+
+
+static void *xmalloc(int size)
+{
+ void *ptr;
+
+ ptr = malloc(size);
+ if (ptr)
+ return ptr;
+ fatal("malloc(%d) failed", size);
+}
+
+static void xgettimeofday(struct timeval *tv)
+{
+ if (gettimeofday(tv, 0) != 0)
+ fatal("gettimeofday failed: %s", strerror(errno));
+}
+
+static void push(struct pid_list **list, pid_t pid)
+{
+ struct pid_list *p;
+
+ p = xmalloc(sizeof(*p));
+ p->next = *list;
+ p->pid = pid;
+ *list = p;
+}
+
+static void clear(struct pid_list **list)
+{
+ struct pid_list *here, *next;
+
+ for (here = *list; here != NULL; here = next) {
+ next = here->next;
+ free(here);
+ }
+
+ *list = NULL;
+}
+
+#ifdef linux
+static const char *next_dirname(const char *s)
+{
+ const char *cur;
+
+ cur = s;
+
+ if (*cur != '\0') {
+ for (; *cur != '/'; ++cur)
+ if (*cur == '\0')
+ return cur;
+
+ for (; *cur == '/'; ++cur)
+ ;
+ }
+
+ return cur;
+}
+
+static void add_namespace(const char *path)
+{
+ int nstype;
+ const char *nsdirname, *nsname, *cur;
+ struct namespace *namespace;
+
+ cur = path;
+ nsdirname = nsname = "";
+
+ while ((cur = next_dirname(cur))[0] != '\0') {
+ nsdirname = nsname;
+ nsname = cur;
+ }
+
+ if (!strncmp(nsdirname, "ipcns/", strlen("ipcns/")))
+ nstype = CLONE_NEWIPC;
+ else if (!strncmp(nsdirname, "netns/", strlen("netns/")))
+ nstype = CLONE_NEWNET;
+ else if (!strncmp(nsdirname, "utcns/", strlen("utcns/")))
+ nstype = CLONE_NEWUTS;
+ else
+ badusage("invalid namepspace path");
+
+ namespace = xmalloc(sizeof(*namespace));
+ namespace->path = path;
+ namespace->nstype = nstype;
+ LIST_INSERT_HEAD(&namespace_head, namespace, list);
+}
+#endif
+
+#ifdef HAVE_LXC
+static void set_namespaces(void)
+{
+ struct namespace *namespace;
+ int fd;
+
+ LIST_FOREACH (namespace, &namespace_head, list) {
+ if ((fd = open(namespace->path, O_RDONLY)) == -1)
+ fatal("open namespace %s: %s", namespace->path,
+ strerror(errno));
+ if (setns(fd, namespace->nstype) == -1)
+ fatal("setns %s: %s", namespace->path, strerror(errno));
+ }
+}
+#else
+static void set_namespaces(void)
+{
+ if (!LIST_EMPTY(&namespace_head))
+ fatal("LCX namespaces not supported");
+}
+#endif
+
+static void do_help(void)
+{
+ printf("start-stop-daemon " VERSION
+ " for Debian - small and fast C version written by\n"
+ "Marek Michalkiewicz <marekm@i17linuxb.ists.pwr.wroc.pl>, public domain.\n"
+ "\n"
+ "Usage:\n"
+ " start-stop-daemon -S|--start options ... -- arguments ...\n"
+ " start-stop-daemon -K|--stop options ...\n"
+ " start-stop-daemon -H|--help\n"
+ " start-stop-daemon -V|--version\n"
+ "\n"
+ "Options (at least one of --exec|--pidfile|--user is required):\n"
+ " -x|--exec <executable> program to start/check if it is running\n"
+ " -p|--pidfile <pid-file> pid file to check\n"
+ " -c|--chuid <name|uid[:group|gid]>\n"
+ " change to this user/group before starting process\n"
+ " -u|--user <username>|<uid> stop processes owned by this user\n"
+ " -n|--name <process-name> stop processes with this name\n"
+ " -s|--signal <signal> signal to send (default TERM)\n"
+ " -a|--startas <pathname> program to start (default is <executable>)\n"
+ " -N|--nicelevel <incr> add incr to the process's nice level\n"
+ " -b|--background force the process to detach\n"
+ " -m|--make-pidfile create the pidfile before starting\n"
+ " -R|--retry <schedule> check whether processes die, and retry\n"
+ " -t|--test test mode, don't do anything\n"
+ " -o|--oknodo exit status 0 (not 1) if nothing done\n"
+ " -q|--quiet be more quiet\n"
+ " -v|--verbose be more verbose\n"
+ "Retry <schedule> is <item>|/<item>/... where <item> is one of\n"
+ " -<signal-num>|[-]<signal-name> send that signal\n"
+ " <timeout> wait that many seconds\n"
+ " forever repeat remainder forever\n"
+ "or <schedule> may be just <timeout>, meaning <signal>/<timeout>/KILL/<timeout>\n"
+ "\n"
+ "Exit status: 0 = done 1 = nothing done (=> 0 if --oknodo)\n"
+ " 3 = trouble 2 = with --retry, processes wouldn't die\n");
+}
+
+
+static void badusage(const char *msg)
+{
+ if (msg)
+ fprintf(stderr, "%s: %s\n", progname, msg);
+ fprintf(stderr, "Try `%s --help' for more information.\n", progname);
+ exit(3);
+}
+
+struct sigpair {
+ const char *name;
+ int signal;
+};
+
+const struct sigpair siglist[] = {
+ {"ABRT", SIGABRT}, {"ALRM", SIGALRM}, {"FPE", SIGFPE},
+ {"HUP", SIGHUP}, {"ILL", SIGILL}, {"INT", SIGINT},
+ {"KILL", SIGKILL}, {"PIPE", SIGPIPE}, {"QUIT", SIGQUIT},
+ {"SEGV", SIGSEGV}, {"TERM", SIGTERM}, {"USR1", SIGUSR1},
+ {"USR2", SIGUSR2}, {"CHLD", SIGCHLD}, {"CONT", SIGCONT},
+ {"STOP", SIGSTOP}, {"TSTP", SIGTSTP}, {"TTIN", SIGTTIN},
+ {"TTOU", SIGTTOU}};
+
+static int parse_integer(const char *string, int *value_r)
+{
+ unsigned long ul;
+ char *ep;
+
+ if (!string[0])
+ return -1;
+
+ ul = strtoul(string, &ep, 10);
+ if (ul > INT_MAX || *ep != '\0')
+ return -1;
+
+ *value_r = ul;
+ return 0;
+}
+
+static int parse_signal(const char *signal_str, int *signal_nr)
+{
+ unsigned int i;
+
+ if (parse_integer(signal_str, signal_nr) == 0)
+ return 0;
+
+ for (i = 0; i < sizeof(siglist) / sizeof(siglist[0]); i++) {
+ if (strcmp(signal_str, siglist[i].name) == 0) {
+ *signal_nr = siglist[i].signal;
+ return 0;
+ }
+ }
+ return -1;
+}
+
+static void parse_schedule_item(const char *string, struct schedule_item *item)
+{
+ const char *after_hyph;
+
+ if (!strcmp(string, "forever")) {
+ item->type = sched_forever;
+ } else if (isdigit((unsigned char)string[0])) {
+ item->type = sched_timeout;
+ if (parse_integer(string, &item->value) != 0)
+ badusage("invalid timeout value in schedule");
+ } else if ((after_hyph = string + (string[0] == '-'))
+ && parse_signal(after_hyph, &item->value) == 0) {
+ item->type = sched_signal;
+ } else {
+ badusage(
+ "invalid schedule item (must be [-]<signal-name>, -<signal-number>, <timeout> or `forever'");
+ }
+}
+
+static void parse_schedule(const char *schedule_str)
+{
+ char item_buf[20];
+ const char *slash;
+ int count, repeatat;
+ ptrdiff_t str_len;
+
+ count = 0;
+ for (slash = schedule_str; *slash; slash++)
+ if (*slash == '/')
+ count++;
+
+ schedule_length = (count == 0) ? 4 : count + 1;
+ schedule = xmalloc(sizeof(*schedule) * schedule_length);
+
+ if (count == 0) {
+ schedule[0].type = sched_signal;
+ schedule[0].value = signal_nr;
+ parse_schedule_item(schedule_str, &schedule[1]);
+ if (schedule[1].type != sched_timeout) {
+ badusage(
+ "--retry takes timeout, or schedule list of at least two items");
+ }
+ schedule[2].type = sched_signal;
+ schedule[2].value = SIGKILL;
+ schedule[3] = schedule[1];
+ } else {
+ count = 0;
+ repeatat = -1;
+ while (schedule_str != NULL) {
+ slash = strchr(schedule_str, '/');
+ str_len = slash ? slash - schedule_str
+ : (ptrdiff_t)strlen(schedule_str);
+ if (str_len >= (ptrdiff_t)sizeof(item_buf))
+ badusage(
+ "invalid schedule item: far too long (you must delimit items with slashes)");
+ memcpy(item_buf, schedule_str, str_len);
+ item_buf[str_len] = 0;
+ schedule_str = slash ? slash + 1 : NULL;
+
+ parse_schedule_item(item_buf, &schedule[count]);
+ if (schedule[count].type == sched_forever) {
+ if (repeatat >= 0)
+ badusage(
+ "invalid schedule: `forever' appears more than once");
+ repeatat = count;
+ continue;
+ }
+ count++;
+ }
+ if (repeatat >= 0) {
+ schedule[count].type = sched_goto;
+ schedule[count].value = repeatat;
+ count++;
+ }
+ assert(count == schedule_length);
+ }
+}
+
+static void parse_options(int argc, char *const *argv)
+{
+ static struct option longopts[] = {
+ {"help", 0, NULL, 'H'}, {"stop", 0, NULL, 'K'},
+ {"start", 0, NULL, 'S'}, {"version", 0, NULL, 'V'},
+ {"startas", 1, NULL, 'a'}, {"name", 1, NULL, 'n'},
+ {"oknodo", 0, NULL, 'o'}, {"pidfile", 1, NULL, 'p'},
+ {"quiet", 0, NULL, 'q'}, {"signal", 1, NULL, 's'},
+ {"test", 0, NULL, 't'}, {"user", 1, NULL, 'u'},
+ {"chroot", 1, NULL, 'r'}, {"namespace", 1, NULL, 'd'},
+ {"verbose", 0, NULL, 'v'}, {"exec", 1, NULL, 'x'},
+ {"chuid", 1, NULL, 'c'}, {"nicelevel", 1, NULL, 'N'},
+ {"background", 0, NULL, 'b'}, {"make-pidfile", 0, NULL, 'm'},
+ {"retry", 1, NULL, 'R'}, {NULL, 0, NULL, 0}};
+ int c;
+
+ for (;;) {
+ c = getopt_long(argc, argv,
+ "HKSVa:n:op:qr:d:s:tu:vx:c:N:bmR:", longopts,
+ (int *)0);
+ if (c == -1)
+ break;
+ switch (c) {
+ case 'H': /* --help */
+ do_help();
+ exit(0);
+ case 'K': /* --stop */
+ stop = 1;
+ break;
+ case 'S': /* --start */
+ start = 1;
+ break;
+ case 'V': /* --version */
+ printf("start-stop-daemon " VERSION "\n");
+ exit(0);
+ case 'a': /* --startas <pathname> */
+ startas = optarg;
+ break;
+ case 'n': /* --name <process-name> */
+ cmdname = optarg;
+ break;
+ case 'o': /* --oknodo */
+ exitnodo = 0;
+ break;
+ case 'p': /* --pidfile <pid-file> */
+ pidfile = optarg;
+ break;
+ case 'q': /* --quiet */
+ quietmode = 1;
+ break;
+ case 's': /* --signal <signal> */
+ signal_str = optarg;
+ break;
+ case 't': /* --test */
+ testmode = 1;
+ break;
+ case 'u': /* --user <username>|<uid> */
+ userspec = optarg;
+ break;
+ case 'v': /* --verbose */
+ quietmode = -1;
+ break;
+ case 'x': /* --exec <executable> */
+ execname = optarg;
+ break;
+ case 'c': /* --chuid <username>|<uid> */
+ changeuser = strtok(optarg, ":");
+ changegroup = strtok(NULL, ":");
+ break;
+ case 'r': /* --chroot /new/root */
+ changeroot = optarg;
+ break;
+ case 'd': /* --namespace /.../<ipcns>|<netns>|<utsns>/name */
+#ifdef linux
+ add_namespace(optarg);
+#endif
+ break;
+ case 'N': /* --nice */
+ nicelevel = atoi(optarg);
+ break;
+ case 'b': /* --background */
+ background = 1;
+ break;
+ case 'm': /* --make-pidfile */
+ mpidfile = 1;
+ break;
+ case 'R': /* --retry <schedule>|<timeout> */
+ schedule_str = optarg;
+ break;
+ default:
+ badusage(NULL); /* message printed by getopt */
+ }
+ }
+
+ if (signal_str != NULL) {
+ if (parse_signal(signal_str, &signal_nr) != 0)
+ badusage(
+ "signal value must be numeric or name of signal (KILL, INTR, ...)");
+ }
+
+ if (schedule_str != NULL) {
+ parse_schedule(schedule_str);
+ }
+
+ if (start == stop)
+ badusage("need one of --start or --stop");
+
+ if (!execname && !pidfile && !userspec && !cmdname)
+ badusage(
+ "need at least one of --exec, --pidfile, --user or --name");
+
+ if (!startas)
+ startas = execname;
+
+ if (start && !startas)
+ badusage("--start needs --exec or --startas");
+
+ if (mpidfile && pidfile == NULL)
+ badusage("--make-pidfile is only relevant with --pidfile");
+
+ if (background && !start)
+ badusage("--background is only relevant with --start");
+}
+
+static int pid_is_exec(pid_t pid, const struct stat *esb)
+{
+ struct stat sb;
+ char buf[PATH_MAX];
+
+ snprintf(buf, sizeof(buf), "/proc/%ld/exe", (long)pid);
+ if (stat(buf, &sb) != 0)
+ return 0;
+ return (sb.st_dev == esb->st_dev && sb.st_ino == esb->st_ino);
+}
+
+
+static int pid_is_user(pid_t pid, uid_t uid)
+{
+ struct stat sb;
+ char buf[PATH_MAX];
+
+ snprintf(buf, sizeof(buf), "/proc/%ld", (long)pid);
+ if (stat(buf, &sb) != 0)
+ return 0;
+ return (sb.st_uid == uid);
+}
+
+
+static int pid_is_cmd(pid_t pid, const char *name)
+{
+ char buf[PATH_MAX];
+ FILE *f;
+ int c;
+
+ snprintf(buf, sizeof(buf), "/proc/%ld/stat", (long)pid);
+ f = fopen(buf, "r");
+ if (!f)
+ return 0;
+ while ((c = getc(f)) != EOF && c != '(')
+ ;
+ if (c != '(') {
+ fclose(f);
+ return 0;
+ }
+ /* this hopefully handles command names containing ')' */
+ while ((c = getc(f)) != EOF && c == *name)
+ name++;
+ fclose(f);
+ return (c == ')' && *name == '\0');
+}
+
+
+static void check(pid_t pid)
+{
+ if (execname && !pid_is_exec(pid, &exec_stat))
+ return;
+ if (userspec && !pid_is_user(pid, user_id))
+ return;
+ if (cmdname && !pid_is_cmd(pid, cmdname))
+ return;
+ push(&found, pid);
+}
+
+static void do_pidfile(const char *name)
+{
+ FILE *f;
+ long pid;
+
+ f = fopen(name, "r");
+ if (f) {
+ if (fscanf(f, "%ld", &pid) == 1)
+ check((pid_t)pid);
+ fclose(f);
+ } else if (errno != ENOENT)
+ fatal("open pidfile %s: %s", name, strerror(errno));
+}
+
+/* WTA: this needs to be an autoconf check for /proc/pid existance.
+ */
+static void do_procinit(void)
+{
+ DIR *procdir;
+ struct dirent *entry;
+ int foundany;
+ long pid;
+
+ procdir = opendir("/proc");
+ if (!procdir)
+ fatal("opendir /proc: %s", strerror(errno));
+
+ foundany = 0;
+ while ((entry = readdir(procdir)) != NULL) {
+ if (sscanf(entry->d_name, "%ld", &pid) != 1)
+ continue;
+ foundany++;
+ check((pid_t)pid);
+ }
+ closedir(procdir);
+ if (!foundany)
+ fatal("nothing in /proc - not mounted?");
+}
+
+static void do_findprocs(void)
+{
+ clear(&found);
+
+ if (pidfile)
+ do_pidfile(pidfile);
+ else
+ do_procinit();
+}
+
+/* return 1 on failure */
+static void do_stop(int signal_nr, int quietmode, int *n_killed,
+ int *n_notkilled, int retry_nr)
+{
+ struct pid_list *p;
+
+ do_findprocs();
+
+ *n_killed = 0;
+ *n_notkilled = 0;
+
+ if (!found)
+ return;
+
+ clear(&killed);
+
+ for (p = found; p; p = p->next) {
+ if (testmode)
+ printf("Would send signal %d to %ld.\n", signal_nr,
+ (long)p->pid);
+ else if (kill(p->pid, signal_nr) == 0) {
+ push(&killed, p->pid);
+ (*n_killed)++;
+ } else {
+ printf("%s: warning: failed to kill %ld: %s\n",
+ progname, (long)p->pid, strerror(errno));
+ (*n_notkilled)++;
+ }
+ }
+ if (quietmode < 0 && killed) {
+ printf("Stopped %s (pid", what_stop);
+ for (p = killed; p; p = p->next)
+ printf(" %ld", (long)p->pid);
+ putchar(')');
+ if (retry_nr > 0)
+ printf(", retry #%d", retry_nr);
+ printf(".\n");
+ }
+}
+
+
+static void set_what_stop(const char *str)
+{
+ strncpy(what_stop, str, sizeof(what_stop));
+ what_stop[sizeof(what_stop) - 1] = '\0';
+}
+
+static int run_stop_schedule(void)
+{
+ int r, position, n_killed, n_notkilled, value, ratio, anykilled,
+ retry_nr;
+ struct timeval stopat, before, after, interval, maxinterval;
+
+ if (testmode) {
+ if (schedule != NULL) {
+ printf("Ignoring --retry in test mode\n");
+ schedule = NULL;
+ }
+ }
+
+ if (cmdname)
+ set_what_stop(cmdname);
+ else if (execname)
+ set_what_stop(execname);
+ else if (pidfile)
+ sprintf(what_stop, "process in pidfile `%.200s'", pidfile);
+ else if (userspec)
+ sprintf(what_stop, "process(es) owned by `%.200s'", userspec);
+ else
+ fatal("internal error, please report");
+
+ anykilled = 0;
+ retry_nr = 0;
+ n_killed = 0;
+
+ if (schedule == NULL) {
+ do_stop(signal_nr, quietmode, &n_killed, &n_notkilled, 0);
+ if (n_notkilled > 0 && quietmode <= 0)
+ printf("%d pids were not killed\n", n_notkilled);
+ if (n_killed)
+ anykilled = 1;
+ goto x_finished;
+ }
+
+ for (position = 0; position < schedule_length;) {
+ value = schedule[position].value;
+ n_notkilled = 0;
+
+ switch (schedule[position].type) {
+
+ case sched_goto:
+ position = value;
+ continue;
+
+ case sched_signal:
+ do_stop(value, quietmode, &n_killed, &n_notkilled,
+ retry_nr++);
+ if (!n_killed)
+ goto x_finished;
+ else
+ anykilled = 1;
+ goto next_item;
+
+ case sched_timeout:
+ /* We want to keep polling for the processes, to see if
+ * they've exited,
+ * or until the timeout expires.
+ *
+ * This is a somewhat complicated algorithm to try to
+ * ensure that we
+ * notice reasonably quickly when all the processes have
+ * exited, but
+ * don't spend too much CPU time polling. In
+ * particular, on a fast
+ * machine with quick-exiting daemons we don't want to
+ * delay system
+ * shutdown too much, whereas on a slow one, or where
+ * processes are
+ * taking some time to exit, we want to increase the
+ * polling
+ * interval.
+ *
+ * The algorithm is as follows: we measure the elapsed
+ * time it takes
+ * to do one poll(), and wait a multiple of this time
+ * for the next
+ * poll. However, if that would put us past the end of
+ * the timeout
+ * period we wait only as long as the timeout period,
+ * but in any case
+ * we always wait at least MIN_POLL_INTERVAL (20ms).
+ * The multiple
+ * (`ratio') starts out as 2, and increases by 1 for
+ * each poll to a
+ * maximum of 10; so we use up to between 30% and 10% of
+ * the
+ * machine's resources (assuming a few reasonable things
+ * about system
+ * performance).
+ */
+ xgettimeofday(&stopat);
+ stopat.tv_sec += value;
+ ratio = 1;
+ for (;;) {
+ xgettimeofday(&before);
+ if (timercmp(&before, &stopat, >))
+ goto next_item;
+
+ do_stop(0, 1, &n_killed, &n_notkilled, 0);
+ if (!n_killed)
+ goto x_finished;
+
+ xgettimeofday(&after);
+
+ if (!timercmp(&after, &stopat, <))
+ goto next_item;
+
+ if (ratio < 10)
+ ratio++;
+
+ TVCALC(interval,
+ ratio * (TVELEM(&after) - TVELEM(&before)
+ + TVADJUST));
+ TVCALC(maxinterval,
+ TVELEM(&stopat) - TVELEM(&after)
+ + TVADJUST);
+
+ if (timercmp(&interval, &maxinterval, >))
+ interval = maxinterval;
+
+ if (interval.tv_sec == 0
+ && interval.tv_usec <= MIN_POLL_INTERVAL)
+ interval.tv_usec = MIN_POLL_INTERVAL;
+
+ r = select(0, 0, 0, 0, &interval);
+ if (r < 0 && errno != EINTR)
+ fatal("select() failed for pause: %s",
+ strerror(errno));
+ }
+
+ case sched_forever:
+ assert(!"schedule[].type value must be valid");
+ }
+
+ next_item:
+ position++;
+ }
+
+ if (quietmode <= 0)
+ printf("Program %s, %d process(es), refused to die.\n",
+ what_stop, n_killed);
+
+ return 2;
+
+x_finished:
+ if (!anykilled) {
+ if (quietmode <= 0)
+ printf("No %s found running; none killed.\n",
+ what_stop);
+ return exitnodo;
+ } else {
+ return 0;
+ }
+}
+
+/*
+int main(int argc, char **argv) NONRETURNING;
+*/
+
+int main(int argc, char **argv)
+{
+ progname = argv[0];
+
+ LIST_INIT(&namespace_head);
+
+ parse_options(argc, argv);
+ argc -= optind;
+ argv += optind;
+
+ if (execname && stat(execname, &exec_stat))
+ fatal("stat %s: %s", execname, strerror(errno));
+
+ if (userspec && sscanf(userspec, "%d", &user_id) != 1) {
+ struct passwd *pw;
+
+ pw = getpwnam(userspec);
+ if (!pw)
+ fatal("user `%s' not found\n", userspec);
+
+ user_id = pw->pw_uid;
+ }
+
+ if (changegroup && sscanf(changegroup, "%d", &runas_gid) != 1) {
+ struct group *gr = getgrnam(changegroup);
+ if (!gr)
+ fatal("group `%s' not found\n", changegroup);
+ runas_gid = gr->gr_gid;
+ }
+ if (changeuser && sscanf(changeuser, "%d", &runas_uid) != 1) {
+ struct passwd *pw = getpwnam(changeuser);
+ if (!pw)
+ fatal("user `%s' not found\n", changeuser);
+ runas_uid = pw->pw_uid;
+ if (changegroup
+ == NULL) { /* pass the default group of this user */
+ changegroup = ""; /* just empty */
+ runas_gid = pw->pw_gid;
+ }
+ }
+
+ if (stop) {
+ int i = run_stop_schedule();
+ exit(i);
+ }
+
+ do_findprocs();
+
+ if (found) {
+ if (quietmode <= 0)
+ printf("%s already running.\n", execname);
+ exit(exitnodo);
+ }
+ if (testmode) {
+ printf("Would start %s ", startas);
+ while (argc-- > 0)
+ printf("%s ", *argv++);
+ if (changeuser != NULL) {
+ printf(" (as user %s[%d]", changeuser, runas_uid);
+ if (changegroup != NULL)
+ printf(", and group %s[%d])", changegroup,
+ runas_gid);
+ else
+ printf(")");
+ }
+ if (changeroot != NULL)
+ printf(" in directory %s", changeroot);
+ if (nicelevel)
+ printf(", and add %i to the priority", nicelevel);
+ printf(".\n");
+ exit(0);
+ }
+ if (quietmode < 0)
+ printf("Starting %s...\n", startas);
+ *--argv = startas;
+ if (changeroot != NULL) {
+ if (chdir(changeroot) < 0)
+ fatal("Unable to chdir() to %s", changeroot);
+ if (chroot(changeroot) < 0)
+ fatal("Unable to chroot() to %s", changeroot);
+ }
+ if (changeuser != NULL) {
+ if (setgid(runas_gid))
+ fatal("Unable to set gid to %d", runas_gid);
+ if (initgroups(changeuser, runas_gid))
+ fatal("Unable to set initgroups() with gid %d",
+ runas_gid);
+ if (setuid(runas_uid))
+ fatal("Unable to set uid to %s", changeuser);
+ }
+
+ if (background) { /* ok, we need to detach this process */
+ int i, fd;
+ if (quietmode < 0)
+ printf("Detaching to start %s...", startas);
+ i = fork();
+ if (i < 0) {
+ fatal("Unable to fork.\n");
+ }
+ if (i) { /* parent */
+ if (quietmode < 0)
+ printf("done.\n");
+ exit(0);
+ }
+ /* child continues here */
+ /* now close all extra fds */
+ for (i = getdtablesize() - 1; i >= 0; --i)
+ close(i);
+ /* change tty */
+ fd = open("/dev/tty", O_RDWR);
+ if (fd >= 0) {
+ if (ioctl(fd, TIOCNOTTY, 0) < 0)
+ printf("ioctl TIOCNOTTY failed: %s\n",
+ strerror(errno));
+ close(fd);
+ }
+ chdir("/");
+ umask(022); /* set a default for dumb programs */
+ setpgid(0, 0); /* set the process group */
+ fd = open("/dev/null", O_RDWR); /* stdin */
+ if (fd >= 0) {
+ dup(fd); /* stdout */
+ dup(fd); /* stderr */
+ }
+ }
+ if (nicelevel) {
+ errno = 0;
+ if (nice(nicelevel) < 0 && errno)
+ fatal("Unable to alter nice level by %i: %s", nicelevel,
+ strerror(errno));
+ }
+ if (mpidfile
+ && pidfile != NULL) { /* user wants _us_ to make the pidfile :) */
+ FILE *pidf = fopen(pidfile, "w");
+ pid_t pidt = getpid();
+ if (pidf == NULL)
+ fatal("Unable to open pidfile `%s' for writing: %s",
+ pidfile, strerror(errno));
+ fprintf(pidf, "%ld\n", (long)pidt);
+ fclose(pidf);
+ }
+ set_namespaces();
+ execv(startas, argv);
+ fatal("Unable to start %s: %s", startas, strerror(errno));
+}
diff --git a/tools/stringmangle.py b/tools/stringmangle.py
new file mode 100644
index 0000000..29fcb86
--- /dev/null
+++ b/tools/stringmangle.py
@@ -0,0 +1,62 @@
+# SPDX-License-Identifier: NONE
+# 2020 by David Lamparter, placed in the public domain.
+
+import sys
+import os
+import re
+import argparse
+
+wrap_res = [
+ (re.compile(r'(?<!\\n)"\s*\n\s*"', re.M), r""),
+]
+pri_res = [
+ (re.compile(r'(PRI[udx][0-9]+)\s*\n\s*"', re.M), r'\1"'),
+ (re.compile(r'"\s*PRI([udx])32\s*"'), r"\1"),
+ (re.compile(r'"\s*PRI([udx])32'), r'\1"'),
+ (re.compile(r'"\s*PRI([udx])16\s*"'), r"h\1"),
+ (re.compile(r'"\s*PRI([udx])16'), r'h\1"'),
+ (re.compile(r'"\s*PRI([udx])8\s*"'), r"hh\1"),
+ (re.compile(r'"\s*PRI([udx])8'), r'hh\1"'),
+]
+
+
+def main():
+ argp = argparse.ArgumentParser(description="C string mangler")
+ argp.add_argument("--unwrap", action="store_const", const=True)
+ argp.add_argument("--pri8-16-32", action="store_const", const=True)
+ argp.add_argument("files", type=str, nargs="+")
+ args = argp.parse_args()
+
+ regexes = []
+ if args.unwrap:
+ regexes.extend(wrap_res)
+ if args.pri8_16_32:
+ regexes.extend(pri_res)
+ if len(regexes) == 0:
+ sys.stderr.write("no action selected to execute\n")
+ sys.exit(1)
+
+ l = 0
+
+ for fn in args.files:
+ sys.stderr.write(fn + "\033[K\r")
+ with open(fn, "r") as ifd:
+ data = ifd.read()
+
+ newdata = data
+ n = 0
+ for regex, repl in regexes:
+ newdata, m = regex.subn(repl, newdata)
+ n += m
+
+ if n > 0:
+ sys.stderr.write("changed: %s\n" % fn)
+ with open(fn + ".new", "w") as ofd:
+ ofd.write(newdata)
+ os.rename(fn + ".new", fn)
+ l += 1
+
+ sys.stderr.write("%d files changed.\n" % (l))
+
+
+main()
diff --git a/tools/subdir.am b/tools/subdir.am
new file mode 100644
index 0000000..64ca0bd
--- /dev/null
+++ b/tools/subdir.am
@@ -0,0 +1,69 @@
+#
+# tools
+#
+
+noinst_PROGRAMS += \
+ tools/permutations \
+ tools/gen_northbound_callbacks \
+ tools/gen_yang_deviations \
+ # end
+
+EXTRA_PROGRAMS += \
+ tools/frr-llvm-cg \
+ # end
+
+sbin_PROGRAMS += tools/ssd
+sbin_SCRIPTS += \
+ tools/frr-reload \
+ tools/frr-reload.py \
+ tools/frr \
+ \
+ tools/frrcommon.sh \
+ tools/frrinit.sh \
+ tools/generate_support_bundle.py \
+ tools/frr_babeltrace.py \
+ tools/watchfrr.sh \
+ # end
+
+tools_permutations_SOURCES = tools/permutations.c
+tools_permutations_LDADD = lib/libfrr.la
+
+tools_gen_northbound_callbacks_SOURCES = tools/gen_northbound_callbacks.c
+tools_gen_northbound_callbacks_LDADD = lib/libfrr.la $(LIBYANG_LIBS)
+
+tools_gen_yang_deviations_SOURCES = tools/gen_yang_deviations.c
+tools_gen_yang_deviations_LDADD = lib/libfrr.la $(LIBYANG_LIBS)
+
+tools_ssd_SOURCES = tools/start-stop-daemon.c
+tools_ssd_CPPFLAGS =
+
+# don't bother autoconf'ing these for a simple optional tool
+llvm_version = $(shell echo __clang_major__ | $(CC) -xc -P -E -)
+tools_frr_llvm_cg_CPPFLAGS = $(CPPFLAGS_BASE)
+tools_frr_llvm_cg_CFLAGS = $(AM_CFLAGS) `llvm-config-$(llvm_version) --cflags`
+tools_frr_llvm_cg_CXXFLAGS = $(AM_CXXFLAGS) -O0 -ggdb3 `llvm-config-$(llvm_version) --cxxflags`
+tools_frr_llvm_cg_LDFLAGS = `llvm-config-$(llvm_version) --ldflags --libs`
+tools_frr_llvm_cg_SOURCES = \
+ tools/frr-llvm-cg.c \
+ tools/frr-llvm-debuginfo.cpp \
+ # end
+
+noinst_HEADERS += \
+ tools/frr-llvm-debuginfo.h \
+ # end
+
+EXTRA_DIST += \
+ tools/etc \
+ tools/frr-reload \
+ tools/frr-reload.py \
+ tools/frr.service \
+ tools/frr@.service \
+ tools/generate_support_bundle.py \
+ tools/frr_babeltrace.py \
+ tools/multiple-bgpd.sh \
+ tools/rrcheck.pl \
+ tools/rrlookup.pl \
+ tools/zc.pl \
+ tools/zebra.el \
+ tools/build-debian-package.sh \
+ # end
diff --git a/tools/symalyzer.html b/tools/symalyzer.html
new file mode 100644
index 0000000..b136ce5
--- /dev/null
+++ b/tools/symalyzer.html
@@ -0,0 +1,348 @@
+<html>
+<!--
+ - SPDX-License-Identifier: NONE
+ - 2019 by David Lamparter, placed in public domain
+ -->
+ <head>
+ <title>Symalyzer report</title>
+ <style type="text/css">
+html {
+ margin:auto;
+ max-width:70em;
+ font-family:Fira Sans, sans-serif;
+}
+dl {
+ display:grid;
+ grid-template-columns: 1.4em 1.4em 1fr 1fr;
+ grid-auto-rows: auto;
+}
+dt.dir {
+ background-color:#ff8;
+ color:#000;
+ border:1px solid #000;
+ border-bottom:2px solid #000;
+ font-size:14pt;
+ padding:2pt 15pt;
+ margin:0pt;
+ margin-top:10pt;
+ grid-column:1 / -1;
+}
+dt.file {
+ background-color:#ffa;
+ color:#000;
+ border-bottom:1px solid #000;
+ font-size:12pt;
+ padding:2pt 15pt;
+ margin:5pt 0pt;
+ grid-column:1 / -1;
+}
+dt.file.filehidden {
+ background-color:#ffc;
+ font-size:10pt;
+ padding:0.5pt 15pt;
+ margin-bottom:-5pt;
+}
+dd {
+ display:inline-block;
+ vertical-align:middle;
+ margin:0;
+}
+dd.symtype {
+ grid-column:1;
+
+ border:1px solid #666;
+ text-align:center;
+}
+dd.symklass {
+ grid-column:2;
+
+ border:1px solid #666;
+ text-align:center;
+}
+dd.symname {
+ grid-column:3;
+
+ font-family:monospace;
+ padding:0 0.5em;
+ padding-top:2px;
+ border-bottom:1px dashed #ccc;
+}
+dd.symloc {
+ grid-column:4;
+
+ padding:0 0.5em;
+ border-bottom:1px dashed #ccc;
+}
+.symloc-unknown {
+ font-style:italic;
+ color:#aaa;
+}
+
+.symtype.sym-static {
+ background-color:#cf4;
+ color:#000;
+}
+.symtype.sym-extrastatic {
+ background-color:#fe8;
+ color:#000;
+}
+.symtype.sym-liblocal {
+ background-color:#fc6;
+ color:#000;
+}
+
+.symklass.symk-T {
+ background-color:#ddd;
+ color:#000;
+}
+.symklass.symk-B,
+.symklass.symk-C,
+.symklass.symk-D {
+ background-color:#faa;
+ color:#000;
+}
+.symklass.symk-R {
+ background-color:#fd8;
+ color:#000;
+}
+
+.symtype.sym-api {
+ background-color:#d9f;
+ color:#000;
+}
+.symname.sym-api,
+.symloc.sym-api {
+ background-color:#f8e8ff;
+}
+
+dt.file.dirhidden,
+dd.dirhidden {
+ display:none;
+}
+dd.filehidden {
+ display:none;
+}
+dd.symhidden {
+ display:none;
+}
+
+ul {
+ font-size:10pt;
+}
+li {
+ margin-bottom:6pt;
+ text-indent:-2.5em;
+ margin-left:2.5em;
+}
+code {
+ background-color:#eee;
+ color:#060;
+ text-decoration:underline;
+}
+b.symtype,
+b.symklass {
+ display:inline-block;
+ text-align:center;
+ border:1px solid #666;
+ width:1.4em;
+ text-indent:0;
+}
+ </style>
+ <script src="jquery-3.4.1.min.js"></script>
+ <script>
+
+function dirtoggle(elem, visible) {
+ if (visible) {
+ elem.removeClass("dirhidden");
+ } else {
+ elem.addClass("dirhidden");
+ }
+
+ var next = elem.next();
+ while (next.is("dd") || next.is("dt.file")) {
+ if (visible) {
+ next.removeClass("dirhidden");
+ } else {
+ next.addClass("dirhidden");
+ }
+ next = next.next();
+ }
+}
+
+function filetoggle(elem, visible) {
+ if (visible) {
+ elem.removeClass("filehidden");
+ } else {
+ elem.addClass("filehidden");
+ }
+
+ var next = elem.next();
+ while (next.is("dd")) {
+ if (visible) {
+ next.removeClass("filehidden");
+ } else {
+ next.addClass("filehidden");
+ }
+ next = next.next();
+ }
+}
+
+function symtoggle(elem, visible) {
+ if (visible) {
+ elem.removeClass("symhidden");
+ } else {
+ elem.addClass("symhidden");
+ }
+
+ var next = elem.next();
+ while (next.is(".symklass") || next.is(".symname") || next.is(".symloc")) {
+ if (visible) {
+ next.removeClass("symhidden");
+ } else {
+ next.addClass("symhidden");
+ }
+ next = next.next();
+ }
+}
+
+
+$(document).ready(function(){
+ $("dt.dir").each(function(){
+ var elem = $(this);
+
+ elem.click(function(){
+ dirtoggle(elem, elem.is(".dirhidden"));
+ });
+
+ dirtoggle(elem, false);
+ });
+
+ $("dt.file").each(function(){
+ var elem = $(this);
+
+ elem.click(function(){
+ filetoggle(elem, elem.is(".filehidden"));
+ });
+
+ /* filetoggle(elem, false); */
+ });
+
+ $("#f_hide_all").click(function(){
+ $("dt.file").each(function(){
+ filetoggle($(this), false);
+ });
+ });
+ $("#f_show_all").click(function(){
+ $("dt.file").each(function(){
+ filetoggle($(this), true);
+ });
+ });
+
+ $("#s_show_all").click(function(){
+ $("dd.symtype").each(function(){
+ symtoggle($(this), true);
+ });
+ });
+ $("#s_hide_all").click(function(){
+ $("dd.symtype").each(function(){
+ symtoggle($(this), false);
+ });
+ });
+ $("#s_show_vars").click(function(){
+ $("dd.symtype").each(function(){
+ var elem_type = $(this);
+ if (elem_type.text() === "A") {
+ return;
+ }
+
+ var elem_klass = elem_type.next();
+
+ if ("BbCDdGgnRrSs".indexOf(elem_klass.text()) >= 0) {
+ symtoggle(elem_type, true);
+ }
+ });
+ });
+ $("#s_show_funcs").click(function(){
+ $("dd.symtype").each(function(){
+ var elem_type = $(this);
+ if (elem_type.text() === "A") {
+ return;
+ }
+
+ var elem_klass = elem_type.next();
+
+ if ("Tt".indexOf(elem_klass.text()) >= 0) {
+ symtoggle(elem_type, true);
+ }
+ });
+ });
+ $("#s_show_api").click(function(){
+ $("dd.sym-api").each(function(){
+ symtoggle($(this), true);
+ });
+ });
+
+ $("#jsbuttons").show();
+});
+ </script>
+ </head>
+ <body>
+ <table style="display:none" id="jsbuttons">
+ <tr><td>Files</td><td>
+ <button type="button" id="f_hide_all">Hide all</button>
+ <button type="button" id="f_show_all">Show all</button>
+ </td></tr>
+ <tr><td>Symbols</td><td>
+ <button type="button" id="s_hide_all">Hide all</button>
+ <button type="button" id="s_show_all">Show all</button><br>
+ <button type="button" id="s_show_vars">Show variables</button>
+ <button type="button" id="s_show_funcs">Show functions</button>
+ <button type="button" id="s_show_api">Show module/API usage</button>
+ </td></tr>
+ </table>
+ <div style="display:grid;grid-template-columns:1fr 1fr;">
+ <ul>
+ <li><b class="symtype sym-static">S</b> means the symbol is not used outside its own file.
+ It could either be completely unused or used locally. It might be appropriate to make it
+ <code>static</code>.</li>
+ <li><b class="symtype sym-extrastatic">Z</b> means the symbol is not used outside its own file,
+ and it's not visible to the outside of the library or daemon (i.e. ELF hidden linkage.)
+ It could still be completely unused, or used within the library. It might be appropriate to make it
+ <code>static</code>.</li>
+ <li><b class="symtype sym-liblocal">L</b> means the symbol is used from other files in the library,
+ but not from outside. It might be appropriate to make it <code>DSO_LOCAL</code>.</li>
+ <li><b class="symtype sym-api">A</b> means the symbol is used from some other file, most likely a
+ loadable module. Note this is only flagged for symbols in executable files, not libraries.</li>
+ </ul>
+ <ul>
+ <li><b class="symklass symk-T">T</b> are normal functions ("program <u>T</u>ext")</li>
+ <li style="text-indent:0;margin-left:0">
+ <b class="symklass symk-B">B</b> (<u>B</u>SS),<br>
+ <b class="symklass symk-C">C</b> (<u>C</u>ommon),<br>
+ <b class="symklass symk-D">D</b> (<u>D</u>ata)<br>
+ are various types of writable global variables</li>
+ <li><b class="symklass symk-R">R</b> are read-only global variables ("<u>R</u>odata")</li>
+ </ul>
+ </div>
+ <dl>
+ {%- for subdir, subreport in dirgroups.items()|sort %}
+ <dt class="dir">{{ subdir }}</dt>
+ {%- for obj, reports in subreport.items()|sort %}
+ <dt class="file">{{ obj }}</dt>
+ {%- for report in reports|sort %}
+ {#- <dd class="{{ report.idlong }}"> #}
+ <dd class="sym-{{ report.idlong }} symtype" title="{{ report.title }}">{{ report.idshort }}</dd>
+ <dd class="sym-{{ report.idlong }} symk-{{ report.sym.klass }} symklass" title="{{ klasses.get(report.sym.klass, '???') }}">{{ report.sym.klass }}</dd>
+ <dd class="sym-{{ report.idlong }} symname">{{ report.sym.name }}</dd>
+ {% if report.sym.loc %}
+ <dd class="sym-{{ report.idlong }} symloc">{{ report.sym.loc }}</dd>
+ {% else %}
+ <dd class="sym-{{ report.idlong }} symloc symloc-unknown">unknown</dd>
+ {% endif %}
+ {#- </dd> #}
+ {%- endfor %}
+ {%- endfor %}
+ {%- endfor %}
+ </dl>
+ </body>
+</html>
diff --git a/tools/symalyzer.py b/tools/symalyzer.py
new file mode 100755
index 0000000..7d37a1a
--- /dev/null
+++ b/tools/symalyzer.py
@@ -0,0 +1,407 @@
+#!/usr/bin/python3
+# SPDX-License-Identifier: NONE
+#
+# 2019 by David Lamparter, placed in public domain
+#
+# This tool generates a report of possibly unused symbols in the build. It's
+# particularly useful for libfrr to find bitrotting functions that aren't even
+# used anywhere anymore.
+#
+# Note that the tool can't distinguish between "a symbol is completely unused"
+# and "a symbol is used only in its file" since file-internal references are
+# invisible in nm output. However, the compiler will warn you if a static
+# symbol is unused.
+#
+# This tool is only tested on Linux, it probably needs `nm` from GNU binutils
+# (as opposed to BSD `nm`). Could use pyelftools instead but that's a lot of
+# extra work.
+#
+# This is a developer tool, please don't put it in any packages :)
+
+import sys, os, subprocess
+import re
+from collections import namedtuple
+
+sys.path.insert(
+ 0,
+ os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), "python"),
+)
+
+from makevars import MakeVars
+
+SymRowBase = namedtuple(
+ "SymRow",
+ [
+ "target",
+ "object",
+ "name",
+ "address",
+ "klass",
+ "typ",
+ "size",
+ "line",
+ "section",
+ "loc",
+ ],
+)
+
+
+class SymRow(SymRowBase):
+ """
+ wrapper around a line of `nm` output
+ """
+
+ lib_re = re.compile(r"/lib[^/]+\.(so|la)$")
+
+ def is_global(self):
+ return self.klass.isupper() or self.klass in "uvw"
+
+ def scope(self):
+ if self.lib_re.search(self.target) is None:
+ return self.target
+ # "global"
+ return None
+
+ def is_export(self):
+ """
+ FRR-specific list of symbols which are considered "externally used"
+
+ e.g. hooks are by design APIs for external use, same for qobj_t_*
+ frr_inet_ntop is here because it's used through an ELF alias to
+ "inet_ntop()"
+ """
+ if self.name in ["main", "frr_inet_ntop", "_libfrr_version"]:
+ return True
+ if self.name.startswith("_hook_"):
+ return True
+ if self.name.startswith("qobj_t_"):
+ return True
+ return False
+
+
+class Symbols(dict):
+ """
+ dict of all symbols in all libs & executables
+ """
+
+ from_re = re.compile(r"^Symbols from (.*?):$")
+ lt_re = re.compile(r"^(.*/)([^/]+)\.l[oa]$")
+
+ def __init__(self):
+ super().__init__()
+
+ class ReportSym(object):
+ def __init__(self, sym):
+ self.sym = sym
+
+ def __repr__(self):
+ return "<%-25s %-40s [%s]>" % (
+ self.__class__.__name__ + ":",
+ self.sym.name,
+ self.sym.loc,
+ )
+
+ def __lt__(self, other):
+ return self.sym.name.__lt__(other.sym.name)
+
+ class ReportSymCouldBeStaticAlreadyLocal(ReportSym):
+ idshort = "Z"
+ idlong = "extrastatic"
+ title = "symbol is local to library, but only used in its source file (make static?)"
+
+ class ReportSymCouldBeStatic(ReportSym):
+ idshort = "S"
+ idlong = "static"
+ title = "symbol is only used in its source file (make static?)"
+
+ class ReportSymCouldBeLibLocal(ReportSym):
+ idshort = "L"
+ idlong = "liblocal"
+ title = "symbol is only used inside of library"
+
+ class ReportSymModuleAPI(ReportSym):
+ idshort = "A"
+ idlong = "api"
+ title = "symbol (in executable) is referenced externally from a module"
+
+ class Symbol(object):
+ def __init__(self, name):
+ super().__init__()
+ self.name = name
+ self.defs = {}
+ self.refs = []
+
+ def process(self, row):
+ scope = row.scope()
+ if row.section == "*UND*":
+ self.refs.append(row)
+ else:
+ self.defs.setdefault(scope, []).append(row)
+
+ def evaluate(self, out):
+ """
+ generate output report
+
+ invoked after all object files have been read in, so it can look
+ at inter-object-file relationships
+ """
+ if len(self.defs) == 0:
+ out.extsyms.add(self.name)
+ return
+
+ for scopename, symdefs in self.defs.items():
+ common_defs = [
+ symdef for symdef in symdefs if symdef.section == "*COM*"
+ ]
+ proper_defs = [
+ symdef for symdef in symdefs if symdef.section != "*COM*"
+ ]
+
+ if len(proper_defs) > 1:
+ print(self.name, " DUPLICATE")
+ print(
+ "\tD: %s %s"
+ % (scopename, "\n\t\t".join([repr(s) for s in symdefs]))
+ )
+ for syms in self.refs:
+ print("\tR: %s" % (syms,))
+ return
+
+ if len(proper_defs):
+ primary_def = proper_defs[0]
+ elif len(common_defs):
+ # "common" = global variables without initializer;
+ # they can occur in multiple .o files and the linker will
+ # merge them into one variable/storage location.
+ primary_def = common_defs[0]
+ else:
+ # undefined symbol, e.g. libc
+ continue
+
+ if scopename is not None and len(self.refs) > 0:
+ for ref in self.refs:
+ if ref.target != primary_def.target and ref.target.endswith(
+ ".la"
+ ):
+ outobj = out.report.setdefault(primary_def.object, [])
+ outobj.append(out.ReportSymModuleAPI(primary_def))
+ break
+
+ if len(self.refs) == 0:
+ if primary_def.is_export():
+ continue
+ outobj = out.report.setdefault(primary_def.object, [])
+ if primary_def.visible:
+ outobj.append(out.ReportSymCouldBeStatic(primary_def))
+ else:
+ outobj.append(
+ out.ReportSymCouldBeStaticAlreadyLocal(primary_def)
+ )
+ continue
+
+ if scopename is None and primary_def.visible:
+ # lib symbol
+ for ref in self.refs:
+ if ref.target != primary_def.target:
+ break
+ else:
+ outobj = out.report.setdefault(primary_def.object, [])
+ outobj.append(out.ReportSymCouldBeLibLocal(primary_def))
+
+ def evaluate(self):
+ self.extsyms = set()
+ self.report = {}
+
+ for sym in self.values():
+ sym.evaluate(self)
+
+ def load(self, target, files):
+ def libtoolmustdie(fn):
+ m = self.lt_re.match(fn)
+ if m is None:
+ return fn
+ return m.group(1) + ".libs/" + m.group(2) + ".o"
+
+ def libtooltargetmustdie(fn):
+ m = self.lt_re.match(fn)
+ if m is None:
+ a, b = fn.rsplit("/", 1)
+ return "%s/.libs/%s" % (a, b)
+ return m.group(1) + ".libs/" + m.group(2) + ".so"
+
+ files = list(set([libtoolmustdie(fn) for fn in files]))
+
+ def parse_nm_output(text):
+ filename = None
+ path_rel_to = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
+
+ for line in text.split("\n"):
+ if line.strip() == "":
+ continue
+ m = self.from_re.match(line)
+ if m is not None:
+ filename = m.group(1)
+ continue
+ if line.startswith("Name"):
+ continue
+
+ items = [i.strip() for i in line.split("|")]
+ loc = None
+ if "\t" in items[-1]:
+ items[-1], loc = items[-1].split("\t", 1)
+ fn, lno = loc.rsplit(":", 1)
+ fn = os.path.relpath(fn, path_rel_to)
+ loc = "%s:%s" % (fn, lno)
+
+ items[1] = int(items[1] if items[1] != "" else "0", 16)
+ items[4] = int(items[4] if items[4] != "" else "0", 16)
+ items.append(loc)
+ row = SymRow(target, filename, *items)
+
+ if row.section == ".group" or row.name == "_GLOBAL_OFFSET_TABLE_":
+ continue
+ if not row.is_global():
+ continue
+
+ yield row
+
+ visible_syms = set()
+
+ # the actual symbol report uses output from the individual object files
+ # (e.g. lib/.libs/foo.o), but we also read the linked binary (e.g.
+ # lib/.libs/libfrr.so) to determine which symbols are actually visible
+ # in the linked result (this covers ELF "hidden"/"internal" linkage)
+
+ libfile = libtooltargetmustdie(target)
+ nmlib = subprocess.Popen(
+ ["nm", "-l", "-g", "--defined-only", "-f", "sysv", libfile],
+ stdout=subprocess.PIPE,
+ )
+ out = nmlib.communicate()[0].decode("US-ASCII")
+
+ for row in parse_nm_output(out):
+ visible_syms.add(row.name)
+
+ nm = subprocess.Popen(
+ ["nm", "-l", "-f", "sysv"] + files, stdout=subprocess.PIPE
+ )
+ out = nm.communicate()[0].decode("US-ASCII")
+
+ for row in parse_nm_output(out):
+ row.visible = row.name in visible_syms
+ sym = self.setdefault(row.name, self.Symbol(row.name))
+ sym.process(row)
+
+
+def write_html_report(syms):
+ try:
+ import jinja2
+ except ImportError:
+ sys.stderr.write("jinja2 could not be imported, not writing HTML report!\n")
+ return
+
+ self_path = os.path.dirname(os.path.abspath(__file__))
+ jenv = jinja2.Environment(loader=jinja2.FileSystemLoader(self_path))
+ template = jenv.get_template("symalyzer.html")
+
+ dirgroups = {}
+ for fn, reports in syms.report.items():
+ dirname, filename = fn.replace(".libs/", "").rsplit("/", 1)
+ dirgroups.setdefault(dirname, {})[fn] = reports
+
+ klasses = {
+ "T": "code / plain old regular function (Text)",
+ "D": "global variable, read-write, with nonzero initializer (Data)",
+ "B": "global variable, read-write, with zero initializer (BSS)",
+ "C": "global variable, read-write, with zero initializer (Common)",
+ "R": "global variable, read-only (Rodata)",
+ }
+
+ with open("symalyzer_report.html.tmp", "w") as fd:
+ fd.write(template.render(dirgroups=dirgroups, klasses=klasses))
+ os.rename("symalyzer_report.html.tmp", "symalyzer_report.html")
+
+ if not os.path.exists("jquery-3.4.1.min.js"):
+ url = "https://code.jquery.com/jquery-3.4.1.min.js"
+ sys.stderr.write(
+ "trying to grab a copy of jquery from %s\nif this fails, please get it manually (the HTML output is done.)\n"
+ % (url)
+ )
+ import requests
+
+ r = requests.get("https://code.jquery.com/jquery-3.4.1.min.js")
+ if r.status_code != 200:
+ sys.stderr.write(
+ "failed -- please download jquery-3.4.1.min.js and put it next to the HTML report\n"
+ )
+ else:
+ with open("jquery-3.4.1.min.js.tmp", "w") as fd:
+ fd.write(r.text)
+ os.rename("jquery-3.4.1.min.js.tmp", "jquery-3.4.1.min.js")
+ sys.stderr.write("done.\n")
+
+
+def automake_escape(s):
+ return s.replace(".", "_").replace("/", "_")
+
+
+if __name__ == "__main__":
+ mv = MakeVars()
+
+ if not (os.path.exists("config.version") and os.path.exists("lib/.libs/libfrr.so")):
+ sys.stderr.write(
+ "please execute this script in the root directory of an FRR build tree\n"
+ )
+ sys.stderr.write("./configure && make need to have completed successfully\n")
+ sys.exit(1)
+
+ amtargets = [
+ "bin_PROGRAMS",
+ "sbin_PROGRAMS",
+ "lib_LTLIBRARIES",
+ "module_LTLIBRARIES",
+ ]
+ targets = []
+
+ mv.getvars(amtargets)
+ for amtarget in amtargets:
+ targets.extend(
+ [item for item in mv[amtarget].strip().split() if item != "tools/ssd"]
+ )
+
+ mv.getvars(["%s_LDADD" % automake_escape(t) for t in targets])
+ ldobjs = targets[:]
+ for t in targets:
+ ldadd = mv["%s_LDADD" % automake_escape(t)].strip().split()
+ for item in ldadd:
+ if item.startswith("-"):
+ continue
+ if item.endswith(".a"):
+ ldobjs.append(item)
+
+ mv.getvars(["%s_OBJECTS" % automake_escape(o) for o in ldobjs])
+
+ syms = Symbols()
+
+ for t in targets:
+ objs = mv["%s_OBJECTS" % automake_escape(t)].strip().split()
+ ldadd = mv["%s_LDADD" % automake_escape(t)].strip().split()
+ for item in ldadd:
+ if item.startswith("-"):
+ continue
+ if item.endswith(".a"):
+ objs.extend(mv["%s_OBJECTS" % automake_escape(item)].strip().split())
+
+ sys.stderr.write("processing %s...\n" % t)
+ sys.stderr.flush()
+ # print(t, '\n\t', objs)
+ syms.load(t, objs)
+
+ syms.evaluate()
+
+ for obj, reports in sorted(syms.report.items()):
+ print("%s:" % obj)
+ for report in reports:
+ print("\t%r" % report)
+
+ write_html_report(syms)
diff --git a/tools/valgrind.supp b/tools/valgrind.supp
new file mode 100644
index 0000000..da3d4a8
--- /dev/null
+++ b/tools/valgrind.supp
@@ -0,0 +1,88 @@
+{
+ <zlog_keep_working_at_exit>
+ Memcheck:Leak
+ match-leak-kinds: reachable
+ fun:calloc
+ fun:qcalloc
+ fun:zlog_target_clone
+}
+{
+ <libyang1_1.0.184>
+ Memcheck:Leak
+ match-leak-kinds: reachable
+ fun:calloc
+ fun:_dlerror_run
+ fun:dlopen@@GLIBC_2.2.5
+ obj:/usr/lib/x86_64-linux-gnu/libyang.so.1.9.2
+ fun:ly_load_plugins
+}
+{
+ <zprivs_init leak in a function we do not control>
+ Memcheck:Leak
+ fun:calloc
+ fun:cap_init
+ fun:zprivs_caps_init
+}
+{
+ <sqlite3 leak in a function we do not control>
+ Memcheck:Leak
+ fun:malloc
+ ...
+ fun:sqlite3_step
+}
+{
+ <libyang2 prefix_data stuff>
+ Memcheck:Leak
+ fun:calloc
+ fun:ly_store_prefix_data
+ ...
+ fun:yang_module_load
+}
+{
+ <libyang2 lys_compile_type_union>
+ Memcheck:Leak
+ fun:realloc
+ fun:lys_compile_type_union
+ ...
+ fun:yang_module_load
+}
+{
+ <libyang2 pcre2_compile>
+ Memcheck:Leak
+ fun:malloc
+ fun:pcre2_compile_8
+ ...
+ fun:yang_module_load
+}
+{
+ <libyang2 lys_compile_type_patterns malloc>
+ Memcheck:Leak
+ fun:malloc
+ fun:lys_compile_type_patterns
+ ...
+ fun:yang_module_load
+}
+{
+ <libyang2 lys_compile_type_patterns calloc>
+ Memcheck:Leak
+ fun:calloc
+ fun:lys_compile_type_patterns
+ ...
+ fun:yang_module_load
+}
+{
+ <libyang2 lys_compile_type>
+ Memcheck:Leak
+ fun:calloc
+ fun:lys_compile_type
+ ...
+ fun:yang_module_load
+}
+{
+ <libyang2 lys_compile_type_range>
+ Memcheck:Leak
+ ...
+ fun:lys_compile_type_range
+ ...
+ fun:yang_module_load
+}
diff --git a/tools/vty_index.sh b/tools/vty_index.sh
new file mode 100755
index 0000000..0ac8bfb
--- /dev/null
+++ b/tools/vty_index.sh
@@ -0,0 +1,21 @@
+#!/bin/sh
+
+sed -e '1istatic void bgp_debug_clear_updgrp_update_dbg(struct bgp *bgp);' -i \
+ bgpd/bgp_debug.c
+sed -e 's%^#if 0%#if 1 /* 0 */%' -i \
+ ospfd/ospf_vty.c \
+ ospf6d/ospf6_top.c \
+ #
+spatch \
+ --sp-file tools/vty_index.cocci \
+ --macro-file tools/cocci.h \
+ --all-includes -I . -I lib \
+ --use-gitgrep --dir . --in-place
+
+sed -e 's%^#if 1 /\* 0 \*/%#if 0%' -i \
+ ospfd/ospf_vty.c \
+ ospf6d/ospf6_top.c \
+ #
+sed -e '1d' -i \
+ bgpd/bgp_debug.c
+
diff --git a/tools/watchfrr.sh.in b/tools/watchfrr.sh.in
new file mode 100644
index 0000000..712f962
--- /dev/null
+++ b/tools/watchfrr.sh.in
@@ -0,0 +1,33 @@
+#!/bin/bash
+#
+# This is NOT the init script! This is the watchfrr start/stop/restart
+# command handler, passed to watchfrr with the -s/-r/-k commands. It is used
+# internally by watchfrr to start the protocol daemons with the appropriate
+# options.
+#
+# This script should be installed in @CFG_SBIN@/watchfrr.sh
+
+log_success_msg() {
+ :
+}
+
+log_warning_msg() {
+ echo "$@" >&2
+ [ -x /usr/bin/logger ] && echo "$@" \
+ | /usr/bin/logger -t watchfrr.sh -p daemon.warn
+}
+
+log_failure_msg() {
+ echo "$@" >&2
+ [ -x /usr/bin/logger ] && echo "$@" \
+ | /usr/bin/logger -t watchfrr.sh -p daemon.err
+}
+
+self="`dirname $0`"
+if [ -r "$self/frrcommon.sh" ]; then
+ . "$self/frrcommon.sh"
+else
+ . "@CFG_SBIN@/frrcommon.sh"
+fi
+
+frrcommon_main "$@"
diff --git a/tools/zc.pl b/tools/zc.pl
new file mode 100755
index 0000000..77af500
--- /dev/null
+++ b/tools/zc.pl
@@ -0,0 +1,95 @@
+#!/usr/bin/env perl
+# SPDX-License-Identifier: GPL-2.0-or-later
+##
+## Zebra interactive console
+## Copyright (C) 2000 Vladimir B. Grebenschikov <vova@express.ru>
+
+use Net::Telnet ();
+use Getopt::Std;
+
+#use strict;
+
+my $host = `hostname -s`; $host =~ s/\s//g;
+my $port = 'zebra';
+my $server = 'localhost';
+
+# Check arguments
+&getopts ('l:e:czborh');
+
+&usage () if $opt_h;
+
+# main
+{
+ my $login_pass = $opt_l || $ENV{ZEBRA_PASSWORD} || 'zebra';
+ my $enable_pass = $opt_e || $ENV{ZEBRA_ENABLE} || '';
+
+ my $port = ($opt_z ? 'zebra' : 0) ||
+ ($opt_b ? 'bgpd' : 0) ||
+ ($opt_o ? 'ospfd' : 0) ||
+ ($opt_r ? 'ripd' : 0) || 'zebra';
+
+ my $cmd = join (' ', @ARGV);
+
+ my $t = new Net::Telnet (Timeout => 10,
+ Prompt => '/[\>\#] $/',
+ Port => $port);
+
+ $t->open ($server);
+
+ $t->cmd ($login_pass);
+ if ($enable_pass) {
+ $t->cmd (String => 'en',
+ Prompt => '/Password: /');
+ $t->cmd ($enable_pass);
+ }
+ $t->cmd ('conf t') if "$opt_c";
+
+ if ($cmd)
+ {
+ docmd ($t, $cmd);
+ exit (0);
+ }
+
+ my $prompt = sprintf ("%s%s# ", $host,
+ ($port eq 'zebra') ? '' : "/$port");
+
+ print "\nZEBRA interactive console ($port)\n\n" if -t STDIN;
+
+ while (1)
+ {
+ $| = 1;
+ print $prompt if -t STDIN;
+ chomp ($cmd = <>);
+ if (!defined ($cmd))
+ {
+ print "\n" if -t STDIN;
+ exit(0);
+ }
+ exit (0) if ($cmd eq 'q' || $cmd eq 'quit');
+
+ docmd ($t, $cmd) if $cmd !~ /^\s*$/;
+ }
+
+ exit(0);
+}
+
+sub docmd
+{
+ my ($t, $cmd) = @_;
+ my @lines = $t->cmd ($cmd);
+ print join ('', grep (!/[\>\#] $/, @lines)), "\n";
+}
+
+sub usage
+{
+ print "USAGE: $0 [-l LOGIN_PASSWORD] [-e ENABLE_PASSWORD] [-z|-b|-o|-r|-h] [<cmd>]\n",
+ "\t-l - specify login password\n",
+ "\t-e - specify enable password\n",
+ "\t-c - execute command in configure mode\n",
+ "\t-z - connect to zebra daemon\n",
+ "\t-b - connect to bgpd daemon\n",
+ "\t-o - connect to ospfd daemon\n",
+ "\t-r - connect to ripd daemon\n",
+ "\t-h - help\n";
+ exit (1);
+}
diff --git a/tools/zebra.el b/tools/zebra.el
new file mode 100644
index 0000000..01ff09f
--- /dev/null
+++ b/tools/zebra.el
@@ -0,0 +1,108 @@
+;; -*- lisp -*-
+;;; zebra-mode.el -- major mode for editing zebra configuration file.
+
+;; Copyright (C) 1998 Kunihiro Ishiguro
+
+;; Author: 1998 Kunihiro Ishiguro
+;; SeonMeyong HEO
+;; Maintainer: kunihiro@zebra.org
+;; seirios@Matrix.IRI.Co.JP
+;; Created: Jan 28 1998
+;; Version: Alpha 0.2
+;; Keywords: zebra bgpd ripd ripngd languages
+
+;; You can get the latest version of zebra from
+;;
+;; http://www.zebra.org/
+;;
+;; Install this Emacs Lisp code
+;;
+;; Compile zebra.el
+;; % $(EMACS) -batch -f batch-byte-compile zebra.el
+;; Install zebra.el,zebra.elc to Emacs-load-path
+;; % cp zebra.el zebra.elc $(emacs-load-path)
+;; Add .emacs or (site-load.el | site-start.el)
+;; (auto-load 'zebra-mode "zebra" nil t)
+;; (auto-load 'bgp-mode "zebra" nil t)
+;; (auto-load 'rip-mode "zebra" nil t)
+;;
+
+;;; Code:
+
+;; Set keywords
+
+(defvar zebra-font-lock-keywords
+ (list
+ '("#.*$" . font-lock-comment-face)
+ '("!.*$" . font-lock-comment-face)
+ '("no\\|interface" . font-lock-type-face)
+ '("ip6\\|ip\\|route\\|address" . font-lock-function-name-face)
+ '("ipforward\\|ipv6forward" . font-lock-keyword-face)
+ '("hostname\\|password\\|enable\\|logfile\\|no" . font-lock-keyword-face))
+ "Default value to highlight in zebra mode.")
+
+(defvar bgp-font-lock-keywords
+ (list
+ '("#.*$" . font-lock-comment-face)
+ '("!.*$" . font-lock-comment-face)
+ '("no\\|router" . font-lock-type-face)
+ '("bgp\\|router-id\\|neighbor\\|network" . font-lock-function-name-face)
+ '("ebgp\\|multihop\\|next\\|zebra\\|remote-as" . font-lock-keyword-face)
+ '("hostname\\|password\\|enable\\|logfile\\|no" . font-lock-keyword-face))
+ "Default value to highlight in bgp mode.")
+
+(defvar rip-font-lock-keywords
+ (list
+ '("#.*$" . font-lock-comment-face)
+ '("!.*$" . font-lock-comment-face)
+ '("no\\|router\\|interface\\|ipv6\\|ip6\\|ip" . font-lock-type-face)
+ '("ripng\\|rip\\|recive\\|advertize\\|accept" . font-lock-function-name-face)
+ '("version\\|network" . font-lock-function-name-face)
+ '("default\\|none\\|zebra" . font-lock-keyword-face)
+ '("hostname\\|password\\|enable\\|logfile\\|no" . font-lock-keyword-face))
+ "Default value to highlight in bgp mode.")
+
+;; set font-lock-mode
+
+(defun zebra-font-lock ()
+ (make-local-variable 'font-lock-defaults)
+ (setq font-lock-defaults '(zebra-font-lock-keywords nil t)))
+
+(defun bgp-font-lock ()
+ (make-local-variable 'font-lock-defaults)
+ (setq font-lock-defaults '(bgp-font-lock-keywords nil t)))
+
+(defun rip-font-lock ()
+ (make-local-variable 'font-lock-defaults)
+ (setq font-lock-defaults '(rip-font-lock-keywords nil t)))
+
+;; define Major mode
+
+(defun major-mode-define ()
+ (interactive)
+ (progn
+ (setq comment-start "[#!]"
+ comment-end ""
+ comment-start-skip "!+ ")
+ (run-hooks 'zebra-mode-hook)
+ (cond
+ ((string< "20" emacs-version)
+ (font-lock-mode)))))
+
+(defun zebra-mode ()
+ (progn
+ (setq mode-name "zebra")
+ (zebra-font-lock))
+ (major-mode-define))
+
+(defun bgp-mode ()
+ (progn
+ (setq mode-name "bgp")
+ (bgp-font-lock))
+ (major-mode-define))
+
+(defun rip-mode ()
+ (progn
+ (setq mode-name "rip")
+ (rip-font-lock))
+ (major-mode-define))