summaryrefslogtreecommitdiffstats
path: root/devel
diff options
context:
space:
mode:
Diffstat (limited to 'devel')
-rw-r--r--devel/Makefile.am295
-rw-r--r--devel/README2
-rw-r--r--devel/coccinelle/ref-passed-variables-inited.cocci50
-rw-r--r--devel/coccinelle/rename-fn.cocci23
-rw-r--r--devel/coccinelle/rename-var.cocci29
-rw-r--r--devel/coccinelle/string-any-of.cocci76
-rw-r--r--devel/coccinelle/string-empty.cocci45
-rw-r--r--devel/coccinelle/string-null-matches.cocci74
-rw-r--r--devel/coccinelle/test/ref-passed-variables-inited.input.c88
-rw-r--r--devel/coccinelle/test/ref-passed-variables-inited.output76
-rwxr-xr-xdevel/coccinelle/test/testrunner.sh13
-rw-r--r--devel/coccinelle/use-func.cocci22
-rw-r--r--devel/gdbhelpers109
13 files changed, 902 insertions, 0 deletions
diff --git a/devel/Makefile.am b/devel/Makefile.am
new file mode 100644
index 0000000..94581e1
--- /dev/null
+++ b/devel/Makefile.am
@@ -0,0 +1,295 @@
+#
+# Copyright 2020-2023 the Pacemaker project contributors
+#
+# The version control history for this file may have further details.
+#
+# This source code is licensed under the GNU General Public License version 2
+# or later (GPLv2+) WITHOUT ANY WARRANTY.
+#
+
+include $(top_srcdir)/mk/common.mk
+include $(top_srcdir)/mk/release.mk
+
+# Coccinelle is a tool that takes special patch-like files (called semantic patches) and
+# applies them throughout a source tree. This is useful when refactoring, changing APIs,
+# catching dangerous or incorrect code, and other similar tasks. It's not especially
+# easy to write a semantic patch but most users should only be concerned about running
+# the target and inspecting the results.
+#
+# Documentation (including examples, which are the most useful):
+# https://coccinelle.gitlabpages.inria.fr/website/docs/
+#
+# Run the "make cocci" target to just output what would be done, or "make cocci-inplace"
+# to apply the changes to the source tree.
+#
+# COCCI_FILES may be set on the command line, if you want to test just a single file
+# while it's under development. Otherwise, it is a list of all the files that are ready
+# to be run.
+#
+# ref-passed-variables-inited.cocci seems to be returning some false positives around
+# GHashTableIters, so it is disabled for the moment.
+COCCI_FILES ?= coccinelle/string-any-of.cocci \
+ coccinelle/string-empty.cocci \
+ coccinelle/string-null-matches.cocci \
+ coccinelle/use-func.cocci
+
+
+dist_noinst_SCRIPTS = coccinelle/test/testrunner.sh
+EXTRA_DIST = README gdbhelpers $(COCCI_FILES) \
+ coccinelle/ref-passed-variables-inited.cocci \
+ coccinelle/rename-fn.cocci \
+ coccinelle/test/ref-passed-variables-inited.input.c \
+ coccinelle/test/ref-passed-variables-inited.output
+
+# Any file in this list is allowed to use any of the pcmk__ internal functions.
+# Coccinelle can use any transformation that depends on "internal" to rewrite
+# code to use the internal functions.
+MAY_USE_INTERNAL_FILES = $(shell find .. -path "../lib/*.c" -o -path "../lib/*private.h" -o -path "../tools/*.c" -o -path "../daemons/*.c" -o -path '../include/pcmki/*h' -o -name '*internal.h')
+
+# And then any file in this list is public API, which may not use internal
+# functions. Thus, only those transformations that do not depend on "internal"
+# may be applied.
+OTHER_FILES = $(shell find ../include -name '*h' -a \! -name '*internal.h' -a \! -path '../include/pcmki/*')
+
+cocci:
+ -for cf in $(COCCI_FILES); do \
+ for f in $(MAY_USE_INTERNAL_FILES); do \
+ spatch $(_SPATCH_FLAGS) -D internal --very-quiet --local-includes --preprocess --sp-file $$cf $$f; \
+ done ; \
+ for f in $(OTHER_FILES); do \
+ spatch $(_SPATCH_FLAGS) --very-quiet --local-includes --preprocess --sp-file $$cf $$f; \
+ done ; \
+ done
+
+cocci-inplace:
+ $(MAKE) $(AM_MAKEFLAGS) _SPATCH_FLAGS=--in-place cocci
+
+cocci-test:
+ for f in coccinelle/test/*.c; do \
+ coccinelle/test/testrunner.sh $$f; \
+ done
+
+#
+# Static analysis
+#
+
+## clang
+
+# See scan-build(1) for possible checkers (leave empty to use default set)
+CLANG_checkers ?=
+
+clang:
+ OUT=$$(cd $(top_builddir) \
+ && scan-build $(CLANG_checkers:%=-enable-checker %) \
+ $(MAKE) $(AM_MAKEFLAGS) CFLAGS="-std=c99 $(CFLAGS)" \
+ clean all 2>&1); \
+ REPORT=$$(echo "$$OUT" \
+ | sed -n -e "s/.*'scan-view \(.*\)'.*/\1/p"); \
+ [ -z "$$REPORT" ] && echo "$$OUT" || scan-view "$$REPORT"
+
+## coverity
+
+# Aggressiveness (low, medium, or high)
+COVLEVEL ?= low
+
+# Generated outputs
+COVERITY_DIR = $(abs_top_builddir)/coverity-$(TAG)
+COVTAR = $(abs_top_builddir)/$(PACKAGE)-coverity-$(TAG).tgz
+COVEMACS = $(abs_top_builddir)/$(TAG).coverity
+COVHTML = $(COVERITY_DIR)/output/errors
+
+# Coverity outputs are phony so they get rebuilt every invocation
+
+.PHONY: $(COVERITY_DIR)
+$(COVERITY_DIR): coverity-clean
+ $(MAKE) $(AM_MAKEFLAGS) -C $(top_builddir) init core-clean
+ $(AM_V_GEN)cd $(top_builddir) \
+ && cov-build --dir "$@" $(MAKE) $(AM_MAKEFLAGS) core
+
+# Public coverity instance
+
+.PHONY: $(COVTAR)
+$(COVTAR): $(COVERITY_DIR)
+ $(AM_V_GEN)tar czf "$@" --transform="s@.*$(TAG)@cov-int@" "$<"
+
+.PHONY: coverity
+coverity: $(COVTAR)
+ @echo "Now go to https://scan.coverity.com/users/sign_in and upload:"
+ @echo " $(COVTAR)"
+ @echo "then make clean at the top level"
+
+# Licensed coverity instance
+#
+# The prerequisites are a little hacky; rather than actually required, some
+# of them are designed so that things execute in the proper order (which is
+# not the same as GNU make's order-only prerequisites).
+
+.PHONY: coverity-analyze
+coverity-analyze: $(COVERITY_DIR)
+ @echo ""
+ @echo "Analyzing (waiting for coverity license if necessary) ..."
+ cd $(top_builddir) && cov-analyze --dir "$<" --wait-for-license \
+ --security --aggressiveness-level "$(COVLEVEL)"
+
+.PHONY: $(COVEMACS)
+$(COVEMACS): coverity-analyze
+ $(AM_V_GEN)cd $(top_builddir) \
+ && cov-format-errors --dir "$(COVERITY_DIR)" --emacs-style > "$@"
+
+.PHONY: $(COVHTML)
+$(COVHTML): $(COVEMACS)
+ $(AM_V_GEN)cd $(top_builddir) \
+ && cov-format-errors --dir "$(COVERITY_DIR)" --html-output "$@"
+
+.PHONY: coverity-corp
+coverity-corp: $(COVHTML)
+ $(MAKE) $(AM_MAKEFLAGS) -C $(top_builddir) core-clean
+ @echo "Done. See:"
+ @echo " file://$(COVHTML)/index.html"
+ @echo "When no longer needed, make coverity-clean"
+
+# Remove all outputs regardless of tag
+.PHONY: coverity-clean
+coverity-clean:
+ -rm -rf "$(abs_builddir)"/coverity-* \
+ "$(abs_builddir)"/$(PACKAGE)-coverity-*.tgz \
+ "$(abs_builddir)"/*.coverity
+
+
+## cppcheck
+
+# Use CPPCHECK_ARGS to pass extra cppcheck options, e.g.:
+# --enable={warning,style,performance,portability,information,all}
+# --inconclusive --std=posix
+# -DBUILD_PUBLIC_LIBPACEMAKER -DDEFAULT_CONCURRENT_FENCING_TRUE
+CPPCHECK_ARGS ?=
+
+CPPCHECK_DIRS = replace lib daemons tools
+CPPCHECK_OUT = $(abs_top_builddir)/cppcheck.out
+
+cppcheck:
+ cppcheck $(CPPCHECK_ARGS) -I $(top_srcdir)/include \
+ --output-file=$(CPPCHECK_OUT) \
+ --max-configs=30 --inline-suppr -q \
+ --library=posix --library=gnu --library=gtk \
+ $(GLIB_CFLAGS) -D__GNUC__ \
+ $(foreach dir,$(CPPCHECK_DIRS),$(top_srcdir)/$(dir))
+ @echo "Done: See $(CPPCHECK_OUT)"
+ @echo "When no longer needed, make cppcheck-clean"
+
+.PHONY: cppcheck-clean
+cppcheck-clean:
+ -rm -f "$(CPPCHECK_OUT)"
+
+#
+# Coverage/profiling
+#
+
+COVERAGE_DIR = $(top_builddir)/coverage
+
+# Check coverage of unit tests
+.PHONY: coverage
+coverage: coverage-partial-clean
+ cd $(top_builddir) \
+ && $(MAKE) $(AM_MAKEFLAGS) core \
+ && lcov --no-external --exclude='*_test.c' -c -i -d . \
+ -o pacemaker_base.info \
+ && $(MAKE) $(AM_MAKEFLAGS) check \
+ && lcov --no-external --exclude='*_test.c' -c -d . \
+ -o pacemaker_test.info \
+ && lcov -a pacemaker_base.info -a pacemaker_test.info \
+ -o pacemaker_total.info
+ genhtml $(top_builddir)/pacemaker_total.info -o $(COVERAGE_DIR) -s
+
+# Check coverage of CLI regression tests
+.PHONY: coverage-cts
+coverage-cts: coverage-partial-clean
+ cd $(top_builddir) \
+ && $(MAKE) $(AM_MAKEFLAGS) core \
+ && lcov --no-external -c -i -d tools -o pacemaker_base.info \
+ && cts/cts-cli \
+ && lcov --no-external -c -d tools -o pacemaker_test.info \
+ && lcov -a pacemaker_base.info -a pacemaker_test.info \
+ -o pacemaker_total.info
+ genhtml $(top_builddir)/pacemaker_total.info -o $(COVERAGE_DIR) -s
+
+# Remove coverage-related files that aren't needed across runs
+.PHONY: coverage-partial-clean
+coverage-partial-clean:
+ -rm -f $(top_builddir)/pacemaker_*.info
+ -rm -rf $(COVERAGE_DIR)
+ -find $(top_builddir) -name "*.gcda" -exec rm -f \{\} \;
+
+# This target removes all coverage-related files. It is only to be run when
+# done with coverage analysis and you are ready to go back to normal development,
+# starting with re-running ./configure. It is not to be run in between
+# "make coverage" runs.
+#
+# In particular, the *.gcno files are generated when the source is built.
+# Removing those files will break "make coverage" until the whole source tree
+# has been built and the *.gcno files generated again.
+.PHONY: coverage-clean
+coverage-clean: coverage-partial-clean
+ -find $(top_builddir) -name "*.gcno" -exec rm -f \{\} \;
+
+#
+# indent cannot cope with all our exceptions and needs heavy manual editing
+#
+
+# indent target: Limit indent to these directories
+INDENT_DIRS ?= .
+
+# indent target: Extra options to pass to indent
+INDENT_OPTS ?=
+
+INDENT_IGNORE_PATHS = daemons/controld/controld_fsa.h \
+ lib/gnu/*
+INDENT_PACEMAKER_STYLE = --blank-lines-after-declarations \
+ --blank-lines-after-procedures \
+ --braces-after-func-def-line \
+ --braces-on-if-line \
+ --braces-on-struct-decl-line \
+ --break-before-boolean-operator \
+ --case-brace-indentation4 \
+ --case-indentation4 \
+ --comment-indentation0 \
+ --continuation-indentation4 \
+ --continue-at-parentheses \
+ --cuddle-do-while \
+ --cuddle-else \
+ --declaration-comment-column0 \
+ --declaration-indentation1 \
+ --else-endif-column0 \
+ --honour-newlines \
+ --indent-label0 \
+ --indent-level4 \
+ --line-comments-indentation0 \
+ --line-length80 \
+ --no-blank-lines-after-commas \
+ --no-comment-delimiters-on-blank-lines \
+ --no-space-after-function-call-names \
+ --no-space-after-parentheses \
+ --no-tabs \
+ --preprocessor-indentation2 \
+ --procnames-start-lines \
+ --space-after-cast \
+ --start-left-side-of-comments \
+ --swallow-optional-blank-lines \
+ --tab-size8
+
+indent:
+ VERSION_CONTROL=none \
+ find $(INDENT_DIRS) -type f -name "*.[ch]" \
+ $(INDENT_IGNORE_PATHS:%= ! -path '%') \
+ -exec indent $(INDENT_PACEMAKER_STYLE) $(INDENT_OPTS) \{\} \;
+
+#
+# Scratch file for ad-hoc testing
+#
+
+EXTRA_PROGRAMS = scratch
+nodist_scratch_SOURCES = scratch.c
+scratch_LDADD = $(top_builddir)/lib/common/libcrmcommon.la
+
+clean-local: coverage-clean coverity-clean cppcheck-clean
+ -rm -f $(EXTRA_PROGRAMS)
diff --git a/devel/README b/devel/README
new file mode 100644
index 0000000..526df19
--- /dev/null
+++ b/devel/README
@@ -0,0 +1,2 @@
+This directory contains helpers for the project developers.
+Everyone else can safely ignore it.
diff --git a/devel/coccinelle/ref-passed-variables-inited.cocci b/devel/coccinelle/ref-passed-variables-inited.cocci
new file mode 100644
index 0000000..0d19ff7
--- /dev/null
+++ b/devel/coccinelle/ref-passed-variables-inited.cocci
@@ -0,0 +1,50 @@
+/*
+ * Copyright 2019-2020 the Pacemaker project contributors
+ *
+ * The version control history for this file may have further details.
+ *
+ * This source code is licensed under the GNU General Public License version 2
+ * or later (GPLv2+) WITHOUT ANY WARRANTY.
+ *
+ *
+ * We require each local variable that
+ *
+ * - is passed to a function through a dereference (suggesting it serves
+ * possibly also or merely as one of the output value propagators seperate
+ * from actual return value if employed at all) and
+ *
+ * - is then subsequently reused (possibly naively expecting it will always
+ * have been initialized (in said function at latest) further in its scope,
+ *
+ * to _always_ be assuredly initialized to some determined value, so as to
+ * prevent a risk of accidentally accessing unspecified value subsequent
+ * to the return from the considered function, which might not have set
+ * that variable at all, lest it would touch it at all.
+ */
+
+virtual internal
+
+@ref_passed_variables_inited exists@
+identifier f_init, f_consume, var;
+type T;
+expression E, E_propagate;
+@@
+
+ T
+- var
++ var /*FIXME:initialize me*/
+ ;
+ ... when != var = E
+ f_init(..., &var, ...)
+ ... when != var = E
+(
+ return var;
+|
+ f_consume(..., var, ...)
+|
+ E_propagate = var
+|
+ &var
+|
+ *var
+)
diff --git a/devel/coccinelle/rename-fn.cocci b/devel/coccinelle/rename-fn.cocci
new file mode 100644
index 0000000..5bce9b7
--- /dev/null
+++ b/devel/coccinelle/rename-fn.cocci
@@ -0,0 +1,23 @@
+/*
+ * Copyright 2021 the Pacemaker project contributors
+ *
+ * The version control history for this file may have further details.
+ *
+ * This source code is licensed under the GNU General Public License version 2
+ * or later (GPLv2+) WITHOUT ANY WARRANTY.
+ */
+
+/*
+ * Rename a function or macro. This is here as a template; replace "old" and
+ * "new" below with the old and new names, run in the devel directory:
+ *
+ * make COCCI_FILES=coccinelle/rename-fn.cocci cocci-inplace
+ *
+ * then revert the file before committing.
+ */
+
+virtual internal
+
+@@ expression E; @@
+- old(E)
++ new(E)
diff --git a/devel/coccinelle/rename-var.cocci b/devel/coccinelle/rename-var.cocci
new file mode 100644
index 0000000..65bba80
--- /dev/null
+++ b/devel/coccinelle/rename-var.cocci
@@ -0,0 +1,29 @@
+/*
+ * Copyright 2021 the Pacemaker project contributors
+ *
+ * The version control history for this file may have further details.
+ *
+ * This source code is licensed under the GNU General Public License version 2
+ * or later (GPLv2+) WITHOUT ANY WARRANTY.
+ */
+
+/*
+ * Rename a variable. This is here as a template; replace "old" and
+ * "new" below with the old and new names. Here is an example:
+ *
+ * @@ @@
+ * - xml_private_flags
+ * + pcmk__xml_flags
+ *
+ * Run in the devel directory:
+ *
+ * make COCCI_FILES=coccinelle/rename-var.cocci cocci-inplace
+ *
+ * then revert the file before committing.
+ */
+
+virtual internal
+
+@@ @@
+- old
++ new
diff --git a/devel/coccinelle/string-any-of.cocci b/devel/coccinelle/string-any-of.cocci
new file mode 100644
index 0000000..ef1a2e9
--- /dev/null
+++ b/devel/coccinelle/string-any-of.cocci
@@ -0,0 +1,76 @@
+/*
+ * Copyright 2020 the Pacemaker project contributors
+ *
+ * The version control history for this file may have further details.
+ *
+ * This source code is licensed under the GNU General Public License version 2
+ * or later (GPLv2+) WITHOUT ANY WARRANTY.
+ *
+ * Catch string comparisons where the pcmk__str_any_of function could be used
+ * instead. Note that we are only catching uses involving identifiers (not
+ * expressions), but I think this is probably fine - we are likely not using
+ * the same expression multiple times in a single line of code. If some are
+ * found, it's easy enough to add another block here.
+ */
+
+virtual internal
+
+@ any_of_1 depends on internal @
+expression test_str, str, new_str;
+identifier I =~ "pcmk__str_none";
+@@
+- pcmk__str_eq(test_str, str, I) || pcmk__str_eq(test_str, new_str, I)
++ pcmk__str_any_of(test_str, str, new_str, NULL)
+
+@ any_of_2 depends on internal @
+expression test_str, str, new_str;
+identifier I =~ "pcmk__str_casei";
+@@
+- pcmk__str_eq(test_str, str, I) || pcmk__str_eq(test_str, new_str, I)
++ pcmk__strcase_any_of(test_str, str, new_str, NULL)
+
+@ any_of_3 depends on internal @
+expression test_str, new_str;
+expression list strs;
+identifier I =~ "pcmk__str_none";
+@@
+- pcmk__str_any_of(test_str, strs, NULL) || pcmk__str_eq(test_str, new_str, I)
++ pcmk__str_any_of(test_str, strs, new_str, NULL)
+
+@ any_of_4 depends on internal @
+expression test_str, new_str;
+expression list strs;
+identifier I =~ "pcmk__str_casei";
+@@
+- pcmk__strcase_any_of(test_str, strs, NULL) || pcmk__str_eq(test_str, new_str, I)
++ pcmk__strcase_any_of(test_str, strs, new_str, NULL)
+
+@ none_of_1 depends on internal @
+expression test_str, str, new_str;
+identifier I =~ "pcmk__str_none";
+@@
+- !pcmk__str_eq(test_str, str, I) && !pcmk__str_eq(test_str, new_str, I)
++ !pcmk__str_any_of(test_str, str, new_str, NULL)
+
+@ none_of_2 depends on internal @
+expression test_str, str, new_str;
+identifier I =~ "pcmk__str_casei";
+@@
+- !pcmk__str_eq(test_str, str, I) && !pcmk__str_eq(test_str, new_str, I)
++ !pcmk__strcase_any_of(test_str, str, new_str, NULL)
+
+@ none_of_3 depends on internal @
+expression test_str, new_str;
+expression list strs;
+identifier I =~ "pcmk__str_none";
+@@
+- !pcmk__str_any_of(test_str, strs, NULL) && !pcmk__str_eq(test_str, new_str, I)
++ !pcmk__str_any_of(test_str, strs, new_str, NULL)
+
+@ none_of_4 depends on internal @
+expression test_str, new_str;
+expression list strs;
+identifier I =~ "pcmk__str_casei";
+@@
+- !pcmk__strcase_any_of(test_str, strs, NULL) && !pcmk__str_eq(test_str, new_str, I)
++ !pcmk__strcase_any_of(test_str, strs, new_str, NULL)
diff --git a/devel/coccinelle/string-empty.cocci b/devel/coccinelle/string-empty.cocci
new file mode 100644
index 0000000..16e6b17
--- /dev/null
+++ b/devel/coccinelle/string-empty.cocci
@@ -0,0 +1,45 @@
+/*
+ * Copyright 2020 the Pacemaker project contributors
+ *
+ * The version control history for this file may have further details.
+ *
+ * This source code is licensed under the GNU General Public License version 2
+ * or later (GPLv2+) WITHOUT ANY WARRANTY.
+ *
+ * Catch string comparisons where the pcmk__str_empty function could be used
+ * instead. Note that we are only catching uses involving identifiers (not
+ * expressions), but I think this is probably fine - we are likely not using
+ * the same expression multiple times in a single line of code. If some are
+ * found, it's easy enough to add another block here.
+ */
+
+virtual internal
+
+@ string_empty depends on internal @
+type t;
+identifier func !~ "pcmk__str_empty";
+char* I;
+@@
+t func(...) {
+...
+(
+- (I == NULL) || (strlen(I) == 0)
++ pcmk__str_empty(I)
+|
+- (I == NULL) || !strlen(I)
++ pcmk__str_empty(I)
+|
+- (I == NULL) || (I[0] == 0)
++ pcmk__str_empty(I)
+|
+- (I == NULL) || (*I == 0)
++ pcmk__str_empty(I)
+|
+- (I == NULL) || (I[0] == '\0')
++ pcmk__str_empty(I)
+|
+- (I == NULL) || (*I == '\0')
++ pcmk__str_empty(I)
+)
+...
+}
diff --git a/devel/coccinelle/string-null-matches.cocci b/devel/coccinelle/string-null-matches.cocci
new file mode 100644
index 0000000..b5471f6
--- /dev/null
+++ b/devel/coccinelle/string-null-matches.cocci
@@ -0,0 +1,74 @@
+/*
+ * Copyright 2020 the Pacemaker project contributors
+ *
+ * The version control history for this file may have further details.
+ *
+ * This source code is licensed under the GNU General Public License version 2
+ * or later (GPLv2+) WITHOUT ANY WARRANTY.
+ *
+ * Catch places where a string can either be NULL or can match some other
+ * string. In these cases, passing the right flag to pcmk__str_eq will get
+ * the same result but without having to do the NULL comparison manually.
+ */
+
+virtual internal
+
+@ string_null_matches_1 depends on internal @
+expression E1, E2;
+@@
+- ((E1 == NULL) || crm_str_eq(E1, E2, TRUE))
++ pcmk__str_eq(E1, E2, pcmk__str_null_matches)
+
+@ string_null_matches_2 depends on internal @
+expression E1, E2;
+@@
+- ((E1 == NULL) || crm_str_eq(E2, E1, TRUE))
++ pcmk__str_eq(E1, E2, pcmk__str_null_matches)
+
+@ string_null_matches_3 depends on internal @
+expression E1, E2;
+@@
+- ((E1 == NULL) || safe_str_eq(E1, E2))
++ pcmk__str_eq(E1, E2, pcmk__str_null_matches|pcmk__str_casei)
+
+@ string_null_matches_4 depends on internal @
+expression E1, E2;
+@@
+- ((E1 == NULL) || safe_str_eq(E2, E1))
++ pcmk__str_eq(E1, E2, pcmk__str_null_matches|pcmk__str_casei)
+
+@ string_null_matches_5 depends on internal @
+expression E1, E2;
+@@
+- ((E1 == NULL) || strcmp(E1, E2) == 0)
++ pcmk__str_eq(E1, E2, pcmk__str_null_matches)
+
+@ string_null_matches_6 depends on internal @
+expression E1, E2;
+@@
+- ((E1 == NULL) || strcmp(E2, E1) == 0)
++ pcmk__str_eq(E1, E2, pcmk__str_null_matches)
+
+@ string_null_matches_7 depends on internal @
+expression E1, E2;
+@@
+- ((E1 == NULL) || !strcmp(E1, E2))
++ pcmk__str_eq(E1, E2, pcmk__str_null_matches)
+
+@ string_null_matches_8 depends on internal @
+expression E1, E2;
+@@
+- ((E1 == NULL) || !strcmp(E2, E1))
++ pcmk__str_eq(E1, E2, pcmk__str_null_matches)
+
+@ string_null_matches_9 depends on internal @
+expression E1, E2;
+@@
+- ((E1 != NULL) && strcmp(E1, E2) != 0)
++ !pcmk__str_eq(E1, E2, pcmk__str_null_matches)
+
+@ string_null_matches_10 depends on internal @
+expression E1, E2;
+@@
+- ((E1 != NULL) && strcmp(E2, E1) != 0)
++ !pcmk__str_eq(E1, E2, pcmk__str_null_matches)
diff --git a/devel/coccinelle/test/ref-passed-variables-inited.input.c b/devel/coccinelle/test/ref-passed-variables-inited.input.c
new file mode 100644
index 0000000..9ddb626
--- /dev/null
+++ b/devel/coccinelle/test/ref-passed-variables-inited.input.c
@@ -0,0 +1,88 @@
+void foo(int *z) {
+ return:
+}
+void baz(int z) {
+ return;
+}
+void zob1(int **z) {
+ return;
+}
+void zob2(int ***z) {
+ return;
+}
+
+void bar0(void) {
+ int i;
+ foo(&i);
+ baz(i);
+}
+
+void bar1(void) {
+ int i;
+ foo(&i);
+ foo(&i);
+}
+
+void bar2(void) {
+ int a = 1, b, c = 3;
+ foo(&a);
+ baz(a);
+ foo(&b);
+ baz(b);
+ foo(&c);
+ baz(c);
+}
+
+void bar3(int *w) {
+ int i;
+ foo(&i);
+ *w = i;
+}
+
+void bar4(int **w) {
+ int i;
+ foo(&i);
+ **w = *i;
+}
+
+void bar5(int ***w) {
+ int *i;
+ zob(&i);
+ ***w = *i;
+}
+
+void bar6(int ***w) {
+ int *i;
+ zob1(&i);
+ ***w = *i;
+}
+
+void bar7(int ****w) {
+ int **i;
+ zob1(&i);
+ ****w = **i;
+}
+
+int bar8(void) {
+ int i;
+ foo(&i);
+ return i;
+}
+
+void not0(void) {
+ int i;
+ foo(&i);
+}
+
+void not1(void) {
+ int i;
+ foo(&i);
+ i = 1;
+}
+
+/* XXX false positive */
+void not2(void) {
+ int i;
+ foo(&i);
+ *(&i) = 2;
+}
diff --git a/devel/coccinelle/test/ref-passed-variables-inited.output b/devel/coccinelle/test/ref-passed-variables-inited.output
new file mode 100644
index 0000000..c7a9f91
--- /dev/null
+++ b/devel/coccinelle/test/ref-passed-variables-inited.output
@@ -0,0 +1,76 @@
+@@ -12,19 +12,19 @@ void zob2(int ***z) {
+ }
+
+ void bar0(void) {
+- int i;
++ int i/*FIXME:initialize me*/;
+ foo(&i);
+ baz(i);
+ }
+
+ void bar1(void) {
+- int i;
++ int i/*FIXME:initialize me*/;
+ foo(&i);
+ foo(&i);
+ }
+
+ void bar2(void) {
+- int a = 1, b, c = 3;
++ int a = 1, b/*FIXME:initialize me*/, c = 3;
+ foo(&a);
+ baz(a);
+ foo(&b);
+@@ -34,37 +34,37 @@ void bar2(void) {
+ }
+
+ void bar3(int *w) {
+- int i;
++ int i/*FIXME:initialize me*/;
+ foo(&i);
+ *w = i;
+ }
+
+ void bar4(int **w) {
+- int i;
++ int i/*FIXME:initialize me*/;
+ foo(&i);
+ **w = *i;
+ }
+
+ void bar5(int ***w) {
+- int *i;
++ int *i/*FIXME:initialize me*/;
+ zob(&i);
+ ***w = *i;
+ }
+
+ void bar6(int ***w) {
+- int *i;
++ int *i/*FIXME:initialize me*/;
+ zob1(&i);
+ ***w = *i;
+ }
+
+ void bar7(int ****w) {
+- int **i;
++ int **i/*FIXME:initialize me*/;
+ zob1(&i);
+ ****w = **i;
+ }
+
+ int bar8(void) {
+- int i;
++ int i/*FIXME:initialize me*/;
+ foo(&i);
+ return i;
+ }
+@@ -82,7 +82,7 @@ void not1(void) {
+
+ /* XXX false positive */
+ void not2(void) {
+- int i;
++ int i/*FIXME:initialize me*/;
+ foo(&i);
+ *(&i) = 2;
+ }
diff --git a/devel/coccinelle/test/testrunner.sh b/devel/coccinelle/test/testrunner.sh
new file mode 100755
index 0000000..07e4795
--- /dev/null
+++ b/devel/coccinelle/test/testrunner.sh
@@ -0,0 +1,13 @@
+#!/bin/sh
+set -eu
+_tmpdir=$(mktemp -d /tmp/coccicheck-XXXXXX)
+_bname=$(basename "$1" .input.c)
+_dname=$(dirname "$1")
+spatch --very-quiet --sp-file "${_dname}/../${_bname}.cocci" "$1" \
+ | tail -n+3 > "${_tmpdir}/out"
+diff -u "${_dname}/${_bname}.output" "${_tmpdir}/out"
+
+if [ -d "${_tmpdir}" ]; then
+ rm "${_tmpdir}"/*
+ rmdir "${_tmpdir}"
+fi
diff --git a/devel/coccinelle/use-func.cocci b/devel/coccinelle/use-func.cocci
new file mode 100644
index 0000000..bfca919
--- /dev/null
+++ b/devel/coccinelle/use-func.cocci
@@ -0,0 +1,22 @@
+/*
+ * Copyright 2020 the Pacemaker project contributors
+ *
+ * The version control history for this file may have further details.
+ *
+ * This source code is licensed under the GNU General Public License version 2
+ * or later (GPLv2+) WITHOUT ANY WARRANTY.
+ */
+
+/*
+ * Always use __func__ (which is in the C99 standard) instead of __FUNCTION__
+ * (which is an older GNU C extension)
+ */
+
+virtual internal
+
+@ use_func @
+@@
+(
+- __FUNCTION__
++ __func__
+)
diff --git a/devel/gdbhelpers b/devel/gdbhelpers
new file mode 100644
index 0000000..319a5f2
--- /dev/null
+++ b/devel/gdbhelpers
@@ -0,0 +1,109 @@
+# Copyright 2018-2020 the Pacemaker project contributors
+#
+# The version control history for this file may have further details.
+#
+# This source code is licensed under the GNU General Public License version 2
+# or later (GPLv2+) WITHOUT ANY WARRANTY.
+
+# notes:
+# "print"/"set" as "compile code" requires re-including headers/multi-line
+
+define pcmk
+ # shelling avoids necessity to have an inferior around
+ shell printf '%s %s\n\n' 'Available commands' \
+ '(all require an inferior, just break on main or so):'
+ shell printf '%s\t%s\n' pcmk-follow-daemon \
+ 'Convenient pacemakerd to particular daemon descent'
+ shell printf '%s\t%s\n' pcmk-xmlnode2file \
+ 'Convenient XML node to (specified/temporary) file dump'
+ shell printf '%s\t%s\n' pcmk-terminate \
+ 'Convenient way to shut pacemaker down as a whole'
+end
+document pcmk
+Show possible pcmk namespace rooted user-defined convenience commands.
+end
+
+
+# XXX one would expect that order of pcmk_children naturally matches
+# the actual sequential ordering, but that's governed with start_seq
+define pcmk-follow-daemon
+ dont-repeat
+ set $d = $arg0
+ eval "set $d_alt = \"pacemaker-%s\"", $d
+ set $cont = 1
+ break start_child
+ cont
+ while ($cont)
+ if (!(int)strcmp(child->name, $d) || !(int)strcmp(child->name, $d_alt))
+ set $cont = 0
+ set follow-fork-mode child
+ break getrlimit
+ continue
+ set follow-fork-mode parent # restore
+ else
+ if (child->start_seq == sizeof(pcmk_children)/sizeof(*pcmk_children) - 1)
+ set $cont = 0
+ printf "no such daemon: %s (%s)\n", $d, $d_alt
+ set follow-fork-mode parent # restore
+ else
+ continue
+ end
+ end
+ end
+end
+document pcmk-follow-daemon
+Convenient way to follow into particular daemon when starting debugging
+from the master control process of pacemaker (pacemakerd).
+For "pacemaker-" prefixed daemons, this very prefix is not needed.
+
+Synopsis: pcmk-follow-daemon CHILD-DAEMON
+Examples: pcmk-follow-daemon "controld"
+end
+
+
+define pcmk-xmlnode2file
+ dont-repeat
+ set $n = $arg0
+ if ($argc > 1)
+ set $fn = $arg1
+ else
+ set $fn = tmpnam((void *) 0)
+ printf "refer to this file: %s\n", $fn
+ end
+ set $f = (FILE *) fopen($fn, "w")
+ eval "print (xmlElemDump((FILE *) %p, ((xmlNodePtr) %p)->doc, (xmlNodePtr) %p), \"%s\")", \
+ $f, $n, $n, "dump done"
+ eval "print (fprintf((FILE *) %p, \"\\n\"), fclose((FILE *) %p), \"%s\")", \
+ $f, $f, "dump finished"
+end
+document pcmk-xmlnode2file
+Convenient way to dump an XML element to specified file, or a temporary file
+when it's not.
+
+Synopsis: pcmk-xmlnode2file XMLNODEPTR-EXPR [FILE-NAME]
+Examples: pcmk-xmlnode2file node "debug-dump.xml"
+ pcmk-xmlnode2file elem
+end
+
+
+define pcmk-terminate
+ dont-repeat
+ eval "set $msg = \"<create_request_adv origin='gdb' %s/>\"", \
+ "t='crmd' subt='request' crm_task='quit' crm_sys_to='pacemakerd'"
+ set $con = crm_ipc_new("pacemakerd", 0)
+ eval "set $con_ok = crm_ipc_connect((crm_ipc_t *) %p)", $con
+ if ($con_ok)
+ eval "print (crm_ipc_send((crm_ipc_t *) %p, (xmlReadMemory(\"%s\", %u, %s))->children, %s), \"%s\")", \
+ $con, $msg, sizeof($msg), "\"gdb.xml\", (void *) 0, 0", "0, 0, (void *) 0", "terminating..."
+ else
+ print "cannot terminate, not running/listening?"
+ end
+ eval "print (crm_ipc_close((crm_ipc_t *) %p), \"connection closed\")", $con
+ eval "print (crm_ipc_destroy((crm_ipc_t *) %p), \"connection destroyed\")", $con
+end
+document pcmk-terminate
+Convenient way to shut pacemaker down as a whole, no matter from
+which process, parent or child, and safely(!).
+
+Synopsis: pcmk-terminate
+end