diff --git a/cts/cts-cli.in b/cts/cts-cli.in
index cff4bbfb70..98eabe95d5 100755
--- a/cts/cts-cli.in
+++ b/cts/cts-cli.in
@@ -1,1134 +1,1136 @@
 #!@BASH_PATH@
 #
-# Copyright 2008-2019 the Pacemaker project contributors
+# Copyright 2008-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.
 #
 
 #
 # Note on portable usage of sed: GNU/POSIX/*BSD sed have a limited subset of
 # compatible functionality. Do not use the -i option, alternation (\|),
 # \0, or character sequences such as \n or \s.
 #
 
 USAGE_TEXT="Usage: cts-cli [<options>]
 Options:
  --help          Display this text, then exit
  -V, --verbose   Display any differences from expected output
  -t 'TEST [...]' Run only specified tests (default: 'dates tools acls validity upgrade rules')
  -p DIR          Look for executables in DIR (may be specified multiple times)
  -v, --valgrind  Run all commands under valgrind
  -s              Save actual output as expected output"
 
 # If readlink supports -e (i.e. GNU), use it
 readlink -e / >/dev/null 2>/dev/null
 if [ $? -eq 0 ]; then
     test_home="$(dirname "$(readlink -e "$0")")"
 else
     test_home="$(dirname "$0")"
 fi
 
 : ${shadow=cts-cli}
 shadow_dir=$(mktemp -d ${TMPDIR:-/tmp}/cts-cli.shadow.XXXXXXXXXX)
 num_errors=0
 num_passed=0
 verbose=0
 tests="dates tools acls validity upgrade rules"
 do_save=0
 VALGRIND_CMD=
 VALGRIND_OPTS="
     -q
     --gen-suppressions=all
     --show-reachable=no
     --leak-check=full
     --trace-children=no
     --time-stamp=yes
     --num-callers=20
     --suppressions=$test_home/valgrind-pcmk.suppressions
 "
 
 # These constants must track crm_exit_t values
 CRM_EX_OK=0
 CRM_EX_ERROR=1
 CRM_EX_INVALID_PARAM=2
 CRM_EX_INSUFFICIENT_PRIV=4
 CRM_EX_USAGE=64
 CRM_EX_CONFIG=78
 CRM_EX_OLD=103
 CRM_EX_DIGEST=104
 CRM_EX_NOSUCH=105
 CRM_EX_UNSAFE=107
 CRM_EX_EXISTS=108
 CRM_EX_MULTIPLE=109
 CRM_EX_EXPIRED=110
 CRM_EX_NOT_YET_IN_EFFECT=111
+CRM_EX_INDETERMINATE=112
+CRM_EX_UNSATISFIED=113
 
 function test_assert() {
     target=$1; shift
     cib=$1; shift
     app=`echo "$cmd" | sed 's/\ .*//'`
     printf "* Running: $app - $desc\n" 1>&2
 
     printf "=#=#=#= Begin test: $desc =#=#=#=\n"
     eval $VALGRIND_CMD $cmd 2>&1
     rc=$?
 
     if [ x$cib != x0 ]; then
         printf "=#=#=#= Current cib after: $desc =#=#=#=\n"
         CIB_user=root cibadmin -Q
     fi
 
     printf "=#=#=#= End test: $desc - $(crm_error --exit $rc) ($rc) =#=#=#=\n"
 
     if [ $rc -ne $target ]; then
         num_errors=$(( $num_errors + 1 ))
         printf "* Failed (rc=%.3d): %-14s - %s\n" $rc $app "$desc"
         printf "* Failed (rc=%.3d): %-14s - %s\n" $rc $app "$desc (`which $app`)" 1>&2
         return
         exit $CRM_EX_ERROR
     else
         printf "* Passed: %-14s - %s\n" $app "$desc"
         num_passed=$(( $num_passed + 1 ))
     fi
 }
 
 function test_tools() {
     local TMPXML
     local TMPORIG
 
     TMPXML=$(mktemp ${TMPDIR:-/tmp}/cts-cli.tools.xml.XXXXXXXXXX)
     TMPORIG=$(mktemp ${TMPDIR:-/tmp}/cts-cli.tools.existing.xml.XXXXXXXXXX)
     export CIB_shadow_dir="${shadow_dir}"
 
     $VALGRIND_CMD crm_shadow --batch --force --create-empty $shadow  2>&1
     export CIB_shadow=$shadow
 
     desc="Validate CIB"
     cmd="cibadmin -Q"
     test_assert $CRM_EX_OK
 
     desc="Configure something before erasing"
     cmd="crm_attribute -n cluster-delay -v 60s"
     test_assert $CRM_EX_OK
 
     desc="Require --force for CIB erasure"
     cmd="cibadmin -E"
     test_assert $CRM_EX_UNSAFE
 
     desc="Allow CIB erasure with --force"
     cmd="cibadmin -E --force"
     test_assert $CRM_EX_OK
 
     desc="Query CIB"
     cmd="cibadmin -Q > $TMPORIG"
     test_assert $CRM_EX_OK
 
     desc="Set cluster option"
     cmd="crm_attribute -n cluster-delay -v 60s"
     test_assert $CRM_EX_OK
 
     desc="Query new cluster option"
     cmd="cibadmin -Q -o crm_config | grep cib-bootstrap-options-cluster-delay"
     test_assert $CRM_EX_OK
 
     desc="Query cluster options"
     cmd="cibadmin -Q -o crm_config > $TMPXML"
     test_assert $CRM_EX_OK
 
     desc="Set no-quorum policy"
     cmd="crm_attribute -n no-quorum-policy -v ignore"
     test_assert $CRM_EX_OK
 
     desc="Delete nvpair"
     cmd="cibadmin -D -o crm_config --xml-text '<nvpair id=\"cib-bootstrap-options-cluster-delay\"/>'"
     test_assert $CRM_EX_OK
 
     desc="Create operation should fail"
     cmd="cibadmin -C -o crm_config --xml-file $TMPXML"
     test_assert $CRM_EX_EXISTS
 
     desc="Modify cluster options section"
     cmd="cibadmin -M -o crm_config --xml-file $TMPXML"
     test_assert $CRM_EX_OK
 
     desc="Query updated cluster option"
     cmd="cibadmin -Q -o crm_config | grep cib-bootstrap-options-cluster-delay"
     test_assert $CRM_EX_OK
 
     desc="Set duplicate cluster option"
     cmd="crm_attribute -n cluster-delay -v 40s -s duplicate"
     test_assert $CRM_EX_OK
 
     desc="Setting multiply defined cluster option should fail"
     cmd="crm_attribute -n cluster-delay -v 30s"
     test_assert $CRM_EX_MULTIPLE
 
     desc="Set cluster option with -s"
     cmd="crm_attribute -n cluster-delay -v 30s -s duplicate"
     test_assert $CRM_EX_OK
 
     desc="Delete cluster option with -i"
     cmd="crm_attribute -n cluster-delay -D -i cib-bootstrap-options-cluster-delay"
     test_assert $CRM_EX_OK
 
     desc="Create node1 and bring it online"
     cmd="crm_simulate --live-check --in-place --node-up=node1"
     test_assert $CRM_EX_OK
 
     desc="Create node attribute"
     cmd="crm_attribute -n ram -v 1024M -N node1 -t nodes"
     test_assert $CRM_EX_OK
 
     desc="Query new node attribute"
     cmd="cibadmin -Q -o nodes | grep node1-ram"
     test_assert $CRM_EX_OK
 
     desc="Set a transient (fail-count) node attribute"
     cmd="crm_attribute -n fail-count-foo -v 3 -N node1 -t status"
     test_assert $CRM_EX_OK
 
     desc="Query a fail count"
     cmd="crm_failcount --query -r foo -N node1"
     test_assert $CRM_EX_OK
 
     desc="Delete a transient (fail-count) node attribute"
     cmd="crm_attribute -n fail-count-foo -D -N node1 -t status"
     test_assert $CRM_EX_OK
 
     desc="Digest calculation"
     cmd="cibadmin -Q | cibadmin -5 -p 2>&1 > /dev/null"
     test_assert $CRM_EX_OK
 
     # This update will fail because it has version numbers
     desc="Replace operation should fail"
     cmd="cibadmin -R --xml-file $TMPORIG"
     test_assert $CRM_EX_OLD
 
     desc="Default standby value"
     cmd="crm_standby -N node1 -G"
     test_assert $CRM_EX_OK
  
     desc="Set standby status"
     cmd="crm_standby -N node1 -v true"
     test_assert $CRM_EX_OK
  
     desc="Query standby value"
     cmd="crm_standby -N node1 -G"
     test_assert $CRM_EX_OK
  
     desc="Delete standby value"
     cmd="crm_standby -N node1 -D"
     test_assert $CRM_EX_OK
 
     desc="Create a resource"
     cmd="cibadmin -C -o resources --xml-text '<primitive id=\"dummy\" class=\"ocf\" provider=\"pacemaker\" type=\"Dummy\"/>'"
     test_assert $CRM_EX_OK
 
     desc="Create a resource meta attribute"
     cmd="crm_resource -r dummy --meta -p is-managed -v false"
     test_assert $CRM_EX_OK
 
     desc="Query a resource meta attribute"
     cmd="crm_resource -r dummy --meta -g is-managed"
     test_assert $CRM_EX_OK
 
     desc="Remove a resource meta attribute"
     cmd="crm_resource -r dummy --meta -d is-managed"
     test_assert $CRM_EX_OK
 
     desc="Create a resource attribute"
     cmd="crm_resource -r dummy -p delay -v 10s"
     test_assert $CRM_EX_OK
 
     desc="List the configured resources"
     cmd="crm_resource -L"
     test_assert $CRM_EX_OK
 
     desc="Require a destination when migrating a resource that is stopped"
     cmd="crm_resource -r dummy -M"
     test_assert $CRM_EX_USAGE
 
     desc="Don't support migration to non-existent locations"
     cmd="crm_resource -r dummy -M -N i.do.not.exist"
     test_assert $CRM_EX_NOSUCH
 
     desc="Create a fencing resource"
     cmd="cibadmin -C -o resources --xml-text '<primitive id=\"Fence\" class=\"stonith\" type=\"fence_true\"/>'"
     test_assert $CRM_EX_OK
 
     desc="Bring resources online"
     cmd="crm_simulate --live-check --in-place -S"
     test_assert $CRM_EX_OK
 
     desc="Try to move a resource to its existing location"
     cmd="crm_resource -r dummy --move --host node1"
     test_assert $CRM_EX_EXISTS
 
     desc="Move a resource from its existing location"
     cmd="crm_resource -r dummy --move"
     test_assert $CRM_EX_OK
 
     desc="Clear out constraints generated by --move"
     cmd="crm_resource -r dummy --clear"
     test_assert $CRM_EX_OK
 
     desc="Default ticket granted state"
     cmd="crm_ticket -t ticketA -G granted -d false"
     test_assert $CRM_EX_OK
 
     desc="Set ticket granted state"
     cmd="crm_ticket -t ticketA -r --force"
     test_assert $CRM_EX_OK
 
     desc="Query ticket granted state"
     cmd="crm_ticket -t ticketA -G granted"
     test_assert $CRM_EX_OK
 
     desc="Delete ticket granted state"
     cmd="crm_ticket -t ticketA -D granted --force"
     test_assert $CRM_EX_OK
 
     desc="Make a ticket standby"
     cmd="crm_ticket -t ticketA -s"
     test_assert $CRM_EX_OK
 
     desc="Query ticket standby state"
     cmd="crm_ticket -t ticketA -G standby"
     test_assert $CRM_EX_OK
 
     desc="Activate a ticket"
     cmd="crm_ticket -t ticketA -a"
     test_assert $CRM_EX_OK
 
     desc="Delete ticket standby state"
     cmd="crm_ticket -t ticketA -D standby"
     test_assert $CRM_EX_OK
 
     desc="Ban a resource on unknown node"
     cmd="crm_resource -r dummy -B -N host1"
     test_assert $CRM_EX_NOSUCH
 
     desc="Create two more nodes and bring them online"
     cmd="crm_simulate --live-check --in-place --node-up=node2 --node-up=node3"
     test_assert $CRM_EX_OK
 
     desc="Ban dummy from node1"
     cmd="crm_resource -r dummy -B -N node1"
     test_assert $CRM_EX_OK
 
     desc="Ban dummy from node2"
     cmd="crm_resource -r dummy -B -N node2"
     test_assert $CRM_EX_OK
 
     desc="Relocate resources due to ban"
     cmd="crm_simulate --live-check --in-place -S"
     test_assert $CRM_EX_OK
 
     desc="Move dummy to node1"
     cmd="crm_resource -r dummy -M -N node1"
     test_assert $CRM_EX_OK
 
     desc="Clear implicit constraints for dummy on node2"
     cmd="crm_resource -r dummy -U -N node2"
     test_assert $CRM_EX_OK
 
     desc="Drop the status section"
     cmd="cibadmin -R -o status --xml-text '<status/>'"
     test_assert $CRM_EX_OK 0
     
     desc="Create a clone"
     cmd="cibadmin -C -o resources --xml-text '<clone id=\"test-clone\"><primitive id=\"test-primitive\" class=\"ocf\" provider=\"pacemaker\" type=\"Dummy\"/></clone>'"
     test_assert $CRM_EX_OK 0
 
     desc="Create a resource meta attribute"
     cmd="crm_resource -r test-primitive --meta -p is-managed -v false"
     test_assert $CRM_EX_OK
 
     desc="Create a resource meta attribute in the primitive"
     cmd="crm_resource -r test-primitive --meta -p is-managed -v false --force"
     test_assert $CRM_EX_OK
 
     desc="Update resource meta attribute with duplicates"
     cmd="crm_resource -r test-clone --meta -p is-managed -v true"
     test_assert $CRM_EX_OK
 
     desc="Update resource meta attribute with duplicates (force clone)"
     cmd="crm_resource -r test-clone --meta -p is-managed -v true --force"
     test_assert $CRM_EX_OK
 
     desc="Update child resource meta attribute with duplicates"
     cmd="crm_resource -r test-primitive --meta -p is-managed -v false"
     test_assert $CRM_EX_OK
 
     desc="Delete resource meta attribute with duplicates"
     cmd="crm_resource -r test-clone --meta -d is-managed"
     test_assert $CRM_EX_OK
 
     desc="Delete resource meta attribute in parent"
     cmd="crm_resource -r test-primitive --meta -d is-managed"
     test_assert $CRM_EX_OK
 
     desc="Create a resource meta attribute in the primitive"
     cmd="crm_resource -r test-primitive --meta -p is-managed -v false --force"
     test_assert $CRM_EX_OK
 
     desc="Update existing resource meta attribute"
     cmd="crm_resource -r test-clone --meta -p is-managed -v true"
     test_assert $CRM_EX_OK
     
     desc="Create a resource meta attribute in the parent"
     cmd="crm_resource -r test-clone --meta -p is-managed -v true --force"
     test_assert $CRM_EX_OK
 
     desc="Copy resources"
     cmd="cibadmin -Q -o resources > $TMPXML"
     test_assert $CRM_EX_OK 0
 
     desc="Delete resource paremt meta attribute (force)"
     cmd="crm_resource -r test-clone --meta -d is-managed --force"
     test_assert $CRM_EX_OK
 
     desc="Restore duplicates"
     cmd="cibadmin -R -o resources --xml-file $TMPXML"
     test_assert $CRM_EX_OK
 
     desc="Delete resource child meta attribute"
     cmd="crm_resource -r test-primitive --meta -d is-managed"
     test_assert $CRM_EX_OK
 
     cibadmin -C -o resources --xml-text '<group id="dummy-group"> \
         <primitive id="dummy1" class="ocf" provider="pacemaker" type="Dummy"\/> \
         <primitive id="dummy2" class="ocf" provider="pacemaker" type="Dummy"\/> \
       </group>'
 
     desc="Create a resource meta attribute in dummy1"
     cmd="crm_resource -r dummy1 --meta -p is-managed -v true"
     test_assert $CRM_EX_OK
 
     desc="Create a resource meta attribute in dummy-group"
     cmd="crm_resource -r dummy-group --meta -p is-managed -v false"
     test_assert $CRM_EX_OK
 
     cibadmin -D -o resource --xml-text '<group id="dummy-group">'
 
     desc="Specify a lifetime when moving a resource"
     cmd="crm_resource -r dummy --move --node node2 --lifetime=PT1H"
     test_assert $CRM_EX_OK
 
     desc="Try to move a resource previously moved with a lifetime"
     cmd="crm_resource -r dummy --move --node node1"
     test_assert $CRM_EX_OK
 
     desc="Ban dummy from node1 for a short time"
     cmd="crm_resource -r dummy -B -N node1 --lifetime=PT1S"
     test_assert $CRM_EX_OK
 
     desc="Remove expired constraints"
     sleep 2
     cmd="crm_resource --clear --expired"
     test_assert $CRM_EX_OK
 
     unset CIB_shadow_dir
     rm -f "$TMPXML" "$TMPORIG"
 
     desc="Create an XML patchset"
     cmd="crm_diff -o $test_home/cli/crm_diff_old.xml -n $test_home/cli/crm_diff_new.xml"
     test_assert $CRM_EX_ERROR 0
 }
 
 INVALID_PERIODS=(
     "2019-01-01 00:00:00Z"              # Start with no end
     "2019-01-01 00:00:00Z/"             # Start with only a trailing slash
     "PT2S/P1M"                          # Two durations
     "2019-13-01 00:00:00Z/P1M"          # Out-of-range month
     "20191077T15/P1M"                   # Out-of-range day
     "2019-10-01T25:00:00Z/P1M"          # Out-of-range hour
     "2019-10-01T24:00:01Z/P1M"          # Hour 24 with anything but :00:00
     "PT5H/20191001T007000Z"             # Out-of-range minute
     "2019-10-01 00:00:80Z/P1M"          # Out-of-range second
     "2019-10-01 00:00:10 +25:00/P1M"    # Out-of-range offset hour
     "20191001T000010 -00:61/P1M"        # Out-of-range offset minute
     "P1Y/2019-02-29 00:00:00Z"          # Feb. 29 in non-leap-year
     "2019-01-01 00:00:00Z/P"            # Duration with no values
     "P1Z/2019-02-20 00:00:00Z"          # Invalid duration unit
     "P1YM/2019-02-20 00:00:00Z"         # No number for duration unit
 )
         
 function test_dates() {
     # Ensure invalid period specifications are rejected
     for spec in '' "${INVALID_PERIODS[@]}"; do
         desc="Invalid period - [$spec]"
         cmd="iso8601 -p \"$spec\""
         test_assert $CRM_EX_INVALID_PARAM 0
     done
 
     desc="2014-01-01 00:30:00 - 1 Hour"
     cmd="iso8601 -d '2014-01-01 00:30:00Z' -D P-1H -E '2013-12-31 23:30:00Z'"
     test_assert $CRM_EX_OK 0
 
     desc="Valid date - Feb 29 in leap year"
     cmd="iso8601 -d '2020-02-29 00:00:00Z' -E '2020-02-29 00:00:00Z'"
     test_assert $CRM_EX_OK 0
 
     desc="Valid date - using 'T' and offset"
     cmd="iso8601 -d '20191201T131211 -05:00' -E '2019-12-01 18:12:11Z'"
     test_assert $CRM_EX_OK 0
 
     desc="24:00:00 equivalent to 00:00:00 of next day"
     cmd="iso8601 -d '2019-12-31 24:00:00Z' -E '2020-01-01 00:00:00Z'"
     test_assert $CRM_EX_OK 0
 
     for y in 06 07 08 09 10 11 12 13 14 15 16 17 18 40; do
         desc="20$y-W01-7"
         cmd="iso8601 -d '20$y-W01-7 00Z'"
         test_assert $CRM_EX_OK 0
 
         desc="20$y-W01-7 - round-trip"
         cmd="iso8601 -d '20$y-W01-7 00Z' -W -E '20$y-W01-7 00:00:00Z'"
         test_assert $CRM_EX_OK 0
 
         desc="20$y-W01-1"
         cmd="iso8601 -d '20$y-W01-1 00Z'"
         test_assert $CRM_EX_OK 0
 
         desc="20$y-W01-1 - round-trip"
         cmd="iso8601 -d '20$y-W01-1 00Z' -W -E '20$y-W01-1 00:00:00Z'"
         test_assert $CRM_EX_OK 0
     done
 
     desc="2009-W53-07"
     cmd="iso8601 -d '2009-W53-7 00:00:00Z' -W -E '2009-W53-7 00:00:00Z'"
     test_assert $CRM_EX_OK 0
 
     desc="epoch + 2 Years 5 Months 6 Minutes"
     cmd="iso8601 -d 'epoch' -D P2Y5MT6M -E '1972-06-01 00:06:00Z'"
     test_assert $CRM_EX_OK 0
 
     desc="2009-01-31 + 1 Month"
     cmd="iso8601 -d '20090131T000000Z' -D P1M -E '2009-02-28 00:00:00Z'"
     test_assert $CRM_EX_OK 0
 
     desc="2009-01-31 + 2 Months"
     cmd="iso8601 -d '2009-01-31 00:00:00Z' -D P2M -E '2009-03-31 00:00:00Z'"
     test_assert $CRM_EX_OK 0
 
     desc="2009-01-31 + 3 Months"
     cmd="iso8601 -d '2009-01-31 00:00:00Z' -D P3M -E '2009-04-30 00:00:00Z'"
     test_assert $CRM_EX_OK 0
 
     desc="2009-03-31 - 1 Month"
     cmd="iso8601 -d '2009-03-31 01:00:00 +01:00' -D P-1M -E '2009-02-28 00:00:00Z'"
     test_assert $CRM_EX_OK 0
 
     desc="2038-01-01 + 3 Months"
     cmd="iso8601 -d '2038-01-01 00:00:00Z' -D P3M -E '2038-04-01 00:00:00Z'"
     test_assert $CRM_EX_OK 0
 }
 
 function test_acl_loop() {
     local TMPXML
 
     TMPXML="$1"
 
     # Make sure we're rejecting things for the right reasons
     export PCMK_trace_functions=pcmk__check_acl,pcmk__apply_creation_acl
     export PCMK_stderr=1
 
     CIB_user=root cibadmin --replace --xml-text '<resources/>'
 
     export CIB_user=unknownguy
     desc="$CIB_user: Query configuration"
     cmd="cibadmin -Q"
     test_assert $CRM_EX_INSUFFICIENT_PRIV 0
 
     desc="$CIB_user: Set enable-acl"
     cmd="crm_attribute -n enable-acl -v false"
     test_assert $CRM_EX_INSUFFICIENT_PRIV 0
 
     desc="$CIB_user: Set stonith-enabled"
     cmd="crm_attribute -n stonith-enabled -v false"
     test_assert $CRM_EX_INSUFFICIENT_PRIV 0
 
     desc="$CIB_user: Create a resource"
     cmd="cibadmin -C -o resources --xml-text '<primitive id=\"dummy\" class=\"ocf\" provider=\"pacemaker\" type=\"Dummy\"/>'"
     test_assert $CRM_EX_INSUFFICIENT_PRIV 0
 
     export CIB_user=l33t-haxor
     desc="$CIB_user: Query configuration"
     cmd="cibadmin -Q"
     test_assert $CRM_EX_INSUFFICIENT_PRIV 0
 
     desc="$CIB_user: Set enable-acl"
     cmd="crm_attribute -n enable-acl -v false"
     test_assert $CRM_EX_INSUFFICIENT_PRIV 0
 
     desc="$CIB_user: Set stonith-enabled"
     cmd="crm_attribute -n stonith-enabled -v false"
     test_assert $CRM_EX_INSUFFICIENT_PRIV 0
 
     desc="$CIB_user: Create a resource"
     cmd="cibadmin -C -o resources --xml-text '<primitive id=\"dummy\" class=\"ocf\" provider=\"pacemaker\" type=\"Dummy\"/>'"
     test_assert $CRM_EX_INSUFFICIENT_PRIV 0
 
     export CIB_user=niceguy
     desc="$CIB_user: Query configuration"
     cmd="cibadmin -Q"
     test_assert $CRM_EX_OK 0
 
     desc="$CIB_user: Set enable-acl"
     cmd="crm_attribute -n enable-acl -v false"
     test_assert $CRM_EX_INSUFFICIENT_PRIV 0
 
     desc="$CIB_user: Set stonith-enabled"
     cmd="crm_attribute -n stonith-enabled -v false"
     test_assert $CRM_EX_OK
 
     desc="$CIB_user: Create a resource"
     cmd="cibadmin -C -o resources --xml-text '<primitive id=\"dummy\" class=\"ocf\" provider=\"pacemaker\" type=\"Dummy\"/>'"
     test_assert $CRM_EX_INSUFFICIENT_PRIV 0
 
     export CIB_user=root
     desc="$CIB_user: Query configuration"
     cmd="cibadmin -Q"
     test_assert $CRM_EX_OK 0
 
     desc="$CIB_user: Set stonith-enabled"
     cmd="crm_attribute -n stonith-enabled -v true"
     test_assert $CRM_EX_OK
 
     desc="$CIB_user: Create a resource"
     cmd="cibadmin -C -o resources --xml-text '<primitive id=\"dummy\" class=\"ocf\" provider=\"pacemaker\" type=\"Dummy\"/>'"
     test_assert $CRM_EX_OK
 
     export CIB_user=l33t-haxor
 
     desc="$CIB_user: Create a resource meta attribute"
     cmd="crm_resource -r dummy --meta -p target-role -v Stopped"
     test_assert $CRM_EX_INSUFFICIENT_PRIV 0
 
     desc="$CIB_user: Query a resource meta attribute"
     cmd="crm_resource -r dummy --meta -g target-role"
     test_assert $CRM_EX_INSUFFICIENT_PRIV 0
 
     desc="$CIB_user: Remove a resource meta attribute"
     cmd="crm_resource -r dummy --meta -d target-role"
     test_assert $CRM_EX_INSUFFICIENT_PRIV 0
 
     export CIB_user=niceguy
 
     desc="$CIB_user: Create a resource meta attribute"
     cmd="crm_resource -r dummy --meta -p target-role -v Stopped"
     test_assert $CRM_EX_OK
 
     desc="$CIB_user: Query a resource meta attribute"
     cmd="crm_resource -r dummy --meta -g target-role"
     test_assert $CRM_EX_OK
 
     desc="$CIB_user: Remove a resource meta attribute"
     cmd="crm_resource -r dummy --meta -d target-role"
     test_assert $CRM_EX_OK
 
     desc="$CIB_user: Create a resource meta attribute"
     cmd="crm_resource -r dummy --meta -p target-role -v Started"
     test_assert $CRM_EX_OK
 
     export CIB_user=badidea
     desc="$CIB_user: Query configuration - implied deny"
     cmd="cibadmin -Q"
     test_assert $CRM_EX_OK 0
 
     export CIB_user=betteridea
     desc="$CIB_user: Query configuration - explicit deny"
     cmd="cibadmin -Q"
     test_assert $CRM_EX_OK 0
 
     CIB_user=root cibadmin -Q > "$TMPXML"
     CIB_user=root CIB_file="$TMPXML" CIB_shadow="" cibadmin --delete --xml-text '<acls/>'
     CIB_user=root CIB_file="$TMPXML" CIB_shadow="" cibadmin -Ql
 
     export CIB_user=niceguy
     desc="$CIB_user: Replace - remove acls"
     cmd="cibadmin --replace --xml-file $TMPXML"
     test_assert $CRM_EX_INSUFFICIENT_PRIV 0
 
     CIB_user=root cibadmin -Q > "$TMPXML"
     CIB_user=root CIB_file="$TMPXML" CIB_shadow="" cibadmin -C -o resources --xml-text '<primitive id="dummy2" class="ocf" provider="pacemaker" type="Dummy"/>'
     CIB_user=root CIB_file="$TMPXML" CIB_shadow="" cibadmin -Ql
 
     desc="$CIB_user: Replace - create resource"
     cmd="cibadmin --replace --xml-file $TMPXML"
     test_assert $CRM_EX_INSUFFICIENT_PRIV 0
 
     CIB_user=root cibadmin -Q > "$TMPXML"
     CIB_user=root CIB_file="$TMPXML" CIB_shadow="" crm_attribute -n enable-acl -v false
     CIB_user=root CIB_file="$TMPXML" CIB_shadow="" cibadmin -Ql
 
     desc="$CIB_user: Replace - modify attribute (deny)"
     cmd="cibadmin --replace --xml-file $TMPXML"
     test_assert $CRM_EX_INSUFFICIENT_PRIV 0
 
     CIB_user=root cibadmin -Q > "$TMPXML"
     CIB_user=root CIB_file="$TMPXML" CIB_shadow="" cibadmin --replace --xml-text '<nvpair id="cib-bootstrap-options-enable-acl" name="enable-acl"/>'
     CIB_user=root CIB_file="$TMPXML" CIB_shadow="" cibadmin -Ql
 
     desc="$CIB_user: Replace - delete attribute (deny)"
     cmd="cibadmin --replace --xml-file $TMPXML"
     test_assert $CRM_EX_INSUFFICIENT_PRIV 0
 
     CIB_user=root cibadmin -Q > "$TMPXML"
     CIB_user=root CIB_file="$TMPXML" CIB_shadow="" cibadmin --modify --xml-text '<primitive id="dummy" description="nothing interesting"/>'
     CIB_user=root CIB_file="$TMPXML" CIB_shadow="" cibadmin -Ql
 
     desc="$CIB_user: Replace - create attribute (deny)"
     cmd="cibadmin --replace --xml-file $TMPXML"
     test_assert $CRM_EX_INSUFFICIENT_PRIV 0
 
     CIB_user=bob
     CIB_user=root cibadmin -Q > "$TMPXML"
     CIB_user=root CIB_file="$TMPXML" CIB_shadow="" cibadmin --modify --xml-text '<primitive id="dummy" description="nothing interesting"/>'
     CIB_user=root CIB_file="$TMPXML" CIB_shadow="" cibadmin -Ql
 
     desc="$CIB_user: Replace - create attribute (allow)"
     cmd="cibadmin --replace -o resources --xml-file $TMPXML"
     test_assert $CRM_EX_OK 0
 
     CIB_user=root cibadmin -Q > "$TMPXML"
     CIB_user=root CIB_file="$TMPXML" CIB_shadow="" cibadmin --modify --xml-text '<primitive id="dummy" description="something interesting"/>'
     CIB_user=root CIB_file="$TMPXML" CIB_shadow="" cibadmin -Ql
 
     desc="$CIB_user: Replace - modify attribute (allow)"
     cmd="cibadmin --replace -o resources --xml-file $TMPXML"
     test_assert $CRM_EX_OK 0
 
     CIB_user=root cibadmin -Q > "$TMPXML"
     CIB_user=root CIB_file="$TMPXML" CIB_shadow="" cibadmin --replace -o resources --xml-text '<primitive id="dummy" class="ocf" provider="pacemaker" type="Dummy"/>'
     CIB_user=root CIB_file="$TMPXML" CIB_shadow="" cibadmin -Ql
 
     desc="$CIB_user: Replace - delete attribute (allow)"
     cmd="cibadmin --replace -o resources --xml-file $TMPXML"
     test_assert $CRM_EX_OK 0
 }
 
 function test_acls() {
     local SHADOWPATH
     local TMPXML
 
     TMPXML=$(mktemp ${TMPDIR:-/tmp}/cts-cli.acls.xml.XXXXXXXXXX)
     export CIB_shadow_dir="${shadow_dir}"
 
     $VALGRIND_CMD crm_shadow --batch --force --create-empty $shadow --validate-with pacemaker-1.3 2>&1
     export CIB_shadow=$shadow
 
     cat <<EOF > "$TMPXML"
     <acls>
       <acl_user id="l33t-haxor">
         <deny id="crook-nothing" xpath="/cib"/>
       </acl_user>
       <acl_user id="niceguy">
         <role_ref id="observer"/>
       </acl_user>
       <acl_user id="bob">
         <role_ref id="admin"/>
       </acl_user>
       <acl_role id="observer">
         <read id="observer-read-1" xpath="/cib"/>
         <write id="observer-write-1" xpath="//nvpair[@name=&apos;stonith-enabled&apos;]"/>
         <write id="observer-write-2" xpath="//nvpair[@name=&apos;target-role&apos;]"/>
       </acl_role>
       <acl_role id="admin">
         <read id="admin-read-1" xpath="/cib"/>
         <write id="admin-write-1" xpath="//resources"/>
       </acl_role>
     </acls>
 EOF
 
     desc="Configure some ACLs"
     cmd="cibadmin -M -o acls --xml-file $TMPXML"
     test_assert $CRM_EX_OK
 
     desc="Enable ACLs"
     cmd="crm_attribute -n enable-acl -v true"
     test_assert $CRM_EX_OK
 
     desc="Set cluster option"
     cmd="crm_attribute -n no-quorum-policy -v ignore"
     test_assert $CRM_EX_OK
 
     desc="New ACL"
     cmd="cibadmin --create -o acls --xml-text '<acl_user id=\"badidea\"><read id=\"badidea-resources\" xpath=\"//meta_attributes\"/></acl_user>'"
     test_assert $CRM_EX_OK
 
     desc="Another ACL"
     cmd="cibadmin --create -o acls --xml-text '<acl_user id=\"betteridea\"><read id=\"betteridea-resources\" xpath=\"//meta_attributes\"/></acl_user>'"
     test_assert $CRM_EX_OK
 
     desc="Updated ACL"
     cmd="cibadmin --replace -o acls --xml-text '<acl_user id=\"betteridea\"><deny id=\"betteridea-nothing\" xpath=\"/cib\"/><read id=\"betteridea-resources\" xpath=\"//meta_attributes\"/></acl_user>'"
     test_assert $CRM_EX_OK
 
     test_acl_loop "$TMPXML"
 
     printf "\n\n    !#!#!#!#! Upgrading to latest CIB schema and re-testing !#!#!#!#!\n"
     printf "\nUpgrading to latest CIB schema and re-testing\n" 1>&2
 
     export CIB_user=root
     desc="$CIB_user: Upgrade to latest CIB schema"
     cmd="cibadmin --upgrade --force -V"
     test_assert $CRM_EX_OK
 
     SHADOWPATH="$(crm_shadow --file)"
     # sed -i isn't portable :-(
     cp -p "$SHADOWPATH" "${SHADOWPATH}.$$" # to keep permissions
     sed -e 's/epoch=.2/epoch=\"6/g' -e 's/admin_epoch=.1/admin_epoch=\"0/g' \
         "$SHADOWPATH" > "${SHADOWPATH}.$$"
     mv -- "${SHADOWPATH}.$$" "$SHADOWPATH"
 
     test_acl_loop "$TMPXML"
 
     unset CIB_shadow_dir
     rm -f "$TMPXML"
 }
 
 function test_validity() {
     local TMPGOOD
     local TMPBAD
 
     TMPGOOD=$(mktemp ${TMPDIR:-/tmp}/cts-cli.validity.good.xml.XXXXXXXXXX)
     TMPBAD=$(mktemp ${TMPDIR:-/tmp}/cts-cli.validity.bad.xml.XXXXXXXXXX)
     export CIB_shadow_dir="${shadow_dir}"
 
     $VALGRIND_CMD crm_shadow --batch --force --create-empty $shadow --validate-with pacemaker-1.2 2>&1
     export CIB_shadow=$shadow
     export PCMK_trace_functions=apply_upgrade,update_validation,cli_config_update
     export PCMK_stderr=1
 
     cibadmin -C -o resources --xml-text '<primitive id="dummy1" class="ocf" provider="pacemaker" type="Dummy"/>'
     cibadmin -C -o resources --xml-text '<primitive id="dummy2" class="ocf" provider="pacemaker" type="Dummy"/>'
     cibadmin -C -o constraints --xml-text '<rsc_order id="ord_1-2" first="dummy1" first-action="start" then="dummy2"/>'
     cibadmin -Q > "$TMPGOOD"
 
 
     desc="Try to make resulting CIB invalid (enum violation)"
     cmd="cibadmin -M -o constraints --xml-text '<rsc_order id=\"ord_1-2\" first=\"dummy1\" first-action=\"break\" then=\"dummy2\"/>'"
     test_assert $CRM_EX_CONFIG
 
     sed 's|"start"|"break"|' "$TMPGOOD" > "$TMPBAD"
     desc="Run crm_simulate with invalid CIB (enum violation)"
     cmd="crm_simulate -x $TMPBAD -S"
     test_assert $CRM_EX_CONFIG 0
 
 
     desc="Try to make resulting CIB invalid (unrecognized validate-with)"
     cmd="cibadmin -M --xml-text '<cib validate-with=\"pacemaker-9999.0\"/>'"
     test_assert $CRM_EX_CONFIG
 
     sed 's|"pacemaker-1.2"|"pacemaker-9999.0"|' "$TMPGOOD" > "$TMPBAD"
     desc="Run crm_simulate with invalid CIB (unrecognized validate-with)"
     cmd="crm_simulate -x $TMPBAD -S"
     test_assert $CRM_EX_CONFIG 0
 
 
     desc="Try to make resulting CIB invalid, but possibly recoverable (valid with X.Y+1)"
     cmd="cibadmin -C -o configuration --xml-text '<tags/>'"
     test_assert $CRM_EX_CONFIG
 
     sed 's|</configuration>|<tags/></configuration>|' "$TMPGOOD" > "$TMPBAD"
     desc="Run crm_simulate with invalid, but possibly recoverable CIB (valid with X.Y+1)"
     cmd="crm_simulate -x $TMPBAD -S"
     test_assert $CRM_EX_OK 0
 
 
     sed 's|[ 	][ 	]*validate-with="[^"]*"||' "$TMPGOOD" > "$TMPBAD"
     desc="Make resulting CIB valid, although without validate-with attribute"
     cmd="cibadmin -R --xml-file $TMPBAD"
     test_assert $CRM_EX_OK
 
     desc="Run crm_simulate with valid CIB, but without validate-with attribute"
     cmd="crm_simulate -x $TMPBAD -S"
     test_assert $CRM_EX_OK 0
 
 
     # this will just disable validation and accept the config, outputting
     # validation errors
     sed -e 's|[ 	][ 	]*validate-with="[^"]*"||' \
         -e 's|\([ 	][ 	]*epoch="[^"]*\)"|\10"|' -e 's|"start"|"break"|' \
         "$TMPGOOD" > "$TMPBAD"
     desc="Make resulting CIB invalid, and without validate-with attribute"
     cmd="cibadmin -R --xml-file $TMPBAD"
     test_assert $CRM_EX_OK
 
     desc="Run crm_simulate with invalid CIB, also without validate-with attribute"
     cmd="crm_simulate -x $TMPBAD -S"
     test_assert $CRM_EX_OK 0
 
     unset CIB_shadow_dir
     rm -f "$TMPGOOD" "$TMPBAD"
 }
 
 test_upgrade() {
     local TMPXML
 
     TMPXML=$(mktemp ${TMPDIR:-/tmp}/cts-cli.tools.xml.XXXXXXXXXX)
     export CIB_shadow_dir="${shadow_dir}"
 
     $VALGRIND_CMD crm_shadow --batch --force --create-empty $shadow --validate-with pacemaker-2.10 2>&1
     export CIB_shadow=$shadow
 
     desc="Set stonith-enabled=false"
     cmd="crm_attribute -n stonith-enabled -v false"
     test_assert $CRM_EX_OK
 
     cat <<EOF > "$TMPXML"
     <resources>
       <primitive id="mySmartFuse" class="ocf" provider="experiment" type="SmartFuse">
         <operations>
           <op id="mySmartFuse-start" name="start" interval="0" timeout="40s"/>
           <op id="mySmartFuse-monitor-inputpower" name="monitor" interval="30s">
             <instance_attributes id="mySmartFuse-inputpower-instanceparams">
               <nvpair id="mySmartFuse-inputpower-requires" name="requires" value="inputpower"/>
             </instance_attributes>
           </op>
           <op id="mySmartFuse-monitor-outputpower" name="monitor" interval="2s">
             <instance_attributes id="mySmartFuse-outputpower-instanceparams">
               <nvpair id="mySmartFuse-outputpower-requires" name="requires" value="outputpower"/>
             </instance_attributes>
           </op>
         </operations>
         <instance_attributes id="mySmartFuse-params">
           <nvpair id="mySmartFuse-params-ip" name="ip" value="192.0.2.10"/>
         </instance_attributes>
 	<!-- a bit hairy but valid -->
         <instance_attributes id-ref="mySmartFuse-outputpower-instanceparams"/>
       </primitive>
     </resources>
 EOF
 
     desc="Configure the initial resource"
     cmd="cibadmin -M -o resources --xml-file $TMPXML"
     test_assert $CRM_EX_OK
 
     desc="Upgrade to latest CIB schema (trigger 2.10.xsl + the wrapping)"
     cmd="cibadmin --upgrade --force -V -V"
     test_assert $CRM_EX_OK
 
     desc="Query a resource instance attribute (shall survive)"
     cmd="crm_resource -r mySmartFuse -g requires"
     test_assert $CRM_EX_OK
 
     unset CIB_shadow_dir
     rm -f "$TMPXML"
 }
 
 test_rules() {
     local TMPXML
 
     export CIB_shadow_dir="${shadow_dir}"
     $VALGRIND_CMD crm_shadow --batch --force --create-empty $shadow 2>&1
     export CIB_shadow=$shadow
 
     cibadmin -C -o resources   --xml-text '<primitive class="ocf" id="dummy" provider="heartbeat" type="Dummy" />'
     cibadmin -C -o constraints --xml-text '<rsc_location id="no-date-expression" rsc="dummy" score="-INFINITY" node="node01" />'
 
     TMPXML=$(mktemp ${TMPDIR:-/tmp}/cts-cli.tools.xml.XXXXXXXXXX)
     cat <<EOF > "$TMPXML"
 <rsc_location id="cli-prefer-dummy-expired" rsc="dummy">
   <rule id="cli-prefer-rule-dummy-expired" score="INFINITY">
     <date_expression id="cli-prefer-lifetime-end-dummy-expired" operation="lt" end="2019-01-01 12:00:00 -05:00"/>
   </rule>
 </rsc_location>
 EOF
 
     cibadmin -C -o constraints -x "$TMPXML"
     rm -f "$TMPXML"
 
     if [ "$(uname)" == "FreeBSD" ]; then
         tomorrow=$(date -v+1d +"%F %T %z")
     else
         tomorrow=$(date --date=tomorrow +"%F %T %z")
     fi
 
     TMPXML=$(mktemp ${TMPDIR:-/tmp}/cts-cli.tools.xml.XXXXXXXXXX)
     cat <<EOF > "$TMPXML"
 <rsc_location id="cli-prefer-dummy-not-yet" rsc="dummy">
   <rule id="cli-prefer-rule-dummy-not-yet" score="INFINITY">
     <date_expression id="cli-prefer-lifetime-end-dummy-not-yet" operation="gt" start="${tomorrow}"/>
   </rule>
 </rsc_location>
 EOF
 
     cibadmin -C -o constraints -x "$TMPXML"
     rm -f "$TMPXML"
 
     desc="Try to check a rule that doesn't exist"
     cmd="crm_rule -c -r blahblah"
     test_assert $CRM_EX_NOSUCH
 
     desc="Try to check a rule that has no date_expression"
     cmd="crm_rule -c -r no-date-expression"
     test_assert $CRM_EX_NOSUCH
 
     desc="Verify basic rule is expired"
     cmd="crm_rule -c -r cli-prefer-rule-dummy-expired"
     test_assert $CRM_EX_EXPIRED
 
     desc="Verify basic rule worked in the past"
     cmd="crm_rule -c -r cli-prefer-rule-dummy-expired -d 20180101"
     test_assert $CRM_EX_OK
 
     desc="Verify basic rule is not yet in effect"
     cmd="crm_rule -c -r cli-prefer-rule-dummy-not-yet"
     test_assert $CRM_EX_NOT_YET_IN_EFFECT
 
     unset CIB_shadow_dir
 }
 
 # Process command-line arguments
 while [ $# -gt 0 ]; do
     case "$1" in
         -t)
             tests="$2"
             shift 2
             ;;
         -V|--verbose)
             verbose=1
             shift
             ;;
         -v|--valgrind)
             export G_SLICE=always-malloc
             VALGRIND_CMD="valgrind $VALGRIND_OPTS"
             shift
             ;;
         -s)
             do_save=1
             shift
             ;;
         -p)
             export PATH="$2:$PATH"
             shift
             ;;
         --help)
             echo "$USAGE_TEXT"
             exit $CRM_EX_OK
             ;;
         *)
             echo "error: unknown option $1"
             echo
             echo "$USAGE_TEXT"
             exit $CRM_EX_USAGE
             ;;
     esac
 done
 
 for t in $tests; do
     case "$t" in
         dates) ;;
         tools) ;;
         acls) ;;
         validity) ;;
         upgrade) ;;
         rules) ;;
         *)
             echo "error: unknown test $t"
             echo
             echo "$USAGE_TEXT"
             exit $CRM_EX_USAGE
             ;;
     esac
 done
 
 # Check whether we're running from source directory
 SRCDIR=$(dirname $test_home)
 if [ -x "$SRCDIR/tools/crm_simulate" ]; then
     export PATH="$SRCDIR/tools:$PATH"
     echo "Using local binaries from: $SRCDIR/tools"
 
     if [ -x "$SRCDIR/xml" ]; then
         export PCMK_schema_directory="$SRCDIR/xml"
         echo "Using local schemas from: $PCMK_schema_directory"
     fi
 fi
 
 for t in $tests; do
     echo "Testing $t"
     TMPFILE=$(mktemp ${TMPDIR:-/tmp}/cts-cli.$t.XXXXXXXXXX)
     eval TMPFILE_$t="$TMPFILE"
     test_$t > "$TMPFILE"
 
     sed -e 's/cib-last-written.*>/>/'\
         -e 's/ last-run=\"[0-9]*\"//'\
         -e 's/crm_feature_set="[^"]*" //'\
         -e 's/validate-with="[^"]*" //'\
         -e 's/Created new pacemaker-.* configuration/Created new pacemaker configuration/'\
         -e 's/.*\(pcmk__.*\)@.*\.c:[0-9][0-9]*)/\1/g' \
         -e 's/.*\(unpack_.*\)@.*\.c:[0-9][0-9]*)/\1/g' \
         -e 's/.*\(update_validation\)@.*\.c:[0-9][0-9]*)/\1/g' \
         -e 's/.*\(apply_upgrade\)@.*\.c:[0-9][0-9]*)/\1/g' \
         -e 's/ last-rc-change=\"[0-9]*\"//'\
         -e 's|^/tmp/cts-cli\.validity\.bad.xml\.[^:]*:|validity.bad.xml:|'\
         -e 's/^Entity: line [0-9][0-9]*: //'\
         -e 's/\(validation ([0-9][0-9]* of \)[0-9][0-9]*\().*\)/\1X\2/' \
         -e 's/^Migration will take effect until: .*/Migration will take effect until:/' \
         -e 's/ end=\"[0-9][-+: 0-9]*Z*\"/ end=\"\"/' \
         -e 's/ start=\"[0-9][-+: 0-9]*Z*\"/ start=\"\"/' \
         -e 's/^Error checking rule: Device not configured/Error checking rule: No such device or address/' \
         "$TMPFILE" > "${TMPFILE}.$$"
     mv -- "${TMPFILE}.$$" "$TMPFILE"
 
     if [ $do_save -eq 1 ]; then
         cp "$TMPFILE" $test_home/cli/regression.$t.exp
     fi
 done
 
 rm -rf "${shadow_dir}"
     
 failed=0
 
 if [ $verbose -eq 1 ]; then
     echo -e "\n\nResults"
 fi
 for t in $tests; do
     eval TMPFILE="\$TMPFILE_$t"
     if [ $verbose -eq 1 ]; then
         diff -wu $test_home/cli/regression.$t.exp "$TMPFILE"
     else
         diff -w $test_home/cli/regression.$t.exp "$TMPFILE" >/dev/null 2>&1
     fi
     if [ $? -ne 0 ]; then
         failed=1
     fi
 done
 
 echo -e "\n\nSummary"
 for t in $tests; do
     eval TMPFILE="\$TMPFILE_$t"
     grep -e '^\*' "$TMPFILE"
 done
 
 if [ $num_errors -ne 0 ]; then
     echo "$num_errors tests failed; see output in:"
     for t in $tests; do
         eval TMPFILE="\$TMPFILE_$t"
         echo "    $TMPFILE"
     done
     exit $CRM_EX_ERROR
 
 elif [ $failed -eq 1 ]; then
     echo "$num_passed tests passed but output was unexpected; see output in:"
     for t in $tests; do
         eval TMPFILE="\$TMPFILE_$t"
         echo "    $TMPFILE"
     done
     exit $CRM_EX_DIGEST
 
 else
     echo $num_passed tests passed
     for t in $tests; do
         eval TMPFILE="\$TMPFILE_$t"
         rm -f "$TMPFILE"
     done
     crm_shadow --force --delete $shadow >/dev/null 2>&1
     exit $CRM_EX_OK
 fi
diff --git a/include/crm/common/results.h b/include/crm/common/results.h
index 98869e3f3b..23ddb127e8 100644
--- a/include/crm/common/results.h
+++ b/include/crm/common/results.h
@@ -1,231 +1,232 @@
 /*
  * Copyright 2012-2020 the Pacemaker project contributors
  *
  * The version control history for this file may have further details.
  *
  * This source code is licensed under the GNU Lesser General Public License
  * version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY.
  */
 #ifndef CRM_RESULTS__H
 #  define CRM_RESULTS__H
 
 #ifdef __cplusplus
 extern "C" {
 #endif
 
 /*!
  * \file
  * \brief Function and executable result codes
  * \ingroup core
  */
 
 // Lifted from config.h
 /* The _Noreturn keyword of C11.  */
 #ifndef _Noreturn
 # if (defined __cplusplus \
       && ((201103 <= __cplusplus && !(__GNUC__ == 4 && __GNUC_MINOR__ == 7)) \
           || (defined _MSC_VER && 1900 <= _MSC_VER)))
 #  define _Noreturn [[noreturn]]
 # elif ((!defined __cplusplus || defined __clang__) \
         && (201112 <= (defined __STDC_VERSION__ ? __STDC_VERSION__ : 0)  \
             || 4 < __GNUC__ + (7 <= __GNUC_MINOR__)))
    /* _Noreturn works as-is.  */
 # elif 2 < __GNUC__ + (8 <= __GNUC_MINOR__) || 0x5110 <= __SUNPRO_C
 #  define _Noreturn __attribute__ ((__noreturn__))
 # elif 1200 <= (defined _MSC_VER ? _MSC_VER : 0)
 #  define _Noreturn __declspec (noreturn)
 # else
 #  define _Noreturn
 # endif
 #endif
 
 #  define CRM_ASSERT(expr) do {                                              \
         if(__unlikely((expr) == FALSE)) {                                    \
             crm_abort(__FILE__, __FUNCTION__, __LINE__, #expr, TRUE, FALSE); \
             abort(); /* Redundant but it makes static analyzers happy */     \
         }                                                                    \
     } while(0)
 
 /*
  * Function return codes
  *
  * Most Pacemaker API functions return an integer return code. There are two
  * alternative interpretations. The legacy interpration is that the absolute
  * value of the return code is either a system error number or a custom
  * pcmk_err_* number. This is less than ideal because system error numbers are
  * constrained only to the positive int range, so there's the possibility
  * (though not noticed in the wild) that system errors and custom errors could
  * collide. The new intepretation is that negative values are from the pcmk_rc_e
  * enum, and positive values are system error numbers. Both use 0 for success.
  *
  * For system error codes, see:
  * - /usr/include/asm-generic/errno.h
  * - /usr/include/asm-generic/errno-base.h
  */
 
 // Legacy custom return codes for Pacemaker API functions (deprecated)
 #  define pcmk_ok                       0
 #  define PCMK_ERROR_OFFSET             190    /* Replacements on non-linux systems, see include/portability.h */
 #  define PCMK_CUSTOM_OFFSET            200    /* Purely custom codes */
 #  define pcmk_err_generic              201
 #  define pcmk_err_no_quorum            202
 #  define pcmk_err_schema_validation    203
 #  define pcmk_err_transform_failed     204
 #  define pcmk_err_old_data             205
 #  define pcmk_err_diff_failed          206
 #  define pcmk_err_diff_resync          207
 #  define pcmk_err_cib_modified         208
 #  define pcmk_err_cib_backup           209
 #  define pcmk_err_cib_save             210
 #  define pcmk_err_schema_unchanged     211
 #  define pcmk_err_cib_corrupt          212
 #  define pcmk_err_multiple             213
 #  define pcmk_err_node_unknown         214
 #  define pcmk_err_already              215
 /* On HPPA 215 is ENOSYM (Unknown error 215), which hopefully never happens. */
 #ifdef __hppa__
 #  define pcmk_err_bad_nvpair           250	/* 216 is ENOTSOCK */
 #  define pcmk_err_unknown_format       252	/* 217 is EDESTADDRREQ */
 #else
 #  define pcmk_err_bad_nvpair           216
 #  define pcmk_err_unknown_format       217
 #endif
 
 /*!
  * \enum pcmk_rc_e
  * \brief Return codes for Pacemaker API functions
  *
  * Any Pacemaker API function documented as returning a "standard Pacemaker
  * return code" will return pcmk_rc_ok (0) on success, and one of this
  * enumeration's other (negative) values or a (positive) system error number
  * otherwise. The custom codes are at -1001 and lower, so that the caller may
  * use -1 through -1000 for their own custom values if desired. While generally
  * referred to as "errors", nonzero values simply indicate a result, which might
  * or might not be an error depending on the calling context.
  */
 enum pcmk_rc_e {
     /* When adding new values, use consecutively lower numbers, update the array
      * in lib/common/results.c, and test with crm_error.
      */
     pcmk_rc_ipc_pid_only        = -1020,
     pcmk_rc_ipc_unresponsive    = -1019,
     pcmk_rc_ipc_unauthorized    = -1018,
     pcmk_rc_no_quorum           = -1017,
     pcmk_rc_schema_validation   = -1016,
     pcmk_rc_schema_unchanged    = -1015,
     pcmk_rc_transform_failed    = -1014,
     pcmk_rc_old_data            = -1013,
     pcmk_rc_diff_failed         = -1012,
     pcmk_rc_diff_resync         = -1011,
     pcmk_rc_cib_modified        = -1010,
     pcmk_rc_cib_backup          = -1009,
     pcmk_rc_cib_save            = -1008,
     pcmk_rc_cib_corrupt         = -1007,
     pcmk_rc_multiple            = -1006,
     pcmk_rc_node_unknown        = -1005,
     pcmk_rc_already             = -1004,
     pcmk_rc_bad_nvpair          = -1003,
     pcmk_rc_unknown_format      = -1002,
     // Developers: Use a more specific code than pcmk_rc_error whenever possible
     pcmk_rc_error               = -1001,
 
     // Values -1 through -1000 reserved for caller use
 
     pcmk_rc_ok                  =     0
 
     // Positive values reserved for system error numbers
 };
 
 /*
  * Exit status codes
  *
  * We want well-specified (i.e. OS-invariant) exit status codes for our daemons
  * and applications so they can be relied on by callers. (Function return codes
  * and errno's do not make good exit statuses.)
  *
  * The only hard rule is that exit statuses must be between 0 and 255; all else
  * is convention. Universally, 0 is success, and 1 is generic error (excluding
  * OSes we don't support -- for example, OpenVMS considers 1 success!).
  *
  * For init scripts, the LSB gives meaning to 0-7, and sets aside 150-199 for
  * application use. OCF adds 8-9 and 189-199.
  *
  * sysexits.h was an attempt to give additional meanings, but never really
  * caught on. It uses 0 and 64-78.
  *
  * Bash reserves 2 ("incorrect builtin usage") and 126-255 (126 is "command
  * found but not executable", 127 is "command not found", 128 + n is
  * "interrupted by signal n").
  *
  * tldp.org recommends 64-113 for application use.
  *
  * We try to overlap with the above conventions when practical.
  */
 typedef enum crm_exit_e {
     // Common convention
     CRM_EX_OK                   =   0,
     CRM_EX_ERROR                =   1,
 
     // LSB + OCF
     CRM_EX_INVALID_PARAM        =   2,
     CRM_EX_UNIMPLEMENT_FEATURE  =   3,
     CRM_EX_INSUFFICIENT_PRIV    =   4,
     CRM_EX_NOT_INSTALLED        =   5,
     CRM_EX_NOT_CONFIGURED       =   6,
     CRM_EX_NOT_RUNNING          =   7,
 
     // sysexits.h
     CRM_EX_USAGE                =  64, // command line usage error
     CRM_EX_DATAERR              =  65, // user-supplied data incorrect
     CRM_EX_NOINPUT              =  66, // input file not available
     CRM_EX_NOUSER               =  67, // user does not exist
     CRM_EX_NOHOST               =  68, // host unknown
     CRM_EX_UNAVAILABLE          =  69, // needed service unavailable
     CRM_EX_SOFTWARE             =  70, // internal software bug
     CRM_EX_OSERR                =  71, // external (OS/environmental) problem
     CRM_EX_OSFILE               =  72, // system file not usable
     CRM_EX_CANTCREAT            =  73, // file couldn't be created
     CRM_EX_IOERR                =  74, // file I/O error
     CRM_EX_TEMPFAIL             =  75, // try again
     CRM_EX_PROTOCOL             =  76, // protocol violated
     CRM_EX_NOPERM               =  77, // non-file permission issue
     CRM_EX_CONFIG               =  78, // misconfiguration
 
     // Custom
     CRM_EX_FATAL                = 100, // do not respawn
     CRM_EX_PANIC                = 101, // panic the local host
     CRM_EX_DISCONNECT           = 102, // lost connection to something
     CRM_EX_OLD                  = 103, // update older than existing config
     CRM_EX_DIGEST               = 104, // digest comparison failed
     CRM_EX_NOSUCH               = 105, // requested item does not exist
     CRM_EX_QUORUM               = 106, // local partition does not have quorum
     CRM_EX_UNSAFE               = 107, // requires --force or new conditions
     CRM_EX_EXISTS               = 108, // requested item already exists
     CRM_EX_MULTIPLE             = 109, // requested item has multiple matches
     CRM_EX_EXPIRED              = 110, // requested item has expired
     CRM_EX_NOT_YET_IN_EFFECT    = 111, // requested item is not in effect
     CRM_EX_INDETERMINATE        = 112, // could not determine status
+    CRM_EX_UNSATISFIED          = 113, // requested item does not satisfy constraints
 
     // Other
     CRM_EX_TIMEOUT              = 124, // convention from timeout(1)
     CRM_EX_MAX                  = 255, // ensure crm_exit_t can hold this
 } crm_exit_t;
 
 const char *pcmk_rc_name(int rc);
 const char *pcmk_rc_str(int rc);
 crm_exit_t pcmk_rc2exitc(int rc);
 int pcmk_rc2legacy(int rc);
 int pcmk_legacy2rc(int legacy_rc);
 const char *pcmk_strerror(int rc);
 const char *pcmk_errorname(int rc);
 const char *bz2_strerror(int rc);
 crm_exit_t crm_errno2exit(int rc);
 const char *crm_exit_name(crm_exit_t exit_code);
 const char *crm_exit_str(crm_exit_t exit_code);
 _Noreturn crm_exit_t crm_exit(crm_exit_t rc);
 
 #ifdef __cplusplus
 }
 #endif
 
 #endif
diff --git a/lib/common/results.c b/lib/common/results.c
index 6e618555f3..063e0950eb 100644
--- a/lib/common/results.c
+++ b/lib/common/results.c
@@ -1,721 +1,727 @@
 /*
  * Copyright 2004-2020 the Pacemaker project contributors
  *
  * The version control history for this file may have further details.
  *
  * This source code is licensed under the GNU Lesser General Public License
  * version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY.
  */
 
 #include <crm_internal.h>
 
 #ifndef _GNU_SOURCE
 #  define _GNU_SOURCE
 #endif
 
 #include <bzlib.h>
 #include <errno.h>
 #include <stdlib.h>
 #include <string.h>
 #include <qb/qbdefs.h>
 
 #include <crm/common/mainloop.h>
 #include <crm/common/xml.h>
 
 // @COMPAT Legacy function return codes
 
 //! \deprecated Use standard return codes and pcmk_rc_name() instead
 const char *
 pcmk_errorname(int rc)
 {
     rc = abs(rc);
     switch (rc) {
         case pcmk_err_generic: return "pcmk_err_generic";
         case pcmk_err_no_quorum: return "pcmk_err_no_quorum";
         case pcmk_err_schema_validation: return "pcmk_err_schema_validation";
         case pcmk_err_transform_failed: return "pcmk_err_transform_failed";
         case pcmk_err_old_data: return "pcmk_err_old_data";
         case pcmk_err_diff_failed: return "pcmk_err_diff_failed";
         case pcmk_err_diff_resync: return "pcmk_err_diff_resync";
         case pcmk_err_cib_modified: return "pcmk_err_cib_modified";
         case pcmk_err_cib_backup: return "pcmk_err_cib_backup";
         case pcmk_err_cib_save: return "pcmk_err_cib_save";
         case pcmk_err_cib_corrupt: return "pcmk_err_cib_corrupt";
         case pcmk_err_multiple: return "pcmk_err_multiple";
         case pcmk_err_node_unknown: return "pcmk_err_node_unknown";
         case pcmk_err_already: return "pcmk_err_already";
         case pcmk_err_bad_nvpair: return "pcmk_err_bad_nvpair";
         case pcmk_err_unknown_format: return "pcmk_err_unknown_format";
         default: return pcmk_rc_name(rc); // system errno
     }
 }
 
 //! \deprecated Use standard return codes and pcmk_rc_str() instead
 const char *
 pcmk_strerror(int rc)
 {
     if (rc == 0) {
         return "OK";
     }
 
     rc = abs(rc);
 
     // Of course rc > 0 ... unless someone passed INT_MIN as rc
     if ((rc > 0) && (rc < PCMK_ERROR_OFFSET)) {
         return strerror(rc);
     }
 
     switch (rc) {
         case pcmk_err_generic:
             return "Generic Pacemaker error";
         case pcmk_err_no_quorum:
             return "Operation requires quorum";
         case pcmk_err_schema_validation:
             return "Update does not conform to the configured schema";
         case pcmk_err_transform_failed:
             return "Schema transform failed";
         case pcmk_err_old_data:
             return "Update was older than existing configuration";
         case pcmk_err_diff_failed:
             return "Application of an update diff failed";
         case pcmk_err_diff_resync:
             return "Application of an update diff failed, requesting a full refresh";
         case pcmk_err_cib_modified:
             return "The on-disk configuration was manually modified";
         case pcmk_err_cib_backup:
             return "Could not archive the previous configuration";
         case pcmk_err_cib_save:
             return "Could not save the new configuration to disk";
         case pcmk_err_cib_corrupt:
             return "Could not parse on-disk configuration";
         case pcmk_err_multiple:
             return "Resource active on multiple nodes";
         case pcmk_err_node_unknown:
             return "Node not found";
         case pcmk_err_already:
             return "Situation already as requested";
         case pcmk_err_bad_nvpair:
             return "Bad name/value pair given";
         case pcmk_err_schema_unchanged:
             return "Schema is already the latest available";
         case pcmk_err_unknown_format:
             return "Unknown output format";
 
             /* The following cases will only be hit on systems for which they are non-standard */
             /* coverity[dead_error_condition] False positive on non-Linux */
         case ENOTUNIQ:
             return "Name not unique on network";
             /* coverity[dead_error_condition] False positive on non-Linux */
         case ECOMM:
             return "Communication error on send";
             /* coverity[dead_error_condition] False positive on non-Linux */
         case ELIBACC:
             return "Can not access a needed shared library";
             /* coverity[dead_error_condition] False positive on non-Linux */
         case EREMOTEIO:
             return "Remote I/O error";
             /* coverity[dead_error_condition] False positive on non-Linux */
         case EUNATCH:
             return "Protocol driver not attached";
             /* coverity[dead_error_condition] False positive on non-Linux */
         case ENOKEY:
             return "Required key not available";
     }
     crm_err("Unknown error code: %d", rc);
     return "Unknown error";
 }
 
 // Standard Pacemaker API return codes
 
 /* This array is used only for nonzero values of pcmk_rc_e. Its values must be
  * kept in the exact reverse order of the enum value numbering (i.e. add new
  * values to the end of the array).
  */
 static struct pcmk__rc_info {
     const char *name;
     const char *desc;
     int legacy_rc;
 } pcmk__rcs[] = {
     { "pcmk_rc_error",
       "Error",
       -pcmk_err_generic,
     },
     { "pcmk_rc_unknown_format",
       "Unknown output format",
       -pcmk_err_unknown_format,
     },
     { "pcmk_rc_bad_nvpair",
       "Bad name/value pair given",
       -pcmk_err_bad_nvpair,
     },
     { "pcmk_rc_already",
       "Already in requested state",
       -pcmk_err_already,
     },
     { "pcmk_rc_node_unknown",
       "Node not found",
       -pcmk_err_node_unknown,
     },
     { "pcmk_rc_multiple",
       "Resource active on multiple nodes",
       -pcmk_err_multiple,
     },
     { "pcmk_rc_cib_corrupt",
       "Could not parse on-disk configuration",
       -pcmk_err_cib_corrupt,
     },
     { "pcmk_rc_cib_save",
       "Could not save new configuration to disk",
       -pcmk_err_cib_save,
     },
     { "pcmk_rc_cib_backup",
       "Could not archive previous configuration",
       -pcmk_err_cib_backup,
     },
     { "pcmk_rc_cib_modified",
       "On-disk configuration was manually modified",
       -pcmk_err_cib_modified,
     },
     { "pcmk_rc_diff_resync",
       "Application of update diff failed, requesting full refresh",
       -pcmk_err_diff_resync,
     },
     { "pcmk_rc_diff_failed",
       "Application of update diff failed",
       -pcmk_err_diff_failed,
     },
     { "pcmk_rc_old_data",
       "Update was older than existing configuration",
       -pcmk_err_old_data,
     },
     { "pcmk_rc_transform_failed",
       "Schema transform failed",
       -pcmk_err_transform_failed,
     },
     { "pcmk_rc_schema_unchanged",
       "Schema is already the latest available",
       -pcmk_err_schema_unchanged,
     },
     { "pcmk_rc_schema_validation",
       "Update does not conform to the configured schema",
       -pcmk_err_schema_validation,
     },
     { "pcmk_rc_no_quorum",
       "Operation requires quorum",
       -pcmk_err_no_quorum,
     },
     { "pcmk_rc_ipc_pid_only",
       "IPC server process is active but not accepting connections",
       -pcmk_err_generic,
     },
     { "pcmk_rc_ipc_unresponsive",
       "IPC server is unresponsive",
       -pcmk_err_generic,
     },
     { "pcmk_rc_ipc_unauthorized",
       "IPC server is blocked by unauthorized process",
       -pcmk_err_generic,
     },
 };
 
 #define PCMK__N_RC (sizeof(pcmk__rcs) / sizeof(struct pcmk__rc_info))
 
 /*!
  * \brief Get a return code constant name as a string
  *
  * \param[in] rc  Integer return code to convert
  *
  * \return String of constant name corresponding to rc
  */
 const char *
 pcmk_rc_name(int rc)
 {
     if ((rc <= pcmk_rc_error) && ((pcmk_rc_error - rc) < PCMK__N_RC)) {
         return pcmk__rcs[pcmk_rc_error - rc].name;
     }
     switch (rc) {
         case pcmk_rc_ok:        return "pcmk_rc_ok";
         case E2BIG:             return "E2BIG";
         case EACCES:            return "EACCES";
         case EADDRINUSE:        return "EADDRINUSE";
         case EADDRNOTAVAIL:     return "EADDRNOTAVAIL";
         case EAFNOSUPPORT:      return "EAFNOSUPPORT";
         case EAGAIN:            return "EAGAIN";
         case EALREADY:          return "EALREADY";
         case EBADF:             return "EBADF";
         case EBADMSG:           return "EBADMSG";
         case EBUSY:             return "EBUSY";
         case ECANCELED:         return "ECANCELED";
         case ECHILD:            return "ECHILD";
         case ECOMM:             return "ECOMM";
         case ECONNABORTED:      return "ECONNABORTED";
         case ECONNREFUSED:      return "ECONNREFUSED";
         case ECONNRESET:        return "ECONNRESET";
         /* case EDEADLK:        return "EDEADLK"; */
         case EDESTADDRREQ:      return "EDESTADDRREQ";
         case EDOM:              return "EDOM";
         case EDQUOT:            return "EDQUOT";
         case EEXIST:            return "EEXIST";
         case EFAULT:            return "EFAULT";
         case EFBIG:             return "EFBIG";
         case EHOSTDOWN:         return "EHOSTDOWN";
         case EHOSTUNREACH:      return "EHOSTUNREACH";
         case EIDRM:             return "EIDRM";
         case EILSEQ:            return "EILSEQ";
         case EINPROGRESS:       return "EINPROGRESS";
         case EINTR:             return "EINTR";
         case EINVAL:            return "EINVAL";
         case EIO:               return "EIO";
         case EISCONN:           return "EISCONN";
         case EISDIR:            return "EISDIR";
         case ELIBACC:           return "ELIBACC";
         case ELOOP:             return "ELOOP";
         case EMFILE:            return "EMFILE";
         case EMLINK:            return "EMLINK";
         case EMSGSIZE:          return "EMSGSIZE";
 #ifdef EMULTIHOP // Not available on OpenBSD
         case EMULTIHOP:         return "EMULTIHOP";
 #endif
         case ENAMETOOLONG:      return "ENAMETOOLONG";
         case ENETDOWN:          return "ENETDOWN";
         case ENETRESET:         return "ENETRESET";
         case ENETUNREACH:       return "ENETUNREACH";
         case ENFILE:            return "ENFILE";
         case ENOBUFS:           return "ENOBUFS";
         case ENODATA:           return "ENODATA";
         case ENODEV:            return "ENODEV";
         case ENOENT:            return "ENOENT";
         case ENOEXEC:           return "ENOEXEC";
         case ENOKEY:            return "ENOKEY";
         case ENOLCK:            return "ENOLCK";
 #ifdef ENOLINK // Not available on OpenBSD
         case ENOLINK:           return "ENOLINK";
 #endif
         case ENOMEM:            return "ENOMEM";
         case ENOMSG:            return "ENOMSG";
         case ENOPROTOOPT:       return "ENOPROTOOPT";
         case ENOSPC:            return "ENOSPC";
         case ENOSR:             return "ENOSR";
         case ENOSTR:            return "ENOSTR";
         case ENOSYS:            return "ENOSYS";
         case ENOTBLK:           return "ENOTBLK";
         case ENOTCONN:          return "ENOTCONN";
         case ENOTDIR:           return "ENOTDIR";
         case ENOTEMPTY:         return "ENOTEMPTY";
         case ENOTSOCK:          return "ENOTSOCK";
 #if ENOTSUP != EOPNOTSUPP
         case ENOTSUP:           return "ENOTSUP";
 #endif
         case ENOTTY:            return "ENOTTY";
         case ENOTUNIQ:          return "ENOTUNIQ";
         case ENXIO:             return "ENXIO";
         case EOPNOTSUPP:        return "EOPNOTSUPP";
         case EOVERFLOW:         return "EOVERFLOW";
         case EPERM:             return "EPERM";
         case EPFNOSUPPORT:      return "EPFNOSUPPORT";
         case EPIPE:             return "EPIPE";
         case EPROTO:            return "EPROTO";
         case EPROTONOSUPPORT:   return "EPROTONOSUPPORT";
         case EPROTOTYPE:        return "EPROTOTYPE";
         case ERANGE:            return "ERANGE";
         case EREMOTE:           return "EREMOTE";
         case EREMOTEIO:         return "EREMOTEIO";
         case EROFS:             return "EROFS";
         case ESHUTDOWN:         return "ESHUTDOWN";
         case ESPIPE:            return "ESPIPE";
         case ESOCKTNOSUPPORT:   return "ESOCKTNOSUPPORT";
         case ESRCH:             return "ESRCH";
         case ESTALE:            return "ESTALE";
         case ETIME:             return "ETIME";
         case ETIMEDOUT:         return "ETIMEDOUT";
         case ETXTBSY:           return "ETXTBSY";
         case EUNATCH:           return "EUNATCH";
         case EUSERS:            return "EUSERS";
         /* case EWOULDBLOCK:    return "EWOULDBLOCK"; */
         case EXDEV:             return "EXDEV";
 
 #ifdef EBADE // Not available on OS X
         case EBADE:             return "EBADE";
         case EBADFD:            return "EBADFD";
         case EBADSLT:           return "EBADSLT";
         case EDEADLOCK:         return "EDEADLOCK";
         case EBADR:             return "EBADR";
         case EBADRQC:           return "EBADRQC";
         case ECHRNG:            return "ECHRNG";
 #ifdef EISNAM // Not available on OS X, Illumos, Solaris
         case EISNAM:            return "EISNAM";
         case EKEYEXPIRED:       return "EKEYEXPIRED";
         case EKEYREJECTED:      return "EKEYREJECTED";
         case EKEYREVOKED:       return "EKEYREVOKED";
 #endif
         case EL2HLT:            return "EL2HLT";
         case EL2NSYNC:          return "EL2NSYNC";
         case EL3HLT:            return "EL3HLT";
         case EL3RST:            return "EL3RST";
         case ELIBBAD:           return "ELIBBAD";
         case ELIBMAX:           return "ELIBMAX";
         case ELIBSCN:           return "ELIBSCN";
         case ELIBEXEC:          return "ELIBEXEC";
 #ifdef ENOMEDIUM // Not available on OS X, Illumos, Solaris
         case ENOMEDIUM:         return "ENOMEDIUM";
         case EMEDIUMTYPE:       return "EMEDIUMTYPE";
 #endif
         case ENONET:            return "ENONET";
         case ENOPKG:            return "ENOPKG";
         case EREMCHG:           return "EREMCHG";
         case ERESTART:          return "ERESTART";
         case ESTRPIPE:          return "ESTRPIPE";
 #ifdef EUCLEAN // Not available on OS X, Illumos, Solaris
         case EUCLEAN:           return "EUCLEAN";
 #endif
         case EXFULL:            return "EXFULL";
 #endif // EBADE
         default:                return "Unknown";
     }
 }
 
 /*!
  * \brief Get a user-friendly description of a return code
  *
  * \param[in] rc  Integer return code to convert
  *
  * \return String description of rc
  */
 const char *
 pcmk_rc_str(int rc)
 {
     if (rc == pcmk_rc_ok) {
         return "OK";
     }
     if ((rc <= pcmk_rc_error) && ((pcmk_rc_error - rc) < PCMK__N_RC)) {
         return pcmk__rcs[pcmk_rc_error - rc].desc;
     }
     if (rc < 0) {
         return "Unknown error";
     }
     return strerror(rc);
 }
 
 // This returns negative values for errors
 //! \deprecated Use standard return codes instead
 int
 pcmk_rc2legacy(int rc)
 {
     if (rc >= 0) {
         return -rc; // OK or system errno
     }
     if ((rc <= pcmk_rc_error) && ((pcmk_rc_error - rc) < PCMK__N_RC)) {
         return pcmk__rcs[pcmk_rc_error - rc].legacy_rc;
     }
     return -pcmk_err_generic;
 }
 
 //! \deprecated Use standard return codes instead
 int
 pcmk_legacy2rc(int legacy_rc)
 {
     legacy_rc = abs(legacy_rc);
     switch (legacy_rc) {
         case pcmk_err_no_quorum:            return pcmk_rc_no_quorum;
         case pcmk_err_schema_validation:    return pcmk_rc_schema_validation;
         case pcmk_err_schema_unchanged:     return pcmk_rc_schema_unchanged;
         case pcmk_err_transform_failed:     return pcmk_rc_transform_failed;
         case pcmk_err_old_data:             return pcmk_rc_old_data;
         case pcmk_err_diff_failed:          return pcmk_rc_diff_failed;
         case pcmk_err_diff_resync:          return pcmk_rc_diff_resync;
         case pcmk_err_cib_modified:         return pcmk_rc_cib_modified;
         case pcmk_err_cib_backup:           return pcmk_rc_cib_backup;
         case pcmk_err_cib_save:             return pcmk_rc_cib_save;
         case pcmk_err_cib_corrupt:          return pcmk_rc_cib_corrupt;
         case pcmk_err_multiple:             return pcmk_rc_multiple;
         case pcmk_err_node_unknown:         return pcmk_rc_node_unknown;
         case pcmk_err_already:              return pcmk_rc_already;
         case pcmk_err_bad_nvpair:           return pcmk_rc_bad_nvpair;
         case pcmk_err_unknown_format:       return pcmk_rc_unknown_format;
         case pcmk_err_generic:              return pcmk_rc_error;
         case pcmk_ok:                       return pcmk_rc_ok;
         default:                            return legacy_rc; // system errno
     }
 }
 
 // Exit status codes
 
 const char *
 crm_exit_name(crm_exit_t exit_code)
 {
     switch (exit_code) {
         case CRM_EX_OK: return "CRM_EX_OK";
         case CRM_EX_ERROR: return "CRM_EX_ERROR";
         case CRM_EX_INVALID_PARAM: return "CRM_EX_INVALID_PARAM";
         case CRM_EX_UNIMPLEMENT_FEATURE: return "CRM_EX_UNIMPLEMENT_FEATURE";
         case CRM_EX_INSUFFICIENT_PRIV: return "CRM_EX_INSUFFICIENT_PRIV";
         case CRM_EX_NOT_INSTALLED: return "CRM_EX_NOT_INSTALLED";
         case CRM_EX_NOT_CONFIGURED: return "CRM_EX_NOT_CONFIGURED";
         case CRM_EX_NOT_RUNNING: return "CRM_EX_NOT_RUNNING";
         case CRM_EX_USAGE: return "CRM_EX_USAGE";
         case CRM_EX_DATAERR: return "CRM_EX_DATAERR";
         case CRM_EX_NOINPUT: return "CRM_EX_NOINPUT";
         case CRM_EX_NOUSER: return "CRM_EX_NOUSER";
         case CRM_EX_NOHOST: return "CRM_EX_NOHOST";
         case CRM_EX_UNAVAILABLE: return "CRM_EX_UNAVAILABLE";
         case CRM_EX_SOFTWARE: return "CRM_EX_SOFTWARE";
         case CRM_EX_OSERR: return "CRM_EX_OSERR";
         case CRM_EX_OSFILE: return "CRM_EX_OSFILE";
         case CRM_EX_CANTCREAT: return "CRM_EX_CANTCREAT";
         case CRM_EX_IOERR: return "CRM_EX_IOERR";
         case CRM_EX_TEMPFAIL: return "CRM_EX_TEMPFAIL";
         case CRM_EX_PROTOCOL: return "CRM_EX_PROTOCOL";
         case CRM_EX_NOPERM: return "CRM_EX_NOPERM";
         case CRM_EX_CONFIG: return "CRM_EX_CONFIG";
         case CRM_EX_FATAL: return "CRM_EX_FATAL";
         case CRM_EX_PANIC: return "CRM_EX_PANIC";
         case CRM_EX_DISCONNECT: return "CRM_EX_DISCONNECT";
         case CRM_EX_DIGEST: return "CRM_EX_DIGEST";
         case CRM_EX_NOSUCH: return "CRM_EX_NOSUCH";
         case CRM_EX_QUORUM: return "CRM_EX_QUORUM";
         case CRM_EX_UNSAFE: return "CRM_EX_UNSAFE";
         case CRM_EX_EXISTS: return "CRM_EX_EXISTS";
         case CRM_EX_MULTIPLE: return "CRM_EX_MULTIPLE";
         case CRM_EX_EXPIRED: return "CRM_EX_EXPIRED";
         case CRM_EX_NOT_YET_IN_EFFECT: return "CRM_EX_NOT_YET_IN_EFFECT";
         case CRM_EX_INDETERMINATE: return "CRM_EX_INDETERMINATE";
+        case CRM_EX_UNSATISFIED: return "CRM_EX_UNSATISFIED";
         case CRM_EX_OLD: return "CRM_EX_OLD";
         case CRM_EX_TIMEOUT: return "CRM_EX_TIMEOUT";
         case CRM_EX_MAX: return "CRM_EX_UNKNOWN";
     }
     return "CRM_EX_UNKNOWN";
 }
 
 const char *
 crm_exit_str(crm_exit_t exit_code)
 {
     switch (exit_code) {
         case CRM_EX_OK: return "OK";
         case CRM_EX_ERROR: return "Error occurred";
         case CRM_EX_INVALID_PARAM: return "Invalid parameter";
         case CRM_EX_UNIMPLEMENT_FEATURE: return "Unimplemented";
         case CRM_EX_INSUFFICIENT_PRIV: return "Insufficient privileges";
         case CRM_EX_NOT_INSTALLED: return "Not installed";
         case CRM_EX_NOT_CONFIGURED: return "Not configured";
         case CRM_EX_NOT_RUNNING: return "Not running";
         case CRM_EX_USAGE: return "Incorrect usage";
         case CRM_EX_DATAERR: return "Invalid data given";
         case CRM_EX_NOINPUT: return "Input file not available";
         case CRM_EX_NOUSER: return "User does not exist";
         case CRM_EX_NOHOST: return "Host does not exist";
         case CRM_EX_UNAVAILABLE: return "Necessary service unavailable";
         case CRM_EX_SOFTWARE: return "Internal software bug";
         case CRM_EX_OSERR: return "Operating system error occurred";
         case CRM_EX_OSFILE: return "System file not available";
         case CRM_EX_CANTCREAT: return "Cannot create output file";
         case CRM_EX_IOERR: return "I/O error occurred";
         case CRM_EX_TEMPFAIL: return "Temporary failure, try again";
         case CRM_EX_PROTOCOL: return "Protocol violated";
         case CRM_EX_NOPERM: return "Insufficient privileges";
         case CRM_EX_CONFIG: return "Invalid configuration";
         case CRM_EX_FATAL: return "Fatal error occurred, will not respawn";
         case CRM_EX_PANIC: return "System panic required";
         case CRM_EX_DISCONNECT: return "Not connected";
         case CRM_EX_DIGEST: return "Digest mismatch";
         case CRM_EX_NOSUCH: return "No such object";
         case CRM_EX_QUORUM: return "Quorum required";
         case CRM_EX_UNSAFE: return "Operation not safe";
         case CRM_EX_EXISTS: return "Requested item already exists";
         case CRM_EX_MULTIPLE: return "Multiple items match request";
         case CRM_EX_EXPIRED: return "Requested item has expired";
         case CRM_EX_NOT_YET_IN_EFFECT: return "Requested item is not yet in effect";
         case CRM_EX_INDETERMINATE: return "Could not determine status";
+        case CRM_EX_UNSATISFIED: return "Not applicable under current conditions";
         case CRM_EX_OLD: return "Update was older than existing configuration";
         case CRM_EX_TIMEOUT: return "Timeout occurred";
         case CRM_EX_MAX: return "Error occurred";
     }
     if ((exit_code > 128) && (exit_code < CRM_EX_MAX)) {
         return "Interrupted by signal";
     }
     return "Unknown exit status";
 }
 
 //! \deprecated Use standard return codes and pcmk_rc2exitc() instead
 crm_exit_t
 crm_errno2exit(int rc)
 {
     rc = abs(rc); // Convenience for functions that return -errno
     switch (rc) {
         case pcmk_ok:
             return CRM_EX_OK;
 
         case pcmk_err_no_quorum:
             return CRM_EX_QUORUM;
 
         case pcmk_err_old_data:
             return CRM_EX_OLD;
 
         case pcmk_err_schema_validation:
         case pcmk_err_transform_failed:
             return CRM_EX_CONFIG;
 
         case pcmk_err_bad_nvpair:
             return CRM_EX_INVALID_PARAM;
 
         case pcmk_err_already:
             return CRM_EX_EXISTS;
 
         case pcmk_err_multiple:
             return CRM_EX_MULTIPLE;
 
         case pcmk_err_node_unknown:
         case pcmk_err_unknown_format:
             return CRM_EX_NOSUCH;
 
         default:
             return pcmk_rc2exitc(rc); // system errno
     }
 }
 
 /*!
  * \brief Map a function return code to the most similar exit code
  *
  * \param[in] rc  Function return code
  *
  * \return Most similar exit code
  */
 crm_exit_t
 pcmk_rc2exitc(int rc)
 {
     switch (rc) {
         case pcmk_rc_ok:
             return CRM_EX_OK;
 
         case pcmk_rc_no_quorum:
             return CRM_EX_QUORUM;
 
         case pcmk_rc_old_data:
             return CRM_EX_OLD;
 
         case pcmk_rc_schema_validation:
         case pcmk_rc_transform_failed:
             return CRM_EX_CONFIG;
 
         case pcmk_rc_bad_nvpair:
             return CRM_EX_INVALID_PARAM;
 
         case EACCES:
             return CRM_EX_INSUFFICIENT_PRIV;
 
         case EBADF:
         case EINVAL:
         case EFAULT:
         case ENOSYS:
         case EOVERFLOW:
             return CRM_EX_SOFTWARE;
 
         case EBADMSG:
         case EMSGSIZE:
         case ENOMSG:
         case ENOPROTOOPT:
         case EPROTO:
         case EPROTONOSUPPORT:
         case EPROTOTYPE:
             return CRM_EX_PROTOCOL;
 
         case ECOMM:
         case ENOMEM:
             return CRM_EX_OSERR;
 
         case ECONNABORTED:
         case ECONNREFUSED:
         case ECONNRESET:
         case ENOTCONN:
             return CRM_EX_DISCONNECT;
 
         case EEXIST:
         case pcmk_rc_already:
             return CRM_EX_EXISTS;
 
         case EIO:
             return CRM_EX_IOERR;
 
         case ENOTSUP:
 #if EOPNOTSUPP != ENOTSUP
         case EOPNOTSUPP:
 #endif
             return CRM_EX_UNIMPLEMENT_FEATURE;
 
         case ENOTUNIQ:
         case pcmk_rc_multiple:
             return CRM_EX_MULTIPLE;
 
         case ENXIO:
         case pcmk_rc_node_unknown:
         case pcmk_rc_unknown_format:
             return CRM_EX_NOSUCH;
 
         case ETIME:
         case ETIMEDOUT:
             return CRM_EX_TIMEOUT;
 
+        case EAGAIN:
+        case EBUSY:
+            return CRM_EX_UNSATISFIED;
+
         default:
             return CRM_EX_ERROR;
     }
 }
 
 // Other functions
 
 const char *
 bz2_strerror(int rc)
 {
     // See ftp://sources.redhat.com/pub/bzip2/docs/manual_3.html#SEC17
     switch (rc) {
         case BZ_OK:
         case BZ_RUN_OK:
         case BZ_FLUSH_OK:
         case BZ_FINISH_OK:
         case BZ_STREAM_END:
             return "Ok";
         case BZ_CONFIG_ERROR:
             return "libbz2 has been improperly compiled on your platform";
         case BZ_SEQUENCE_ERROR:
             return "library functions called in the wrong order";
         case BZ_PARAM_ERROR:
             return "parameter is out of range or otherwise incorrect";
         case BZ_MEM_ERROR:
             return "memory allocation failed";
         case BZ_DATA_ERROR:
             return "data integrity error is detected during decompression";
         case BZ_DATA_ERROR_MAGIC:
             return "the compressed stream does not start with the correct magic bytes";
         case BZ_IO_ERROR:
             return "error reading or writing in the compressed file";
         case BZ_UNEXPECTED_EOF:
             return "compressed file finishes before the logical end of stream is detected";
         case BZ_OUTBUFF_FULL:
             return "output data will not fit into the buffer provided";
     }
     return "Unknown error";
 }
 
 crm_exit_t
 crm_exit(crm_exit_t rc)
 {
     /* A compiler could theoretically use any type for crm_exit_t, but an int
      * should always hold it, so cast to int to keep static analysis happy.
      */
     if ((((int) rc) < 0) || (((int) rc) > CRM_EX_MAX)) {
         rc = CRM_EX_ERROR;
     }
 
     mainloop_cleanup();
     crm_xml_cleanup();
 
     qb_log_fini();
     pcmk__cli_option_cleanup();
 
     if (crm_system_name) {
         crm_info("Exiting %s " CRM_XS " with status %d", crm_system_name, rc);
         free(crm_system_name);
     } else {
         crm_trace("Exiting with status %d", rc);
     }
 
     exit(rc);
 }