summaryrefslogtreecommitdiffstats
path: root/tools
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-28 09:53:30 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-28 09:53:30 +0000
commit2c7cac91ed6e7db0f6937923d2b57f97dbdbc337 (patch)
treec05dc0f8e6aa3accc84e3e5cffc933ed94941383 /tools
parentInitial commit. (diff)
downloadfrr-2c7cac91ed6e7db0f6937923d2b57f97dbdbc337.tar.xz
frr-2c7cac91ed6e7db0f6937923d2b57f97dbdbc337.zip
Adding upstream version 8.4.4.upstream/8.4.4upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'tools')
-rw-r--r--tools/.gitignore11
-rw-r--r--tools/Makefile10
-rwxr-xr-xtools/build-debian-package.sh75
-rwxr-xr-xtools/checkpatch.pl6530
-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/etc/frr/daemons120
-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.conf210
-rw-r--r--tools/etc/frr/vtysh.conf1
-rw-r--r--tools/etc/iproute2/rt_protos.d/frr.conf14
-rw-r--r--tools/etc/rsyslog.d/45-frr.conf48
-rwxr-xr-xtools/fixup-deprecated.py81
-rw-r--r--tools/frr-llvm-cg.c974
-rw-r--r--tools/frr-llvm-debuginfo.cpp112
-rw-r--r--tools/frr-llvm-debuginfo.h47
-rwxr-xr-xtools/frr-reload7
-rwxr-xr-xtools/frr-reload.py2218
-rwxr-xr-xtools/frr.in611
-rw-r--r--tools/frr.service.in26
-rw-r--r--tools/frr.vim36
-rw-r--r--tools/frr@.service.in26
-rwxr-xr-xtools/frr_babeltrace.py266
-rwxr-xr-xtools/frrcommon.sh.in390
-rw-r--r--tools/frrinit.sh.in135
-rw-r--r--tools/gcc-plugins/.gitignore7
-rw-r--r--tools/gcc-plugins/COPYING.GPLv3674
-rw-r--r--tools/gcc-plugins/Makefile19
-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.c4475
-rw-r--r--tools/gcc-plugins/frr-format.h366
-rw-r--r--tools/gcc-plugins/gcc-common.h990
-rw-r--r--tools/gcc-plugins/gcc-retain-typeinfo.patch11
-rw-r--r--tools/gen_northbound_callbacks.c403
-rw-r--r--tools/gen_yang_deviations.c80
-rwxr-xr-xtools/generate_support_bundle.py99
-rw-r--r--tools/git-reindent-branch.py104
-rwxr-xr-xtools/indent.py52
-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.sh216
-rw-r--r--tools/permutations.c112
-rwxr-xr-xtools/release_notes.py94
-rwxr-xr-xtools/releasedate.py61
-rw-r--r--tools/render_md.py30
-rw-r--r--tools/rrcheck.pl135
-rw-r--r--tools/rrlookup.pl123
-rw-r--r--tools/start-stop-daemon.c1061
-rw-r--r--tools/stringmangle.py61
-rw-r--r--tools/subdir.am69
-rw-r--r--tools/symalyzer.html347
-rwxr-xr-xtools/symalyzer.py406
-rw-r--r--tools/valgrind.supp88
-rwxr-xr-xtools/vty_index.sh21
-rw-r--r--tools/watchfrr.sh.in33
-rwxr-xr-xtools/zc.pl111
-rw-r--r--tools/zebra.el108
130 files changed, 26596 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..3c922c3
--- /dev/null
+++ b/tools/build-debian-package.sh
@@ -0,0 +1,75 @@
+#!/bin/sh
+#
+# 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..db6460f
--- /dev/null
+++ b/tools/checkpatch.pl
@@ -0,0 +1,6530 @@
+#!/usr/bin/env perl
+# (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>
+# Licensed under the terms of the GNU GPL License version 2
+
+use strict;
+use warnings;
+use POSIX;
+use File::Basename;
+use Cwd 'abs_path';
+use Term::ANSIColor qw(:constants);
+
+my $P = $0;
+my $D = dirname(abs_path($P));
+
+my $V = '0.32';
+
+use Getopt::Long qw(:config no_auto_abbrev);
+
+my $quiet = 0;
+my $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 %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 = 80;
+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 $typedefsfile = "";
+my $color = "auto";
+my $allow_c99_comments = 1;
+
+sub help {
+ my ($exitcode) = @_;
+
+ print << "EOM";
+Usage: $P [OPTION]... [FILE]...
+Version: $V
+
+Options:
+ -q, --quiet quiet
+ --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, if exceeded, warn
+ --min-conf-desc-length=n set the min description length, if shorter, warn
+ --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:/usr/share/codespell/dictionary.txt)
+ --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'.
+ -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
+ for ($text =~ /(?:(?:\bCHK|\bWARN|\bERROR|&\{\$msg_level})\s*\(|\$msg_type\s*=)\s*"([^"]+)"/g) {
+ push (@types, $_);
+ }
+ @types = sort(uniq(@types));
+ print("#\tMessage type\n\n");
+ foreach my $type (@types) {
+ print(++$count . "\t" . $type . "\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;
+}
+
+# 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,
+ '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,
+ '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' => \$codespellfile,
+ 'typedefsfile=s' => \$typedefsfile,
+ 'color=s' => \$color,
+ 'no-color' => \$color, #keep old behaviors of -nocolor
+ 'nocolor' => \$color, #keep old behaviors of -nocolor
+ 'h|help' => \$help,
+ 'version' => \$help
+) or help(1);
+
+help(0) if ($help);
+
+list_types(0) if ($list_types);
+
+$fix = 1 if ($fix_inplace);
+$check_orig = $check;
+
+my $exit = 0;
+
+if ($^V && $^V lt $minimum_perl_version) {
+ printf "$P: requires at least perl version %vd\n", $minimum_perl_version;
+ if (!$ignore_perl_version) {
+ exit(1);
+ }
+}
+
+#if no filenames are given, push '-' to read patch from stdin
+if ($#ARGV < 0) {
+ push(@ARGV, '-');
+}
+
+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 "Invalid color mode: $color\n";
+}
+
+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|
+ __init_refok|
+ __kprobes|
+ __ref|
+ __rcu|
+ __private
+ }x;
+our $InitAttributePrefix = qr{__(?:mem|cpu|dev|net_|)};
+our $InitAttributeData = qr{$InitAttributePrefix(?:initdata\b)};
+our $InitAttributeConst = qr{$InitAttributePrefix(?:initconst\b)};
+our $InitAttributeInit = qr{$InitAttributePrefix(?:init\b)};
+our $InitAttribute = qr{$InitAttributeData|$InitAttributeConst|$InitAttributeInit};
+
+# Notes to $Attribute:
+# We need \b after 'init' otherwise 'initconst' will cause a false positive in a check
+our $Attribute = qr{
+ const|
+ _Atomic|
+ __percpu|
+ __nocast|
+ __safe|
+ __bitwise|
+ __packed__|
+ __packed2__|
+ __naked|
+ __maybe_unused|
+ __always_unused|
+ __noreturn|
+ __used|
+ __cold|
+ __pure|
+ __noclone|
+ __deprecated|
+ __read_mostly|
+ __kprobes|
+ $InitAttribute|
+ ____cacheline_aligned|
+ ____cacheline_aligned_in_smp|
+ ____cacheline_internodealigned_in_smp|
+ __weak
+ }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{"[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 $signature_tags = qr{(?xi:
+ Signed-off-by:|
+ Acked-by:|
+ Tested-by:|
+ Reviewed-by:|
+ Reported-by:|
+ Suggested-by:|
+ To:|
+ Cc:
+)};
+
+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],
+);
+
+#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];
+}
+
+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 $allowed_asm_includes = qr{(?x:
+ irq|
+ memory|
+ time|
+ reboot
+)};
+# memory.h: ARM has a custom one
+
+# Load common spelling mistakes and build regular expression list.
+my $misspellings;
+my %spelling_fix;
+
+if (open(my $spelling, '<', $spelling_file)) {
+ while (<$spelling>) {
+ my $line = $_;
+
+ $line =~ s/\s*\n?$//g;
+ $line =~ s/^\s*//g;
+
+ next if ($line =~ m/^\s*#/);
+ next if ($line =~ m/^\s*$/);
+
+ my ($suspect, $fix) = split(/\|\|/, $line);
+
+ $spelling_fix{$suspect} = $fix;
+ }
+ close($spelling);
+} else {
+ warn "No typos will be found - file '$spelling_file': $!\n";
+}
+
+if ($codespell) {
+ if (open(my $spelling, '<', $codespellfile)) {
+ while (<$spelling>) {
+ my $line = $_;
+
+ $line =~ s/\s*\n?$//g;
+ $line =~ s/^\s*//g;
+
+ next if ($line =~ m/^\s*#/);
+ next if ($line =~ m/^\s*$/);
+ next if ($line =~ m/, disabled/i);
+
+ $line =~ s/,.*$//;
+
+ my ($suspect, $fix) = split(/->/, $line);
+
+ $spelling_fix{$suspect} = $fix;
+ }
+ close($spelling);
+ } else {
+ warn "No codespell typos will be found - file '$codespellfile': $!\n";
+ }
+}
+
+$misspellings = join("|", sort keys %spelling_fix) if keys %spelling_fix;
+
+sub read_words {
+ my ($wordsRef, $file) = @_;
+
+ if (open(my $words, '<', $file)) {
+ while (<$words>) {
+ my $line = $_;
+
+ $line =~ s/\s*\n?$//g;
+ $line =~ s/^\s*//g;
+
+ next if ($line =~ m/^\s*#/);
+ next if ($line =~ m/^\s*$/);
+ if ($line =~ /\s/) {
+ print("$file: '$line' invalid - ignored\n");
+ next;
+ }
+
+ $$wordsRef .= '|' if ($$wordsRef ne "");
+ $$wordsRef .= $line;
+ }
+ close($file);
+ return 1;
+ }
+
+ return 0;
+}
+
+my $typeOtherTypedefs = "";
+if (length($typedefsfile)) {
+ read_words(\$typeOtherTypedefs, $typedefsfile)
+ or warn "No additional types will be considered - file '$typedefsfile': $!\n";
+}
+$typeTypedefs .= '|' . $typeOtherTypedefs if ($typeOtherTypedefs ne "");
+
+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*\])+)?
+ (?:\s+$Inline|\s+$Modifier)*
+ }x;
+ $TypeMisordered = qr{
+ $NonptrTypeMisordered
+ (?:(?:\s|\*|\[\])+\s*const|(?:\s|\*\s*(?:const\s*)?|\[\])+|(?:\s*\[\s*\])+)?
+ (?:\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*\(|
+ (?:$Storage\s+)?${Type}\s+uninitialized_var\s*\(
+)};
+
+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;
+ }
+ }
+}
+
+sub is_maintained_obsolete {
+ my ($filename) = @_;
+
+ return 0 if (!$tree || !(-e "$root/scripts/get_maintainer.pl"));
+
+ my $status = `perl $root/scripts/get_maintainer.pl --status --nom --nol --nogit --nogit-fallback -f $filename 2>&1`;
+
+ return $status =~ /obsolete/i;
+}
+
+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 ".git") {
+ my $git_last_include_commit = `git 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 ".git") {
+ $files = `git 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_commit_info {
+ my ($commit, $id, $desc) = @_;
+
+ return ($id, $desc) if ((which("git") eq "") || !(-e ".git"));
+
+ my $output = `git 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\./) {
+ $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 ".git");
+
+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 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;
+for my $filename (@ARGV) {
+ my $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, $_);
+ }
+ 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();
+}
+
+if (!$quiet) {
+ hash_show_words(\%use_type, "Used");
+ hash_show_words(\%ignore_type, "Ignored");
+
+ if ($^V lt 5.10.0) {
+ print << "EOM"
+
+NOTE: perl $^V is not modern enough to detect all possible issues.
+ An upgrade to at least perl v5.10.0 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 $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/$address.*$//;
+ $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 = "";
+ }
+ }
+
+ $name = trim($name);
+ $name =~ s/^\"|\"$//g;
+ $address = trim($address);
+ $address =~ s/^\<|\>$//g;
+
+ if ($name =~ /[^\w \-]/i) { ##has "must quote" chars
+ $name =~ s/(?<!\\)"/\\"/g; ##escape quotes
+ $name = "\"$name\"";
+ }
+
+ return ($name, $address, $comment);
+}
+
+sub format_email {
+ my ($name, $address) = @_;
+
+ my $formatted_email;
+
+ $name = trim($name);
+ $name =~ s/^\"|\"$//g;
+ $address = trim($address);
+
+ if ($name =~ /[^\w \-]/i) { ##has "must quote" chars
+ $name =~ s/(?<!\\)"/\\"/g; ##escape quotes
+ $name = "\"$name\"";
+ }
+
+ if ("$name" eq "") {
+ $formatted_email = "$address";
+ } else {
+ $formatted_email = "$name <$address>";
+ }
+
+ return $formatted_email;
+}
+
+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 % 8) != 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 wacking completly 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@;
+ }
+
+ return $res;
+}
+
+sub get_quoted_string {
+ my ($line, $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) = @_;
+
+ # Catch a comment on the end of the line itself.
+ my ($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 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);
+ }
+ $output = (split('\n', $output))[0] . "\n" if ($terse);
+
+ 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 = 8;
+ 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 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 $is_patch = 0;
+ my $in_header_lines = $file ? 0 : 1;
+ my $in_commit_log = 0; #Scanning lines before patch
+ my $has_commit_log = 0; #Encountered lines before patch
+ 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_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;
+
+ 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.rst$@) {
+ $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];
+
+#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;
+ }
+ 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);
+
+# 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 signoff:
+ if ($line =~ /^\s*signed-off-by:/i) {
+ $signoff++;
+ $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/) {
+ WARN("BAD_SIGN_OFF",
+ "Non-standard signature: $sign_off\n" . $herecurr);
+ }
+ 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, $email_address, $comment) = parse_email($email);
+ my $suggested_email = format_email(($email_name, $email_address));
+ 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 ("$dequoted$comment" ne $email &&
+ "<$email_address>$comment" ne $email &&
+ "$suggested_email$comment" ne $email) {
+ WARN("BAD_SIGN_OFF",
+ "email address '$email' might be better as '$suggested_email$comment'\n" . $herecurr);
+ }
+ }
+
+# 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 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 old stable address
+ if ($line =~ /^\s*cc:\s*.*<?\bstable\@kernel\.org\b>?.*$/i) {
+ ERROR("STABLE_ADDRESS",
+ "The 'stable' address should be 'stable\@vger.kernel.org'\n" . $herecurr);
+ }
+
+# Check for unwanted Gerrit info
+ if ($in_commit_log && $line =~ /^\s*change-id:/i) {
+ ERROR("GERRIT_CHANGE_ID",
+ "Remove Gerrit Change-Id's before submitting upstream.\n" . $herecurr);
+ }
+
+# 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,}\>\]/)) {
+ # stack dump address
+ $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:)/i ||
+ # A Fixes: or Link: 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 git id commit length and improperly formed commit descriptions
+ if ($in_commit_log && !$commit_log_possible_stack_dump &&
+ $line !~ /^\s*(?:Link|Patchwork|http|https|BugLink):/i &&
+ $line !~ /^This reverts commit [0-9a-f]{7,40}/ &&
+ ($line =~ /\bcommit\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 $hasdesc = 0;
+ my $hasparens = 0;
+ my $id = '0123456789ab';
+ my $orig_desc = "commit description";
+ my $description = "";
+
+ if ($line =~ /\b(c)ommit\s+([0-9a-f]{5,})\b/i) {
+ $init_char = $1;
+ $orig_commit = lc($2);
+ } elsif ($line =~ /\b([0-9a-f]{12,40})\b/i) {
+ $orig_commit = lc($1);
+ }
+
+ $short = 0 if ($line =~ /\bcommit\s+[0-9a-f]{12,40}/i);
+ $long = 1 if ($line =~ /\bcommit\s+[0-9a-f]{41,}/i);
+ $space = 0 if ($line =~ /\bcommit [0-9a-f]/i);
+ $case = 0 if ($line =~ /\b[Cc]ommit\s+[0-9a-f]{5,40}[^A-F]/);
+ if ($line =~ /\bcommit\s+[0-9a-f]{5,}\s+\("([^"]+)"\)/i) {
+ $orig_desc = $1;
+ $hasparens = 1;
+ } elsif ($line =~ /\bcommit\s+[0-9a-f]{5,}\s*$/i &&
+ defined $rawlines[$linenr] &&
+ $rawlines[$linenr] =~ /^\s*\("([^"]+)"\)/) {
+ $orig_desc = $1;
+ $hasparens = 1;
+ } elsif ($line =~ /\bcommit\s+[0-9a-f]{5,}\s+\("[^"]+$/i &&
+ defined $rawlines[$linenr] &&
+ $rawlines[$linenr] =~ /^\s*[^"]+"\)/) {
+ $line =~ /\bcommit\s+[0-9a-f]{5,}\s+\("([^"]+)$/i;
+ $orig_desc = $1;
+ $rawlines[$linenr] =~ /^\s*([^"]+)"\)/;
+ $orig_desc .= " " . $1;
+ $hasparens = 1;
+ }
+
+ ($id, $description) = git_commit_info($orig_commit,
+ $id, $orig_desc);
+
+ if (defined($id) &&
+ ($short || $long || $space || $case || ($orig_desc ne $description) || !$hasparens)) {
+ ERROR("GIT_COMMIT_ID",
+ "Please use git commit description style 'commit <12+ chars of sha1> (\"<title line>\")' - ie: '${init_char}ommit $id (\"$description\")'\n" . $herecurr);
+ }
+ }
+
+# Check for added, moved or deleted files
+ if (!$reported_maintainer_file && !$in_commit_log &&
+ ($line =~ /^(?:new|deleted) file mode\s*\d+\s*$/ ||
+ $line =~ /^rename (?:from|to) [\w\/\.\-]+\s*$/ ||
+ ($line =~ /\{\s*([\w\/\.\-]*)\s*\=\>\s*([\w\/\.\-]*)\s*\}/ &&
+ (defined($1) || defined($2))))) {
+ $is_patch = 1;
+ $reported_maintainer_file = 1;
+ #WARN("FILE_PATH_CHANGES",
+ # "added, moved or deleted file(s), does MAINTAINERS need updating?\n" . $herecurr);
+ }
+
+# Check for 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 =~ /(?:^|[^a-z@])($misspellings)(?:\b|$|[^a-z@])/gi) {
+ my $typo = $1;
+ 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" . $herecurr) &&
+ $fix) {
+ $fixed[$fixlinenr] =~ s/(^|[^A-Za-z@])($typo)($|[^A-Za-z@])/$1$typo_fix$3/;
+ }
+ }
+ }
+
+# 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 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/ &&
+ $line =~ /^\+\s*config\s+/) {
+ 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)\s*\"/) {
+ $is_start = 1;
+ } elsif ($lines[$ln - 1] =~ /^\+\s*(?:---)?help(?:---)?$/) {
+ $length = -1;
+ }
+
+ $f =~ s/^.//;
+ $f =~ s/#.*//;
+ $f =~ s/^\s+//;
+ next if ($f =~ /^$/);
+ if ($f =~ /^\s*config\s/) {
+ $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 for MAINTAINERS entries that don't have the right form
+ if ($realfile =~ /^MAINTAINERS$/ &&
+ $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/;
+ }
+ }
+
+# discourage the use of boolean for type definition attributes of Kconfig options
+ if ($realfile =~ /Kconfig/ &&
+ $line =~ /^\+\s*\bboolean\b/) {
+ WARN("CONFIG_TYPE_BOOLEAN",
+ "Use of boolean is deprecated, please use bool instead.\n" . $herecurr);
+ }
+
+ 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.txt";
+
+ 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 "^$vendor\\b" $vp_file`;
+ if ( $? >> 8 ) {
+ WARN("UNDOCUMENTED_DT_STRING",
+ "DT compatible string vendor \"$vendor\" appears un-documented -- check $vp_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)$/);
+
+# 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
+#
+# 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 = "";
+
+ # 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))) {
+ WARN($msg_type,
+ "line over $max_line_length characters\n" . $herecurr);
+ }
+ }
+
+# check for adding lines without a newline.
+ if ($line =~ /^\+/ && defined $lines[$linenr] && $lines[$linenr] =~ /^\\ No newline at end of file/) {
+ WARN("MISSING_EOF_NEWLINE",
+ "adding a line without newline at end of file\n" . $herecurr);
+ }
+
+# Blackfin: use hi/lo macros
+ if ($realfile =~ m@arch/blackfin/.*\.S$@) {
+ if ($line =~ /\.[lL][[:space:]]*=.*&[[:space:]]*0x[fF][fF][fF][fF]/) {
+ my $herevet = "$here\n" . cat_vet($line) . "\n";
+ ERROR("LO_MACRO",
+ "use the LO() macro, not (... & 0xFFFF)\n" . $herevet);
+ }
+ if ($line =~ /\.[hH][[:space:]]*=.*>>[[:space:]]*16/) {
+ my $herevet = "$here\n" . cat_vet($line) . "\n";
+ ERROR("HI_MACRO",
+ "use the HI() macro, not (... >> 16)\n" . $herevet);
+ }
+ }
+
+# 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 8 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/(^\+.*) {8,8}\t/$1\t\t/) {}
+ while ($fixed[$fixlinenr] =~
+ s/(^\+.*) +\t/$1\t/) {}
+ }
+ }
+
+# check for && or || at the start of a line
+ if ($rawline =~ /^\+\s*(&&|\|\|)/) {
+ CHK("LOGICAL_CONTINUATIONS",
+ "Logical continuations should be on the previous line\n" . $hereprev);
+ }
+
+# check indentation starts on a tab stop
+ if ($^V && $^V ge 5.10.0 &&
+ $sline =~ /^\+\t+( +)(?:$c90_Keywords\b|\{\s*$|\}\s*(?:else\b|while\b|\s*$))/) {
+ my $indent = length($1);
+ if ($indent % 8) {
+ if (WARN("TABSTOP",
+ "Statements should start on a tabstop\n" . $herecurr) &&
+ $fix) {
+ $fixed[$fixlinenr] =~ s@(^\+\t+) +@$1 . "\t" x ($indent/8)@e;
+ }
+ }
+ }
+
+# check multi-line statement indentation matches previous line
+ if ($^V && $^V ge 5.10.0 &&
+ $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 / 8) .
+ " " x ($pos % 8);
+ 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 > 2) {
+ 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
+ if ($sline =~ /^\+\s+\S/ && #Not at char 1
+ # actual declarations
+ ($prevline =~ /^\+\s+$Declare\s*$Ident\s*[=,;:\[]/ ||
+ # function pointer declarations
+ $prevline =~ /^\+\s+$Declare\s*\(\s*\*\s*$Ident\s*\)\s*[=,;:\[\(]/ ||
+ # foo bar; where foo is some local typedef or #define
+ $prevline =~ /^\+\s+$Ident(?:\s+|\s*\*\s*)$Ident\s*[=,;\[]/ ||
+ # known declaration macros
+ $prevline =~ /^\+\s+$declaration_macros/) &&
+ # for "else if" which can look like "$Ident $Ident"
+ !($prevline =~ /^\+\s+$c90_Keywords\b/ ||
+ # other possible extensions of declaration lines
+ $prevline =~ /(?:$Compare|$Assignment|$Operators)\s*$/ ||
+ # not starting a section or a macro "\" extended line
+ $prevline =~ /(?:\{\s*|\\)$/) &&
+ # looks like a declaration
+ !($sline =~ /^\+\s+$Declare\s*$Ident\s*[=,;:\[]/ ||
+ # function pointer declarations
+ $sline =~ /^\+\s+$Declare\s*\(\s*\*\s*$Ident\s*\)\s*[=,;:\[\(]/ ||
+ # foo bar; where foo is some local typedef or #define
+ $sline =~ /^\+\s+$Ident(?:\s+|\s*\*\s*)$Ident\s*[=,;\[]/ ||
+ # known declaration macros
+ $sline =~ /^\+\s+$declaration_macros/ ||
+ # start of struct or union or enum
+ $sline =~ /^\+\s+(?:union|struct|enum|typedef)\b/ ||
+ # start or end of block or continuation of declaration
+ $sline =~ /^\+\s+(?:$|[\{\}\.\#\"\?\:\(\[])/ ||
+ # bitfield continuation
+ $sline =~ /^\+\s+$Ident\s*:\s*\d+\s*[,;]/ ||
+ # other possible extensions of declaration lines
+ $sline =~ /^\+\s+\(?\s*(?:$Compare|$Assignment|$Operators)/) &&
+ # indentation of previous and current line are the same
+ (($prevline =~ /\+(\s+)\S/) && $sline =~ /^\+$1\S/)) {
+ 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 or return and is indented the same # of tabs
+ if ($sline =~ /^\+([\t]+)break\s*;\s*$/) {
+ my $tabs = $1;
+ if ($prevline =~ /^\+$tabs(?:goto|return)\b/) {
+ WARN("UNNECESSARY_BREAK",
+ "break is not useful after a goto or return\n" . $hereprev);
+ }
+ }
+
+# 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);
+ }
+
+# Blackfin: don't use __builtin_bfin_[cs]sync
+ if ($line =~ /__builtin_bfin_csync/) {
+ my $herevet = "$here\n" . cat_vet($line) . "\n";
+ ERROR("CSYNC",
+ "use the CSYNC() macro in asm/blackfin.h\n" . $herevet);
+ }
+ if ($line =~ /__builtin_bfin_ssync/) {
+ my $herevet = "$here\n" . cat_vet($line) . "\n";
+ ERROR("SSYNC",
+ "use the SSYNC() macro in asm/blackfin.h\n" . $herevet);
+ }
+
+# 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 % 8) != 0 ||
+ ($sindent < $indent) ||
+ ($sindent == $indent &&
+ ($s !~ /^\s*(?:\}|\{|else\b)/)) ||
+ ($sindent > $indent + 8))) {
+ 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 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) {
+ if(ERROR("C99_COMMENTS",
+ "do not use C99 // comments\n" . $herecurr) &&
+ $fix) {
+ my $line = $fixed[$fixlinenr];
+ if ($line =~ /\/\/(.*)$/) {
+ my $comment = trim($1);
+ $fixed[$fixlinenr] =~ s@\/\/(.*)$@/\* $comment \*/@;
+ }
+ }
+ } 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.*\((.*)\)/ ||
+ $lines[$realline_next - 1] =~ /EXPORT_UNUSED_SYMBOL.*\((.*)\)/)) {
+ # Handle definitions which produce identifiers with
+ # a prefix:
+ # XXX(foo);
+ # EXPORT_SYMBOL(something_foo);
+ my $name = $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.*\((.*)\)/ ||
+ $line =~ /EXPORT_UNUSED_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*;/) {
+ 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 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 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 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);
+ }
+
+ if ($line =~ /\bprintk\s*\(\s*KERN_([A-Z]+)/) {
+ my $orig = $1;
+ my $level = lc($orig);
+ $level = "warn" if ($level eq "warning");
+ my $level2 = $level;
+ $level2 = "dbg" if ($level eq "debug");
+ WARN("PREFER_PR_LEVEL",
+ "Prefer [subsystem eg: netdev]_$level2([subsystem]dev, ... then dev_$level2(dev, ... then pr_$level(... to printk(KERN_$orig ...\n" . $herecurr);
+ }
+
+ if ($line =~ /\bpr_warning\s*\(/) {
+ if (WARN("PREFER_PR_LEVEL",
+ "Prefer pr_warn(... to pr_warning(...\n" . $herecurr) &&
+ $fix) {
+ $fixed[$fixlinenr] =~
+ s/\bpr_warning\b/pr_warn/;
+ }
+ }
+
+ 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);
+ }
+
+# 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);
+ }
+
+# function brace can't be on same line, except for #defines of do while,
+# or if closed on same line
+ if (($line=~/$Type\s*$Ident\(.*\).*\s*{/) and
+ !($line=~/\#\s*define.*do\s\{/) and !($line=~/}/)) {
+ if (ERROR("OPEN_BRACE",
+ "open brace '{' following function declarations 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/) {
+ # 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./) {
+ 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
+## # falsly 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 =~ /do\{/) {
+ if (ERROR("SPACING",
+ "space required before the open brace '{'\n" . $herecurr) &&
+ $fix) {
+ $fixed[$fixlinenr] =~ s/^(\+.*(?:do|\)))\{/$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
+ if ($^V && $^V ge 5.10.0 && 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);
+ }
+ }
+
+# return is not a function
+ if (defined($stat) && $stat =~ /^.\s*return(\s*)\(/s) {
+ my $spacing = $1;
+ if ($^V && $^V ge 5.10.0 &&
+ $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 ($^V && $^V ge 5.10.0 &&
+ $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 ($^V && $^V ge 5.10.0 &&
+ $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') {
+ 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) {
+ ERROR("ASSIGN_IN_IF",
+ "do not use assignment in if condition\n" . $herecurr);
+ }
+
+ # 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;
+
+#gcc binary extension
+ if ($var =~ /^$Binary$/) {
+ if (WARN("GCC_BINARY_CONSTANT",
+ "Avoid gcc v4.3+ binary constant extension: <$var>\n" . $herecurr) &&
+ $fix) {
+ my $hexval = sprintf("0x%x", oct($var));
+ $fixed[$fixlinenr] =~
+ s/\b$var\b/$hexval/;
+ }
+ }
+
+#CamelCase
+ if ($var !~ /^$Constant$/ &&
+ $var =~ /[A-Z][a-z]|[a-z][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)
+ $var !~ /^(?:[a-z_]*?)_?[a-z][A-Z](?:_[a-z_]+)?$/ &&
+#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;
+ @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/\([^\(\)]*\)/1/ ||
+ $dstat =~ s/\{[^\{\}]*\}/1/ ||
+ $dstat =~ s/.\[[^\[\]]*\]/1/)
+ {
+ }
+
+ # Flatten any obvious string concatentation.
+ 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 $herectx = $here . "\n";
+ my $stmt_cnt = statement_rawlines($ctx);
+
+ for (my $n = 0; $n < $stmt_cnt; $n++) {
+ $herectx .= raw_line($linenr, $n) . "\n";
+ }
+
+ 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 !~ /^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(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 =~ s/\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 $herectx = $here . "\n";
+ my $cnt = statement_rawlines($ctx);
+
+ for (my $n = 0; $n < $cnt; $n++) {
+ $herectx .= raw_line($linenr, $n) . "\n";
+ }
+ 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 ($^V && $^V ge 5.10.0 &&
+ $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 = $here . "\n";
+
+ for (my $n = 0; $n < $cnt; $n++) {
+ $herectx .= raw_line($linenr, $n) . "\n";
+ }
+
+ 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 = $here . "\n";
+
+ for (my $n = 0; $n < $cnt; $n++) {
+ $herectx .= raw_line($linenr, $n) . "\n";
+ }
+
+ WARN("TRAILING_SEMICOLON",
+ "macros should not use a trailing semicolon\n" . "$herectx");
+ }
+ }
+
+# make sure symbols are always wrapped with VMLINUX_SYMBOL() ...
+# all assignments may have only one of the following with an assignment:
+# .
+# ALIGN(...)
+# VMLINUX_SYMBOL(...)
+ if ($realfile eq 'vmlinux.lds.h' && $line =~ /(?:(?:^|\s)$Ident\s*=|=\s*$Ident(?:\s|$))/) {
+ WARN("MISSING_VMLINUX_SYMBOL",
+ "vmlinux.lds.h needs VMLINUX_SYMBOL() around C-visible symbols\n" . $herecurr);
+ }
+
+# 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 $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 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 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/) {
+ CHK("CONCATENATED_STRING",
+ "Concatenated strings should use spaces between elements\n" . $herecurr);
+ }
+
+# uncoalesced string fragments
+ if ($line =~ /$String\s*"/) {
+ CHK("STRING_FRAGMENTS",
+ "Consecutive strings are generally better as a single string\n" . $herecurr);
+ }
+
+# 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 =~ /\\$/ && $rawline =~ 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/) {
+ CHK("REDUNDANT_CODE",
+ "if this code is redundant consider removing it\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*(?:devm_)?(?:[kv][czm]alloc(?:_node|_array)?\b|kstrdup|kmemdup|(?:dev_)?alloc_skb)/) {
+ 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 mask then right shift without a parentheses
+ if ($^V && $^V ge 5.10.0 &&
+ $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 ($^V && $^V ge 5.10.0) {
+ 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.txt\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.txt\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|
+ read_barrier_depends
+ }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 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/;
+
+ }
+ }
+
+#
+# Kernel macros unnused for FRR
+#
+# Check for __attribute__ packed, prefer __packed
+# if ($realfile !~ m@\binclude/uapi/@ &&
+# $line =~ /\b__attribute__\s*\(\s*\(.*\bpacked\b/) {
+# WARN("PREFER_PACKED",
+# "__packed is preferred over __attribute__((packed))\n" . $herecurr);
+# }
+
+# Check for __attribute__ aligned, prefer __aligned
+# if ($realfile !~ m@\binclude/uapi/@ &&
+# $line =~ /\b__attribute__\s*\(\s*\(.*aligned/) {
+# WARN("PREFER_ALIGNED",
+# "__aligned(size) is preferred over __attribute__((aligned(size)))\n" . $herecurr);
+# }
+
+# Check for __attribute__ format(printf, prefer __printf
+# if ($realfile !~ m@\binclude/uapi/@ &&
+# $line =~ /\b__attribute__\s*\(\s*\(\s*format\s*\(\s*printf/) {
+# if (WARN("PREFER_PRINTF",
+# "__printf(string-index, first-to-check) is preferred over __attribute__((format(printf, string-index, first-to-check)))\n" . $herecurr) &&
+# $fix) {
+# $fixed[$fixlinenr] =~ s/\b__attribute__\s*\(\s*\(\s*format\s*\(\s*printf\s*,\s*(.*)\)\s*\)\s*\)/"__printf(" . trim($1) . ")"/ex;
+
+# }
+# }
+
+# Check for __attribute__ format(scanf, prefer __scanf
+# if ($realfile !~ m@\binclude/uapi/@ &&
+# $line =~ /\b__attribute__\s*\(\s*\(\s*format\s*\(\s*scanf\b/) {
+# if (WARN("PREFER_SCANF",
+# "__scanf(string-index, first-to-check) is preferred over __attribute__((format(scanf, string-index, first-to-check)))\n" . $herecurr) &&
+# $fix) {
+# $fixed[$fixlinenr] =~ s/\b__attribute__\s*\(\s*\(\s*format\s*\(\s*scanf\s*,\s*(.*)\)\s*\)\s*\)/"__scanf(" . trim($1) . ")"/ex;
+# }
+# }
+
+# Check for __attribute__ weak, or __weak declarations (may have link issues)
+# if ($^V && $^V ge 5.10.0 &&
+# $line =~ /(?:$Declare|$DeclareMisordered)\s*$Ident\s*$balanced_parens\s*(?:$Attribute)?\s*;/ &&
+# ($line =~ /\b__attribute__\s*\(\s*\(.*\bweak\b/ ||
+# $line =~ /\b__weak\b/)) {
+# ERROR("WEAK_DECLARATION",
+# "Using weak declarations can have unintended link defects\n" . $herecurr);
+# }
+
+# check for c99 types like uint8_t used outside of uapi/ and tools/
+ if ($realfile !~ m@\binclude/uapi/@ &&
+ $realfile !~ m@\btools/@ &&
+ $line =~ /\b($Declare)\s*$Ident\s*[=;,\[]/) {
+ my $type = $1;
+ if ($type =~ /\b($typeC99Typedefs)\b/) {
+ $type = $1;
+ my $kernel_type = 'u';
+ $kernel_type = 's' if ($type =~ /^_*[si]/);
+ $type =~ /(\d+)/;
+ $kernel_type .= $1;
+ if (CHK("PREFER_KERNEL_TYPES",
+ "Prefer kernel type '$kernel_type' over '$type'\n" . $herecurr) &&
+ $fix) {
+ $fixed[$fixlinenr] =~ s/\b$type\b/$kernel_type/;
+ }
+ }
+ }
+
+# check for cast of C90 native int or longer types constants
+ if ($line =~ /(\(\s*$C90_int_types\s*\)\s*)($Constant)\b/) {
+ my $cast = $1;
+ my $const = $2;
+ if (WARN("TYPECAST_INT_CONSTANT",
+ "Unnecessary typecast of c90 int constant\n" . $herecurr) &&
+ $fix) {
+ 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';
+ }
+ $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 (0 && $^V && $^V ge 5.10.0 &&
+ defined $stat &&
+ $stat =~ /^\+(?![^\{]*\{\s*).*\b(\w+)\s*\(.*$String\s*,/s &&
+ $1 !~ /^_*volatile_*$/) {
+ my $bad_extension = "";
+ my $lc = $stat =~ tr@\n@@;
+ $lc = $lc + $linenr;
+ for (my $count = $linenr; $count <= $lc; $count++) {
+ my $fmt = get_quoted_string($lines[$count - 1], raw_line($count, 0));
+ $fmt =~ s/%%//g;
+ if ($fmt =~ /(\%[\*\d\.]*p(?![\WFfSsBKRraEhMmIiUDdgVCbGNOx]).)/) {
+ $bad_extension = $1;
+ last;
+ }
+ }
+ if ($bad_extension ne "") {
+ my $stat_real = raw_line($linenr, 0);
+ for (my $count = $linenr + 1; $count <= $lc; $count++) {
+ $stat_real = $stat_real . "\n" . raw_line($count, 0);
+ }
+ WARN("VSPRINTF_POINTER_EXTENSION",
+ "Invalid vsprintf pointer extension '$bad_extension'\n" . "$here\n$stat_real\n");
+ }
+ }
+
+# Check for misused memsets
+ if ($^V && $^V ge 5.10.0 &&
+ 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 ($^V && $^V ge 5.10.0 &&
+# 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 ($^V && $^V ge 5.10.0 &&
+# 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 ($^V && $^V ge 5.10.0 &&
+# 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)/;
+# }
+# }
+# }
+
+# typecasts on min/max could be min_t/max_t
+ if ($^V && $^V ge 5.10.0 &&
+ 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 ($^V && $^V ge 5.10.0 &&
+ 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.txt\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.txt\n" . "$here\n$stat\n");
+ }
+ }
+
+# check for naked sscanf
+ if ($^V && $^V ge 5.10.0 &&
+ 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 = raw_line($linenr, 0);
+ for (my $count = $linenr + 1; $count <= $lc; $count++) {
+ $stat_real = $stat_real . "\n" . raw_line($count, 0);
+ }
+ WARN("NAKED_SSCANF",
+ "unchecked sscanf return value\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*;/ &&
+ $function_name ne 'uninitialized_var')
+ {
+ 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 ($^V && $^V ge 5.10.0 &&
+ 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.rst\n" . $herecurr);
+ }
+ }
+
+# check for pointless casting of kmalloc return
+ if ($line =~ /\*\s*\)\s*[kv][czm]alloc(_node){0,1}\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 ($^V && $^V ge 5.10.0 &&
+ $line =~ /\b($Lval)\s*\=\s*(?:$balanced_parens)?\s*([kv][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 ($^V && $^V ge 5.10.0 &&
+ 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 $ctx = '';
+ my $herectx = $here . "\n";
+ my $cnt = statement_rawlines($stat);
+ for (my $n = 0; $n < $cnt; $n++) {
+ $herectx .= raw_line($linenr, $n) . "\n";
+ }
+ 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 ($^V && $^V ge 5.10.0 &&
+ $line =~ /\b($Lval)\s*\=\s*(?:$balanced_parens)?\s*krealloc\s*\(\s*\1\s*,/) {
+ WARN("KREALLOC_ARG_REUSE",
+ "Reusing the krealloc arg is almost always a bug\n" . $herecurr);
+ }
+
+# check for alloc argument mismatch
+ if ($line =~ /\b(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 #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 case / default statements not preceded by break/fallthrough/switch
+ if ($line =~ /^.\s*(?:case\s+(?:$Ident|$Constant)\s*|default):/) {
+ my $has_break = 0;
+ my $has_statement = 0;
+ my $count = 0;
+ my $prevline = $linenr;
+ while ($prevline > 1 && ($file || $count < 3) && !$has_break) {
+ $prevline--;
+ my $rline = $rawlines[$prevline - 1];
+ my $fline = $lines[$prevline - 1];
+ last if ($fline =~ /^\@\@/);
+ next if ($fline =~ /^\-/);
+ next if ($fline =~ /^.(?:\s*(?:case\s+(?:$Ident|$Constant)[\s$;]*|default):[\s$;]*)*$/);
+ $has_break = 1 if ($rline =~ /fall[\s_-]*(through|thru)/i);
+ next if ($fline =~ /^.[\s$;]*$/);
+ $has_statement = 1;
+ $count++;
+ $has_break = 1 if ($fline =~ /\bswitch\b|\b(?:break\s*;[\s$;]*$|exit\s*\(\b|return\b|goto\b|continue\b)/);
+ }
+ if (!$has_break && $has_statement) {
+ WARN("MISSING_BREAK",
+ "Possible switch case/default not preceded by break or fallthrough comment\n" . $herecurr);
+ }
+ }
+
+# check for switch/default statements without a break;
+ if ($^V && $^V ge 5.10.0 &&
+ defined $stat &&
+ $stat =~ /^\+[$;\s]*(?:case[$;\s]+\w+[$;\s]*:[$;\s]*|)*[$;\s]*\bdefault[$;\s]*:[$;\s]*;/g) {
+ my $ctx = '';
+ my $herectx = $here . "\n";
+ my $cnt = statement_rawlines($stat);
+ for (my $n = 0; $n < $cnt; $n++) {
+ $herectx .= raw_line($linenr, $n) . "\n";
+ }
+ 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);
+ }
+
+# 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);
+ }
+
+# use of NR_CPUS is usually wrong
+# ignore definitions of NR_CPUS and usage to define arrays as likely right
+ 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[^\]]*\.\.\.[^\]]*\]/)
+ {
+ 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 ($^V && $^V ge 5.10.0 &&
+ $line =~ /\b((?:un)?likely)\s*\(\s*$FuncArg\s*\)\s*$Compare/) {
+ WARN("LIKELY_MISUSE",
+ "Using $1 should generally have parentheses around the comparison\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);
+ }
+ }
+
+# whine about ACCESS_ONCE
+ if ($^V && $^V ge 5.10.0 &&
+ $line =~ /\bACCESS_ONCE\s*$balanced_parens\s*(=(?!=))?\s*($FuncArg)?/) {
+ my $par = $1;
+ my $eq = $2;
+ my $fun = $3;
+ $par =~ s/^\(\s*(.*)\s*\)$/$1/;
+ if (defined($eq)) {
+ if (WARN("PREFER_WRITE_ONCE",
+ "Prefer WRITE_ONCE(<FOO>, <BAR>) over ACCESS_ONCE(<FOO>) = <BAR>\n" . $herecurr) &&
+ $fix) {
+ $fixed[$fixlinenr] =~ s/\bACCESS_ONCE\s*\(\s*\Q$par\E\s*\)\s*$eq\s*\Q$fun\E/WRITE_ONCE($par, $fun)/;
+ }
+ } else {
+ if (WARN("PREFER_READ_ONCE",
+ "Prefer READ_ONCE(<FOO>) over ACCESS_ONCE(<FOO>)\n" . $herecurr) &&
+ $fix) {
+ $fixed[$fixlinenr] =~ s/\bACCESS_ONCE\s*\(\s*\Q$par\E\s*\)/READ_ONCE($par)/;
+ }
+ }
+ }
+
+# check for mutex_trylock_recursive usage
+ if ($line =~ /mutex_trylock_recursive/) {
+ ERROR("LOCKING",
+ "recursive locking is bad, do not use this ever.\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);
+ }
+
+# Mode permission misuses where it seems decimal should be octal
+# This uses a shortcut match to avoid unnecessary uses of a slow foreach loop
+ if ($^V && $^V ge 5.10.0 &&
+ 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 = raw_line($linenr, 0);
+ for (my $count = $linenr + 1; $count <= $lc; $count++) {
+ $stat_real = $stat_real . "\n" . raw_line($count, 0);
+ }
+
+ 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 (($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
+ if ($line =~ /\b$mode_perms_string_search\b/) {
+ my $val = "";
+ my $oval = "";
+ my $to = 0;
+ my $curpos = 0;
+ my $lastpos = 0;
+ while ($line =~ /\b(($mode_perms_string_search)\b(?:\s*\|\s*)?\s*)/g) {
+ $curpos = pos($line);
+ 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*$//;
+ my $octal = sprintf("%04o", $to);
+ if (WARN("SYMBOLIC_PERMS",
+ "Symbolic permissions '$oval' are not preferred. Consider using octal permissions '$octal'.\n" . $herecurr) &&
+ $fix) {
+ $fixed[$fixlinenr] =~ s/$val/$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 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 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 && $signoff == 0) {
+ ERROR("MISSING_SIGN_OFF",
+ "Missing Signed-off-by: line(s)\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/etc/frr/daemons b/tools/etc/frr/daemons
new file mode 100644
index 0000000..8aa0887
--- /dev/null
+++ b/tools/etc/frr/daemons
@@ -0,0 +1,120 @@
+# 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"
+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
+
+
+# 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..ff2c633
--- /dev/null
+++ b/tools/etc/frr/support_bundle_commands.conf
@@ -0,0 +1,210 @@
+# 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 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
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/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..57f9df9
--- /dev/null
+++ b/tools/fixup-deprecated.py
@@ -0,0 +1,81 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+#
+# 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..8ce754f
--- /dev/null
+++ b/tools/frr-llvm-cg.c
@@ -0,0 +1,974 @@
+// This is free and unencumbered software released into the public domain.
+//
+// Anyone is free to copy, modify, publish, use, compile, sell, or
+// distribute this software, either in source code form or as a compiled
+// binary, for any purpose, commercial or non-commercial, and by any
+// means.
+//
+// In jurisdictions that recognize copyright laws, the author or authors
+// of this software dedicate any and all copyright interest in the
+// software to the public domain. We make this dedication for the benefit
+// of the public at large and to the detriment of our heirs and
+// successors. We intend this dedication to be an overt act of
+// relinquishment in perpetuity of all present and future rights to this
+// software under copyright law.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+// IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
+// OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
+// ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+// OTHER DEALINGS IN THE SOFTWARE.
+//
+// For more information, please refer to <http://unlicense.org/>
+
+/* 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 "thread_add_read_write",
+ thread_prefix "thread_add_timer",
+ thread_prefix "thread_add_timer_msec",
+ thread_prefix "thread_add_timer_tv",
+ thread_prefix "thread_add_event",
+ thread_prefix "thread_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..ed3ad95
--- /dev/null
+++ b/tools/frr-llvm-debuginfo.cpp
@@ -0,0 +1,112 @@
+// This is free and unencumbered software released into the public domain.
+//
+// Anyone is free to copy, modify, publish, use, compile, sell, or
+// distribute this software, either in source code form or as a compiled
+// binary, for any purpose, commercial or non-commercial, and by any
+// means.
+//
+// In jurisdictions that recognize copyright laws, the author or authors
+// of this software dedicate any and all copyright interest in the
+// software to the public domain. We make this dedication for the benefit
+// of the public at large and to the detriment of our heirs and
+// successors. We intend this dedication to be an overt act of
+// relinquishment in perpetuity of all present and future rights to this
+// software under copyright law.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+// IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
+// OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
+// ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+// OTHER DEALINGS IN THE SOFTWARE.
+//
+// For more information, please refer to <http://unlicense.org/>
+
+#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..fca4bc1
--- /dev/null
+++ b/tools/frr-llvm-debuginfo.h
@@ -0,0 +1,47 @@
+// This is free and unencumbered software released into the public domain.
+//
+// Anyone is free to copy, modify, publish, use, compile, sell, or
+// distribute this software, either in source code form or as a compiled
+// binary, for any purpose, commercial or non-commercial, and by any
+// means.
+//
+// In jurisdictions that recognize copyright laws, the author or authors
+// of this software dedicate any and all copyright interest in the
+// software to the public domain. We make this dedication for the benefit
+// of the public at large and to the detriment of our heirs and
+// successors. We intend this dedication to be an overt act of
+// relinquishment in perpetuity of all present and future rights to this
+// software under copyright law.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+// IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
+// OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
+// ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+// OTHER DEALINGS IN THE SOFTWARE.
+//
+// For more information, please refer to <http://unlicense.org/>
+
+#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..db8a728
--- /dev/null
+++ b/tools/frr-reload.py
@@ -0,0 +1,2218 @@
+#!/usr/bin/env python3
+# Frr Reloader
+# Copyright (C) 2014 Cumulus Networks, Inc.
+#
+# This file is part of Frr.
+#
+# Frr is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2, or (at your option) any
+# later version.
+#
+# Frr is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Frr; see the file COPYING. If not, write to the Free
+# Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+# 02111-1307, USA.
+#
+"""
+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
+
+
+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.
+
+ 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": {},
+ }
+
+ # 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 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)
+
+ # 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]))
+
+ 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 check_for_exit_vrf(lines_to_add, lines_to_del):
+
+ # exit-vrf is a bit tricky. If the new config is missing it but we
+ # have configs under a vrf, we need to add it at the end to do the
+ # right context changes. If exit-vrf exists in both the running and
+ # new config, we cannot delete it or it will break context changes.
+ add_exit_vrf = False
+ index = 0
+
+ for (ctx_keys, line) in lines_to_add:
+ if add_exit_vrf == True:
+ if ctx_keys[0] != prior_ctx_key:
+ insert_key = ((prior_ctx_key),)
+ lines_to_add.insert(index, ((insert_key, "exit-vrf")))
+ add_exit_vrf = False
+
+ if ctx_keys[0].startswith("vrf") and line:
+ if line != "exit-vrf":
+ add_exit_vrf = True
+ prior_ctx_key = ctx_keys[0]
+ else:
+ add_exit_vrf = False
+ index += 1
+
+ for (ctx_keys, line) in lines_to_del:
+ if line == "exit-vrf":
+ if line_exist(lines_to_add, ctx_keys, line):
+ lines_to_del.remove((ctx_keys, line))
+
+ return (lines_to_add, lines_to_del)
+
+
+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 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 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 = []
+
+ for (ctx_keys, line) in lines_to_del:
+ deleted = False
+
+ # 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
+
+ # 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 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
+
+ # 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) = check_for_exit_vrf(lines_to_add, lines_to_del)
+ (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",
+ "ospf6d",
+ "ospfd",
+ "pbrd",
+ "pimd",
+ "pim6d",
+ "ripd",
+ "ripngd",
+ "sharpd",
+ "staticd",
+ "vrrpd",
+ "ldpd",
+ "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..e9f1122
--- /dev/null
+++ b/tools/frr.in
@@ -0,0 +1,611 @@
+#!/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
+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="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 $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..df1e4f3
--- /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=on-abnormal
+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..86aa0c0
--- /dev/null
+++ b/tools/frr.vim
@@ -0,0 +1,36 @@
+" 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()
diff --git a/tools/frr@.service.in b/tools/frr@.service.in
new file mode 100644
index 0000000..1cbef1b
--- /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=on-abnormal
+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..27d830a
--- /dev/null
+++ b/tools/frr_babeltrace.py
@@ -0,0 +1,266 @@
+#!/usr/bin/env python3
+"""
+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
+
+This program is free software; you can redistribute it and/or modify it
+under the terms of the GNU General Public License as published by the Free
+Software Foundation; either version 2 of the License, or (at your option)
+any later version.
+
+This program is distributed in the hope that it will be useful, but WITHOUT
+ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+more details.
+
+You should have received a copy of the GNU General Public License along
+with this program; see the file COPYING; if not, write to the Free Software
+Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+"""
+
+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..3c16c27
--- /dev/null
+++ b/tools/frrcommon.sh.in
@@ -0,0 +1,390 @@
+#!/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
+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 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 () {
+ [ "${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 ] && 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" ] && [ "$2" != "--quiet" ]; then
+ 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
+ log_failure_msg "Failed to stop $dmninst, pid $pid still running"
+ still_running=1
+ return 1
+ else
+ 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 need_zebra
+
+ 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
+
+ # Stop zebra last, after trying to stop the other daemons
+ for dmninst in $reversed; do
+ if [ "$dmninst" = "zebra" ]; then
+ need_zebra="yes"
+ continue
+ fi
+
+ daemon_stop "$dmninst" "$1" &
+ pids="$pids $!"
+ done
+ for pid in $pids; do
+ wait $pid
+ done
+
+ if [ -n "$need_zebra" ]; then
+ daemon_stop "zebra"
+ fi
+}
+
+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..ee10b89
--- /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 "$NEW_CONFIG_FILE" `echo $nsopt`
+ exit $?
+ ;;
+
+*)
+ 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/COPYING.GPLv3 b/tools/gcc-plugins/COPYING.GPLv3
new file mode 100644
index 0000000..94a9ed0
--- /dev/null
+++ b/tools/gcc-plugins/COPYING.GPLv3
@@ -0,0 +1,674 @@
+ GNU GENERAL PUBLIC LICENSE
+ Version 3, 29 June 2007
+
+ Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+ Preamble
+
+ The GNU General Public License is a free, copyleft license for
+software and other kinds of works.
+
+ The licenses for most software and other practical works are designed
+to take away your freedom to share and change the works. By contrast,
+the GNU General Public License is intended to guarantee your freedom to
+share and change all versions of a program--to make sure it remains free
+software for all its users. We, the Free Software Foundation, use the
+GNU General Public License for most of our software; it applies also to
+any other work released this way by its authors. You can apply it to
+your programs, too.
+
+ When we speak of free software, we are referring to freedom, not
+price. Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+them if you wish), that you receive source code or can get it if you
+want it, that you can change the software or use pieces of it in new
+free programs, and that you know you can do these things.
+
+ To protect your rights, we need to prevent others from denying you
+these rights or asking you to surrender the rights. Therefore, you have
+certain responsibilities if you distribute copies of the software, or if
+you modify it: responsibilities to respect the freedom of others.
+
+ For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must pass on to the recipients the same
+freedoms that you received. You must make sure that they, too, receive
+or can get the source code. And you must show them these terms so they
+know their rights.
+
+ Developers that use the GNU GPL protect your rights with two steps:
+(1) assert copyright on the software, and (2) offer you this License
+giving you legal permission to copy, distribute and/or modify it.
+
+ For the developers' and authors' protection, the GPL clearly explains
+that there is no warranty for this free software. For both users' and
+authors' sake, the GPL requires that modified versions be marked as
+changed, so that their problems will not be attributed erroneously to
+authors of previous versions.
+
+ Some devices are designed to deny users access to install or run
+modified versions of the software inside them, although the manufacturer
+can do so. This is fundamentally incompatible with the aim of
+protecting users' freedom to change the software. The systematic
+pattern of such abuse occurs in the area of products for individuals to
+use, which is precisely where it is most unacceptable. Therefore, we
+have designed this version of the GPL to prohibit the practice for those
+products. If such problems arise substantially in other domains, we
+stand ready to extend this provision to those domains in future versions
+of the GPL, as needed to protect the freedom of users.
+
+ Finally, every program is threatened constantly by software patents.
+States should not allow patents to restrict development and use of
+software on general-purpose computers, but in those that do, we wish to
+avoid the special danger that patents applied to a free program could
+make it effectively proprietary. To prevent this, the GPL assures that
+patents cannot be used to render the program non-free.
+
+ The precise terms and conditions for copying, distribution and
+modification follow.
+
+ TERMS AND CONDITIONS
+
+ 0. Definitions.
+
+ "This License" refers to version 3 of the GNU General Public License.
+
+ "Copyright" also means copyright-like laws that apply to other kinds of
+works, such as semiconductor masks.
+
+ "The Program" refers to any copyrightable work licensed under this
+License. Each licensee is addressed as "you". "Licensees" and
+"recipients" may be individuals or organizations.
+
+ To "modify" a work means to copy from or adapt all or part of the work
+in a fashion requiring copyright permission, other than the making of an
+exact copy. The resulting work is called a "modified version" of the
+earlier work or a work "based on" the earlier work.
+
+ A "covered work" means either the unmodified Program or a work based
+on the Program.
+
+ To "propagate" a work means to do anything with it that, without
+permission, would make you directly or secondarily liable for
+infringement under applicable copyright law, except executing it on a
+computer or modifying a private copy. Propagation includes copying,
+distribution (with or without modification), making available to the
+public, and in some countries other activities as well.
+
+ To "convey" a work means any kind of propagation that enables other
+parties to make or receive copies. Mere interaction with a user through
+a computer network, with no transfer of a copy, is not conveying.
+
+ An interactive user interface displays "Appropriate Legal Notices"
+to the extent that it includes a convenient and prominently visible
+feature that (1) displays an appropriate copyright notice, and (2)
+tells the user that there is no warranty for the work (except to the
+extent that warranties are provided), that licensees may convey the
+work under this License, and how to view a copy of this License. If
+the interface presents a list of user commands or options, such as a
+menu, a prominent item in the list meets this criterion.
+
+ 1. Source Code.
+
+ The "source code" for a work means the preferred form of the work
+for making modifications to it. "Object code" means any non-source
+form of a work.
+
+ A "Standard Interface" means an interface that either is an official
+standard defined by a recognized standards body, or, in the case of
+interfaces specified for a particular programming language, one that
+is widely used among developers working in that language.
+
+ The "System Libraries" of an executable work include anything, other
+than the work as a whole, that (a) is included in the normal form of
+packaging a Major Component, but which is not part of that Major
+Component, and (b) serves only to enable use of the work with that
+Major Component, or to implement a Standard Interface for which an
+implementation is available to the public in source code form. A
+"Major Component", in this context, means a major essential component
+(kernel, window system, and so on) of the specific operating system
+(if any) on which the executable work runs, or a compiler used to
+produce the work, or an object code interpreter used to run it.
+
+ The "Corresponding Source" for a work in object code form means all
+the source code needed to generate, install, and (for an executable
+work) run the object code and to modify the work, including scripts to
+control those activities. However, it does not include the work's
+System Libraries, or general-purpose tools or generally available free
+programs which are used unmodified in performing those activities but
+which are not part of the work. For example, Corresponding Source
+includes interface definition files associated with source files for
+the work, and the source code for shared libraries and dynamically
+linked subprograms that the work is specifically designed to require,
+such as by intimate data communication or control flow between those
+subprograms and other parts of the work.
+
+ The Corresponding Source need not include anything that users
+can regenerate automatically from other parts of the Corresponding
+Source.
+
+ The Corresponding Source for a work in source code form is that
+same work.
+
+ 2. Basic Permissions.
+
+ All rights granted under this License are granted for the term of
+copyright on the Program, and are irrevocable provided the stated
+conditions are met. This License explicitly affirms your unlimited
+permission to run the unmodified Program. The output from running a
+covered work is covered by this License only if the output, given its
+content, constitutes a covered work. This License acknowledges your
+rights of fair use or other equivalent, as provided by copyright law.
+
+ You may make, run and propagate covered works that you do not
+convey, without conditions so long as your license otherwise remains
+in force. You may convey covered works to others for the sole purpose
+of having them make modifications exclusively for you, or provide you
+with facilities for running those works, provided that you comply with
+the terms of this License in conveying all material for which you do
+not control copyright. Those thus making or running the covered works
+for you must do so exclusively on your behalf, under your direction
+and control, on terms that prohibit them from making any copies of
+your copyrighted material outside their relationship with you.
+
+ Conveying under any other circumstances is permitted solely under
+the conditions stated below. Sublicensing is not allowed; section 10
+makes it unnecessary.
+
+ 3. Protecting Users' Legal Rights From Anti-Circumvention Law.
+
+ No covered work shall be deemed part of an effective technological
+measure under any applicable law fulfilling obligations under article
+11 of the WIPO copyright treaty adopted on 20 December 1996, or
+similar laws prohibiting or restricting circumvention of such
+measures.
+
+ When you convey a covered work, you waive any legal power to forbid
+circumvention of technological measures to the extent such circumvention
+is effected by exercising rights under this License with respect to
+the covered work, and you disclaim any intention to limit operation or
+modification of the work as a means of enforcing, against the work's
+users, your or third parties' legal rights to forbid circumvention of
+technological measures.
+
+ 4. Conveying Verbatim Copies.
+
+ You may convey verbatim copies of the Program's source code as you
+receive it, in any medium, provided that you conspicuously and
+appropriately publish on each copy an appropriate copyright notice;
+keep intact all notices stating that this License and any
+non-permissive terms added in accord with section 7 apply to the code;
+keep intact all notices of the absence of any warranty; and give all
+recipients a copy of this License along with the Program.
+
+ You may charge any price or no price for each copy that you convey,
+and you may offer support or warranty protection for a fee.
+
+ 5. Conveying Modified Source Versions.
+
+ You may convey a work based on the Program, or the modifications to
+produce it from the Program, in the form of source code under the
+terms of section 4, provided that you also meet all of these conditions:
+
+ a) The work must carry prominent notices stating that you modified
+ it, and giving a relevant date.
+
+ b) The work must carry prominent notices stating that it is
+ released under this License and any conditions added under section
+ 7. This requirement modifies the requirement in section 4 to
+ "keep intact all notices".
+
+ c) You must license the entire work, as a whole, under this
+ License to anyone who comes into possession of a copy. This
+ License will therefore apply, along with any applicable section 7
+ additional terms, to the whole of the work, and all its parts,
+ regardless of how they are packaged. This License gives no
+ permission to license the work in any other way, but it does not
+ invalidate such permission if you have separately received it.
+
+ d) If the work has interactive user interfaces, each must display
+ Appropriate Legal Notices; however, if the Program has interactive
+ interfaces that do not display Appropriate Legal Notices, your
+ work need not make them do so.
+
+ A compilation of a covered work with other separate and independent
+works, which are not by their nature extensions of the covered work,
+and which are not combined with it such as to form a larger program,
+in or on a volume of a storage or distribution medium, is called an
+"aggregate" if the compilation and its resulting copyright are not
+used to limit the access or legal rights of the compilation's users
+beyond what the individual works permit. Inclusion of a covered work
+in an aggregate does not cause this License to apply to the other
+parts of the aggregate.
+
+ 6. Conveying Non-Source Forms.
+
+ You may convey a covered work in object code form under the terms
+of sections 4 and 5, provided that you also convey the
+machine-readable Corresponding Source under the terms of this License,
+in one of these ways:
+
+ a) Convey the object code in, or embodied in, a physical product
+ (including a physical distribution medium), accompanied by the
+ Corresponding Source fixed on a durable physical medium
+ customarily used for software interchange.
+
+ b) Convey the object code in, or embodied in, a physical product
+ (including a physical distribution medium), accompanied by a
+ written offer, valid for at least three years and valid for as
+ long as you offer spare parts or customer support for that product
+ model, to give anyone who possesses the object code either (1) a
+ copy of the Corresponding Source for all the software in the
+ product that is covered by this License, on a durable physical
+ medium customarily used for software interchange, for a price no
+ more than your reasonable cost of physically performing this
+ conveying of source, or (2) access to copy the
+ Corresponding Source from a network server at no charge.
+
+ c) Convey individual copies of the object code with a copy of the
+ written offer to provide the Corresponding Source. This
+ alternative is allowed only occasionally and noncommercially, and
+ only if you received the object code with such an offer, in accord
+ with subsection 6b.
+
+ d) Convey the object code by offering access from a designated
+ place (gratis or for a charge), and offer equivalent access to the
+ Corresponding Source in the same way through the same place at no
+ further charge. You need not require recipients to copy the
+ Corresponding Source along with the object code. If the place to
+ copy the object code is a network server, the Corresponding Source
+ may be on a different server (operated by you or a third party)
+ that supports equivalent copying facilities, provided you maintain
+ clear directions next to the object code saying where to find the
+ Corresponding Source. Regardless of what server hosts the
+ Corresponding Source, you remain obligated to ensure that it is
+ available for as long as needed to satisfy these requirements.
+
+ e) Convey the object code using peer-to-peer transmission, provided
+ you inform other peers where the object code and Corresponding
+ Source of the work are being offered to the general public at no
+ charge under subsection 6d.
+
+ A separable portion of the object code, whose source code is excluded
+from the Corresponding Source as a System Library, need not be
+included in conveying the object code work.
+
+ A "User Product" is either (1) a "consumer product", which means any
+tangible personal property which is normally used for personal, family,
+or household purposes, or (2) anything designed or sold for incorporation
+into a dwelling. In determining whether a product is a consumer product,
+doubtful cases shall be resolved in favor of coverage. For a particular
+product received by a particular user, "normally used" refers to a
+typical or common use of that class of product, regardless of the status
+of the particular user or of the way in which the particular user
+actually uses, or expects or is expected to use, the product. A product
+is a consumer product regardless of whether the product has substantial
+commercial, industrial or non-consumer uses, unless such uses represent
+the only significant mode of use of the product.
+
+ "Installation Information" for a User Product means any methods,
+procedures, authorization keys, or other information required to install
+and execute modified versions of a covered work in that User Product from
+a modified version of its Corresponding Source. The information must
+suffice to ensure that the continued functioning of the modified object
+code is in no case prevented or interfered with solely because
+modification has been made.
+
+ If you convey an object code work under this section in, or with, or
+specifically for use in, a User Product, and the conveying occurs as
+part of a transaction in which the right of possession and use of the
+User Product is transferred to the recipient in perpetuity or for a
+fixed term (regardless of how the transaction is characterized), the
+Corresponding Source conveyed under this section must be accompanied
+by the Installation Information. But this requirement does not apply
+if neither you nor any third party retains the ability to install
+modified object code on the User Product (for example, the work has
+been installed in ROM).
+
+ The requirement to provide Installation Information does not include a
+requirement to continue to provide support service, warranty, or updates
+for a work that has been modified or installed by the recipient, or for
+the User Product in which it has been modified or installed. Access to a
+network may be denied when the modification itself materially and
+adversely affects the operation of the network or violates the rules and
+protocols for communication across the network.
+
+ Corresponding Source conveyed, and Installation Information provided,
+in accord with this section must be in a format that is publicly
+documented (and with an implementation available to the public in
+source code form), and must require no special password or key for
+unpacking, reading or copying.
+
+ 7. Additional Terms.
+
+ "Additional permissions" are terms that supplement the terms of this
+License by making exceptions from one or more of its conditions.
+Additional permissions that are applicable to the entire Program shall
+be treated as though they were included in this License, to the extent
+that they are valid under applicable law. If additional permissions
+apply only to part of the Program, that part may be used separately
+under those permissions, but the entire Program remains governed by
+this License without regard to the additional permissions.
+
+ When you convey a copy of a covered work, you may at your option
+remove any additional permissions from that copy, or from any part of
+it. (Additional permissions may be written to require their own
+removal in certain cases when you modify the work.) You may place
+additional permissions on material, added by you to a covered work,
+for which you have or can give appropriate copyright permission.
+
+ Notwithstanding any other provision of this License, for material you
+add to a covered work, you may (if authorized by the copyright holders of
+that material) supplement the terms of this License with terms:
+
+ a) Disclaiming warranty or limiting liability differently from the
+ terms of sections 15 and 16 of this License; or
+
+ b) Requiring preservation of specified reasonable legal notices or
+ author attributions in that material or in the Appropriate Legal
+ Notices displayed by works containing it; or
+
+ c) Prohibiting misrepresentation of the origin of that material, or
+ requiring that modified versions of such material be marked in
+ reasonable ways as different from the original version; or
+
+ d) Limiting the use for publicity purposes of names of licensors or
+ authors of the material; or
+
+ e) Declining to grant rights under trademark law for use of some
+ trade names, trademarks, or service marks; or
+
+ f) Requiring indemnification of licensors and authors of that
+ material by anyone who conveys the material (or modified versions of
+ it) with contractual assumptions of liability to the recipient, for
+ any liability that these contractual assumptions directly impose on
+ those licensors and authors.
+
+ All other non-permissive additional terms are considered "further
+restrictions" within the meaning of section 10. If the Program as you
+received it, or any part of it, contains a notice stating that it is
+governed by this License along with a term that is a further
+restriction, you may remove that term. If a license document contains
+a further restriction but permits relicensing or conveying under this
+License, you may add to a covered work material governed by the terms
+of that license document, provided that the further restriction does
+not survive such relicensing or conveying.
+
+ If you add terms to a covered work in accord with this section, you
+must place, in the relevant source files, a statement of the
+additional terms that apply to those files, or a notice indicating
+where to find the applicable terms.
+
+ Additional terms, permissive or non-permissive, may be stated in the
+form of a separately written license, or stated as exceptions;
+the above requirements apply either way.
+
+ 8. Termination.
+
+ You may not propagate or modify a covered work except as expressly
+provided under this License. Any attempt otherwise to propagate or
+modify it is void, and will automatically terminate your rights under
+this License (including any patent licenses granted under the third
+paragraph of section 11).
+
+ However, if you cease all violation of this License, then your
+license from a particular copyright holder is reinstated (a)
+provisionally, unless and until the copyright holder explicitly and
+finally terminates your license, and (b) permanently, if the copyright
+holder fails to notify you of the violation by some reasonable means
+prior to 60 days after the cessation.
+
+ Moreover, your license from a particular copyright holder is
+reinstated permanently if the copyright holder notifies you of the
+violation by some reasonable means, this is the first time you have
+received notice of violation of this License (for any work) from that
+copyright holder, and you cure the violation prior to 30 days after
+your receipt of the notice.
+
+ Termination of your rights under this section does not terminate the
+licenses of parties who have received copies or rights from you under
+this License. If your rights have been terminated and not permanently
+reinstated, you do not qualify to receive new licenses for the same
+material under section 10.
+
+ 9. Acceptance Not Required for Having Copies.
+
+ You are not required to accept this License in order to receive or
+run a copy of the Program. Ancillary propagation of a covered work
+occurring solely as a consequence of using peer-to-peer transmission
+to receive a copy likewise does not require acceptance. However,
+nothing other than this License grants you permission to propagate or
+modify any covered work. These actions infringe copyright if you do
+not accept this License. Therefore, by modifying or propagating a
+covered work, you indicate your acceptance of this License to do so.
+
+ 10. Automatic Licensing of Downstream Recipients.
+
+ Each time you convey a covered work, the recipient automatically
+receives a license from the original licensors, to run, modify and
+propagate that work, subject to this License. You are not responsible
+for enforcing compliance by third parties with this License.
+
+ An "entity transaction" is a transaction transferring control of an
+organization, or substantially all assets of one, or subdividing an
+organization, or merging organizations. If propagation of a covered
+work results from an entity transaction, each party to that
+transaction who receives a copy of the work also receives whatever
+licenses to the work the party's predecessor in interest had or could
+give under the previous paragraph, plus a right to possession of the
+Corresponding Source of the work from the predecessor in interest, if
+the predecessor has it or can get it with reasonable efforts.
+
+ You may not impose any further restrictions on the exercise of the
+rights granted or affirmed under this License. For example, you may
+not impose a license fee, royalty, or other charge for exercise of
+rights granted under this License, and you may not initiate litigation
+(including a cross-claim or counterclaim in a lawsuit) alleging that
+any patent claim is infringed by making, using, selling, offering for
+sale, or importing the Program or any portion of it.
+
+ 11. Patents.
+
+ A "contributor" is a copyright holder who authorizes use under this
+License of the Program or a work on which the Program is based. The
+work thus licensed is called the contributor's "contributor version".
+
+ A contributor's "essential patent claims" are all patent claims
+owned or controlled by the contributor, whether already acquired or
+hereafter acquired, that would be infringed by some manner, permitted
+by this License, of making, using, or selling its contributor version,
+but do not include claims that would be infringed only as a
+consequence of further modification of the contributor version. For
+purposes of this definition, "control" includes the right to grant
+patent sublicenses in a manner consistent with the requirements of
+this License.
+
+ Each contributor grants you a non-exclusive, worldwide, royalty-free
+patent license under the contributor's essential patent claims, to
+make, use, sell, offer for sale, import and otherwise run, modify and
+propagate the contents of its contributor version.
+
+ In the following three paragraphs, a "patent license" is any express
+agreement or commitment, however denominated, not to enforce a patent
+(such as an express permission to practice a patent or covenant not to
+sue for patent infringement). To "grant" such a patent license to a
+party means to make such an agreement or commitment not to enforce a
+patent against the party.
+
+ If you convey a covered work, knowingly relying on a patent license,
+and the Corresponding Source of the work is not available for anyone
+to copy, free of charge and under the terms of this License, through a
+publicly available network server or other readily accessible means,
+then you must either (1) cause the Corresponding Source to be so
+available, or (2) arrange to deprive yourself of the benefit of the
+patent license for this particular work, or (3) arrange, in a manner
+consistent with the requirements of this License, to extend the patent
+license to downstream recipients. "Knowingly relying" means you have
+actual knowledge that, but for the patent license, your conveying the
+covered work in a country, or your recipient's use of the covered work
+in a country, would infringe one or more identifiable patents in that
+country that you have reason to believe are valid.
+
+ If, pursuant to or in connection with a single transaction or
+arrangement, you convey, or propagate by procuring conveyance of, a
+covered work, and grant a patent license to some of the parties
+receiving the covered work authorizing them to use, propagate, modify
+or convey a specific copy of the covered work, then the patent license
+you grant is automatically extended to all recipients of the covered
+work and works based on it.
+
+ A patent license is "discriminatory" if it does not include within
+the scope of its coverage, prohibits the exercise of, or is
+conditioned on the non-exercise of one or more of the rights that are
+specifically granted under this License. You may not convey a covered
+work if you are a party to an arrangement with a third party that is
+in the business of distributing software, under which you make payment
+to the third party based on the extent of your activity of conveying
+the work, and under which the third party grants, to any of the
+parties who would receive the covered work from you, a discriminatory
+patent license (a) in connection with copies of the covered work
+conveyed by you (or copies made from those copies), or (b) primarily
+for and in connection with specific products or compilations that
+contain the covered work, unless you entered into that arrangement,
+or that patent license was granted, prior to 28 March 2007.
+
+ Nothing in this License shall be construed as excluding or limiting
+any implied license or other defenses to infringement that may
+otherwise be available to you under applicable patent law.
+
+ 12. No Surrender of Others' Freedom.
+
+ If conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License. If you cannot convey a
+covered work so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you may
+not convey it at all. For example, if you agree to terms that obligate you
+to collect a royalty for further conveying from those to whom you convey
+the Program, the only way you could satisfy both those terms and this
+License would be to refrain entirely from conveying the Program.
+
+ 13. Use with the GNU Affero General Public License.
+
+ Notwithstanding any other provision of this License, you have
+permission to link or combine any covered work with a work licensed
+under version 3 of the GNU Affero General Public License into a single
+combined work, and to convey the resulting work. The terms of this
+License will continue to apply to the part which is the covered work,
+but the special requirements of the GNU Affero General Public License,
+section 13, concerning interaction through a network will apply to the
+combination as such.
+
+ 14. Revised Versions of this License.
+
+ The Free Software Foundation may publish revised and/or new versions of
+the GNU General Public License from time to time. Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+ Each version is given a distinguishing version number. If the
+Program specifies that a certain numbered version of the GNU General
+Public License "or any later version" applies to it, you have the
+option of following the terms and conditions either of that numbered
+version or of any later version published by the Free Software
+Foundation. If the Program does not specify a version number of the
+GNU General Public License, you may choose any version ever published
+by the Free Software Foundation.
+
+ If the Program specifies that a proxy can decide which future
+versions of the GNU General Public License can be used, that proxy's
+public statement of acceptance of a version permanently authorizes you
+to choose that version for the Program.
+
+ Later license versions may give you additional or different
+permissions. However, no additional obligations are imposed on any
+author or copyright holder as a result of your choosing to follow a
+later version.
+
+ 15. Disclaimer of Warranty.
+
+ THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
+APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
+HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
+OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
+THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
+IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
+ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
+
+ 16. Limitation of Liability.
+
+ IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
+THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
+GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
+USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
+DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
+PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
+EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
+SUCH DAMAGES.
+
+ 17. Interpretation of Sections 15 and 16.
+
+ If the disclaimer of warranty and limitation of liability provided
+above cannot be given local legal effect according to their terms,
+reviewing courts shall apply local law that most closely approximates
+an absolute waiver of all civil liability in connection with the
+Program, unless a warranty or assumption of liability accompanies a
+copy of the Program in return for a fee.
+
+ END OF TERMS AND CONDITIONS
+
+ How to Apply These Terms to Your New Programs
+
+ If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+ To do so, attach the following notices to the program. It is safest
+to attach them to the start of each source file to most effectively
+state the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+ <one line to give the program's name and a brief idea of what it does.>
+ Copyright (C) <year> <name of author>
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+Also add information on how to contact you by electronic and paper mail.
+
+ If the program does terminal interaction, make it output a short
+notice like this when it starts in an interactive mode:
+
+ <program> Copyright (C) <year> <name of author>
+ This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+ This is free software, and you are welcome to redistribute it
+ under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License. Of course, your program's commands
+might be different; for a GUI interface, you would use an "about box".
+
+ You should also get your employer (if you work as a programmer) or school,
+if any, to sign a "copyright disclaimer" for the program, if necessary.
+For more information on this, and how to apply and follow the GNU GPL, see
+<http://www.gnu.org/licenses/>.
+
+ The GNU General Public License does not permit incorporating your program
+into proprietary programs. If your program is a subroutine library, you
+may consider it more useful to permit linking proprietary applications with
+the library. If this is what you want to do, use the GNU Lesser General
+Public License instead of this License. But first, please read
+<http://www.gnu.org/philosophy/why-not-lgpl.html>.
diff --git a/tools/gcc-plugins/Makefile b/tools/gcc-plugins/Makefile
new file mode 100644
index 0000000..d6edd74
--- /dev/null
+++ b/tools/gcc-plugins/Makefile
@@ -0,0 +1,19 @@
+all: frr-format.so
+
+CXX=g++-9
+
+PLUGBASE=`$(CXX) -print-file-name=plugin`
+CPPFLAGS=-I$(PLUGBASE)/include -I$(PLUGBASE)/include/c-family
+
+frr-format.so: frr-format.o
+ $(CXX) -g -shared -o $@ $^
+
+frr-format.o: frr-format.c gcc-common.h
+ $(CXX) -g $(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..0a24b09
--- /dev/null
+++ b/tools/gcc-plugins/frr-format.c
@@ -0,0 +1,4475 @@
+/* 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.
+
+This file is part of GCC.
+
+GCC is free software; you can redistribute it and/or modify it under
+the terms of the GNU General Public License as published by the Free
+Software Foundation; either version 3, or (at your option) any later
+version.
+
+GCC is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or
+FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+for more details.
+
+You should have received a copy of the GNU General Public License
+along with GCC; see the file COPYING.GPLv3. If not see
+<http://www.gnu.org/licenses/>. */
+
+#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 },
+ { "oxX", 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)
+ {
+ }
+
+ label_text get_text (unsigned range_idx) const FINAL OVERRIDE
+ {
+ label_text text = range_label_for_type_mismatch::get_text (range_idx);
+ if (text.m_buffer == 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.m_buffer, p, NULL);
+ text.maybe_free ();
+ return label_take(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..599dbc5
--- /dev/null
+++ b/tools/gcc-plugins/frr-format.h
@@ -0,0 +1,366 @@
+/* Check calls to formatted I/O functions (-Wformat).
+ Copyright (C) 1992-2018 Free Software Foundation, Inc.
+
+This file is part of GCC.
+
+GCC is free software; you can redistribute it and/or modify it under
+the terms of the GNU General Public License as published by the Free
+Software Foundation; either version 3, or (at your option) any later
+version.
+
+GCC is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or
+FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+for more details.
+
+You should have received a copy of the GNU General Public License
+along with GCC; see the file COPYING3. If not see
+<http://www.gnu.org/licenses/>. */
+
+#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..9f59447
--- /dev/null
+++ b/tools/gcc-plugins/gcc-common.h
@@ -0,0 +1,990 @@
+/* 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"
+#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..1705a32
--- /dev/null
+++ b/tools/gen_northbound_callbacks.c
@@ -0,0 +1,403 @@
+/*
+ * Copyright (C) 2018 NetDEF, Inc.
+ * Renato Westphal
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; see the file COPYING; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#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..8aa5695
--- /dev/null
+++ b/tools/gen_yang_deviations.c
@@ -0,0 +1,80 @@
+/*
+ * Copyright (C) 2018 NetDEF, Inc.
+ * Renato Westphal
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; see the file COPYING; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#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..104d971
--- /dev/null
+++ b/tools/generate_support_bundle.py
@@ -0,0 +1,99 @@
+#!/usr/bin/env python3
+#
+# Copyright (c) 2021, LabN Consulting, L.L.C.
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; see the file COPYING; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+#
+
+########################################################
+### 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..61a0fd4
--- /dev/null
+++ b/tools/indent.py
@@ -0,0 +1,52 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+# 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..5dce43f
--- /dev/null
+++ b/tools/nhrpd-event-handler.sh
@@ -0,0 +1,216 @@
+#!/bin/bash
+
+# Author: Joe Maimon
+# Released to public domain
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the Free
+# Software Foundation; either version 2 of the License, or (at your option)
+# any later version.
+#
+# This program is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+# more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; see the file COPYING; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+#
+
+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..b280cc1
--- /dev/null
+++ b/tools/permutations.c
@@ -0,0 +1,112 @@
+/*
+ * Generates all possible matching inputs for a command string.
+ * --
+ * Copyright (C) 2016 Cumulus Networks, Inc.
+ *
+ * This file is part of GNU Zebra.
+ *
+ * GNU Zebra is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2, or (at your option) any
+ * later version.
+ *
+ * GNU Zebra is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; see the file COPYING; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#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
+ || tok->attr == CMD_ATTR_DEPRECATED)
+ 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..7636496
--- /dev/null
+++ b/tools/render_md.py
@@ -0,0 +1,30 @@
+#!/usr/bin/env python
+# 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..0e4cf27
--- /dev/null
+++ b/tools/start-stop-daemon.c
@@ -0,0 +1,1061 @@
+/*
+ * 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));
+ }
+
+ default:
+ 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..1c75c86
--- /dev/null
+++ b/tools/stringmangle.py
@@ -0,0 +1,61 @@
+# 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..eefeee3
--- /dev/null
+++ b/tools/symalyzer.html
@@ -0,0 +1,347 @@
+<html>
+<!--
+ - 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..ce0bfde
--- /dev/null
+++ b/tools/symalyzer.py
@@ -0,0 +1,406 @@
+#!/usr/bin/python3
+#
+# 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..5307fa3
--- /dev/null
+++ b/tools/zc.pl
@@ -0,0 +1,111 @@
+#!/usr/bin/env perl
+##
+## Zebra interactive console
+## Copyright (C) 2000 Vladimir B. Grebenschikov <vova@express.ru>
+##
+## This file is part of GNU Zebra.
+##
+## GNU Zebra is free software; you can redistribute it and/or modify it
+## under the terms of the GNU General Public License as published by the
+## Free Software Foundation; either version 2, or (at your option) any
+## later version.
+##
+## GNU Zebra is distributed in the hope that it will be useful, but
+## WITHOUT ANY WARRANTY; without even the implied warranty of
+## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+## General Public License for more details.
+##
+## You should have received a copy of the GNU General Public License
+## along with GNU Zebra; see the file COPYING. If not, write to the
+## Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+## Boston, MA 02111-1307, USA.
+
+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))