diff --git a/include/crm/msg_xml.h b/include/crm/msg_xml.h
index 11670ac303..851e8eedf4 100644
--- a/include/crm/msg_xml.h
+++ b/include/crm/msg_xml.h
@@ -1,269 +1,270 @@
 /*
  * Copyright 2004-2024 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 PCMK__CRM_MSG_XML__H
 #  define PCMK__CRM_MSG_XML__H
 
 #  include <crm/common/xml.h>
 
 #if !defined(PCMK_ALLOW_DEPRECATED) || (PCMK_ALLOW_DEPRECATED == 1)
 #include <crm/msg_xml_compat.h>
 #endif
 
 #ifdef __cplusplus
 extern "C" {
 #endif
 
 /* This file defines constants for various XML syntax (mainly element and
  * attribute names).
  *
  * For consistency, new constants should start with "PCMK_", followed by:
  * * "XE" for XML element names
  * * "XA" for XML attribute names
  * * "OPT" for cluster option (property) names
  * * "META" for meta-attribute names
  * * "VALUE" for enumerated values for various options
  *
  * Old names that don't follow this policy should eventually be deprecated and
  * replaced with names that do.
  *
  * Symbols should be public if the user may specify them somewhere (especially
  * the CIB) or if they're part of a well-defined structure that a user may need
  * to parse. They should be internal if they're used only internally to
  * Pacemaker (such as daemon IPC/CPG message XML).
  *
  * Constants belong in the following locations:
  * * Public "XE" and "XA": msg_xml.h
  * * Internal "XE" and "XA": crm_internal.h
  * * Public "OPT", "META", and "VALUE": options.h
  * * Internal "OPT", "META", and "VALUE": options_internal.h
  *
  * For meta-attributes that can be specified as either XML attributes or nvpair
  * names, use "META" unless using both "XA" and "META" constants adds clarity.
  * An example is operation attributes, which can be specified either as
  * attributes of the PCMK_XE_OP element or as nvpairs in a meta-attribute set
  * beneath the PCMK_XE_OP element.
  */
 
 /*
  * XML elements
  */
 
+#define PCMK_XE_ACL_TARGET                  "acl_target"
 #define PCMK_XE_ACLS                        "acls"
 #define PCMK_XE_ALERT                       "alert"
 #define PCMK_XE_ALERTS                      "alerts"
 #define PCMK_XE_ATTRIBUTE                   "attribute"
 #define PCMK_XE_BUNDLE                      "bundle"
 #define PCMK_XE_CIB                         "cib"
 #define PCMK_XE_CLONE                       "clone"
 #define PCMK_XE_CLUSTER_PROPERTY_SET        "cluster_property_set"
 #define PCMK_XE_CONFIGURATION               "configuration"
 #define PCMK_XE_CONSTRAINTS                 "constraints"
 #define PCMK_XE_CONTENT                     "content"
 #define PCMK_XE_CRM_CONFIG                  "crm_config"
 #define PCMK_XE_DATE_EXPRESSION             "date_expression"
 #define PCMK_XE_EXPRESSION                  "expression"
 #define PCMK_XE_GROUP                       "group"
 #define PCMK_XE_INSTANCE_ATTRIBUTES         "instance_attributes"
 #define PCMK_XE_LONGDESC                    "longdesc"
 #define PCMK_XE_META_ATTRIBUTES             "meta_attributes"
 #define PCMK_XE_NODE                        "node"
 #define PCMK_XE_NODES                       "nodes"
 #define PCMK_XE_NVPAIR                      "nvpair"
 #define PCMK_XE_OP                          "op"
 #define PCMK_XE_OP_DEFAULTS                 "op_defaults"
 #define PCMK_XE_OPERATION                   "operation"
 #define PCMK_XE_OP_EXPRESSION               "op_expression"
 #define PCMK_XE_OPTION                      "option"
 #define PCMK_XE_PARAMETER                   "parameter"
 #define PCMK_XE_PARAMETERS                  "parameters"
 #define PCMK_XE_PRIMITIVE                   "primitive"
 #define PCMK_XE_RECIPIENT                   "recipient"
 #define PCMK_XE_RESOURCE_AGENT              "resource-agent"
 #define PCMK_XE_RESOURCE_REF                "resource_ref"
 #define PCMK_XE_RESOURCE_SET                "resource_set"
 #define PCMK_XE_RESOURCES                   "resources"
 #define PCMK_XE_ROLE                        "role"
 #define PCMK_XE_RULE                        "rule"
 #define PCMK_XE_RSC_COLOCATION              "rsc_colocation"
 #define PCMK_XE_RSC_DEFAULTS                "rsc_defaults"
 #define PCMK_XE_RSC_EXPRESSION              "rsc_expression"
 #define PCMK_XE_RSC_LOCATION                "rsc_location"
 #define PCMK_XE_RSC_ORDER                   "rsc_order"
 #define PCMK_XE_RSC_TICKET                  "rsc_ticket"
 #define PCMK_XE_SELECT                      "select"
 #define PCMK_XE_SELECT_ATTRIBUTES           "select_attributes"
 #define PCMK_XE_SELECT_FENCING              "select_fencing"
 #define PCMK_XE_SELECT_NODES                "select_nodes"
 #define PCMK_XE_SELECT_RESOURCES            "select_resources"
 #define PCMK_XE_SHORTDESC                   "shortdesc"
 #define PCMK_XE_STATUS                      "status"
 #define PCMK_XE_TEMPLATE                    "template"
 #define PCMK_XE_UTILIZATION                 "utilization"
 #define PCMK_XE_VERSION                     "version"
 
 
 /*
  * XML attributes
  */
 
 #define PCMK_XA_ADMIN_EPOCH                 "admin_epoch"
 #define PCMK_XA_ATTRIBUTE                   "attribute"
 #define PCMK_XA_BOOLEAN_OP                  "boolean-op"
 #define PCMK_XA_CIB_LAST_WRITTEN            "cib-last-written"
 #define PCMK_XA_CLASS                       "class"
 #define PCMK_XA_CRM_DEBUG_ORIGIN            "crm-debug-origin"
 #define PCMK_XA_CRM_FEATURE_SET             "crm_feature_set"
 #define PCMK_XA_CRM_TIMESTAMP               "crm-timestamp"
 #define PCMK_XA_DC_UUID                     "dc-uuid"
 #define PCMK_XA_DEFAULT                     "default"
 #define PCMK_XA_DESCRIPTION                 "description"
 #define PCMK_XA_DEVICES                     "devices"
 #define PCMK_XA_EPOCH                       "epoch"
 #define PCMK_XA_EXEC_TIME                   "exec-time"
 #define PCMK_XA_EXIT_REASON                 "exit-reason"
 #define PCMK_XA_FIRST                       "first"
 #define PCMK_XA_FIRST_ACTION                "first-action"
 #define PCMK_XA_FORMAT                      "format"
 #define PCMK_XA_HAVE_QUORUM                 "have-quorum"
 #define PCMK_XA_ID                          "id"
 #define PCMK_XA_ID_REF                      "id-ref"
 #define PCMK_XA_INDEX                       "index"
 #define PCMK_XA_INFLUENCE                   "influence"
 #define PCMK_XA_KIND                        "kind"
 #define PCMK_XA_LANG                        "lang"
 #define PCMK_XA_LAST_RC_CHANGE              "last-rc-change"
 #define PCMK_XA_LOSS_POLICY                 "loss-policy"
 #define PCMK_XA_NAME                        "name"
 #define PCMK_XA_NO_QUORUM_PANIC             "no-quorum-panic"
 #define PCMK_XA_NODE                        "node"
 #define PCMK_XA_NODE_ATTRIBUTE              "node-attribute"
 #define PCMK_XA_NUM_UPDATES                 "num_updates"
 #define PCMK_XA_OBJECT_TYPE                 "object-type"
 #define PCMK_XA_OP                          "op"
 #define PCMK_XA_OPERATION                   "operation"
 #define PCMK_XA_ORIGIN                      "origin"
 #define PCMK_XA_PATH                        "path"
 #define PCMK_XA_PROVIDER                    "provider"
 #define PCMK_XA_QUEUE_TIME                  "queue-time"
 #define PCMK_XA_REASON                      "reason"
 #define PCMK_XA_REFERENCE                   "reference"
 #define PCMK_XA_REQUEST                     "request"
 #define PCMK_XA_RESOURCE_DISCOVERY          "resource-discovery"
 #define PCMK_XA_RESULT                      "result"
 #define PCMK_XA_ROLE                        "role"
 #define PCMK_XA_RSC                         "rsc"
 #define PCMK_XA_RSC_PATTERN                 "rsc-pattern"
 #define PCMK_XA_RSC_ROLE                    "rsc-role"
 #define PCMK_XA_SCORE                       "score"
 #define PCMK_XA_SCORE_ATTRIBUTE             "score-attribute"
 #define PCMK_XA_SYMMETRICAL                 "symmetrical"
 #define PCMK_XA_TARGET                      "target"
 #define PCMK_XA_TARGET_ATTRIBUTE            "target-attribute"
 #define PCMK_XA_TARGET_PATTERN              "target-pattern"
 #define PCMK_XA_TARGET_VALUE                "target-value"
 #define PCMK_XA_TEMPLATE                    "template"
 #define PCMK_XA_TICKET                      "ticket"
 #define PCMK_XA_THEN                        "then"
 #define PCMK_XA_THEN_ACTION                 "then-action"
 #define PCMK_XA_TYPE                        "type"
 #define PCMK_XA_UNAME                       "uname"
 #define PCMK_XA_UPDATE_CLIENT               "update-client"
 #define PCMK_XA_UPDATE_ORIGIN               "update-origin"
 #define PCMK_XA_UPDATE_USER                 "update-user"
 #define PCMK_XA_VALIDATE_WITH               "validate-with"
 #define PCMK_XA_VALUE                       "value"
 #define PCMK_XA_VALUE_SOURCE                "value-source"
 #define PCMK_XA_VERSION                     "version"
 #define PCMK_XA_WITH_RSC                    "with-rsc"
 #define PCMK_XA_WITH_RSC_ROLE               "with-rsc-role"
 #define PCMK_XA_XPATH                       "xpath"
 
 
 /*
  * Older constants that don't follow current naming
  */
 
 #  ifndef T_CRM
 #    define T_CRM     "crmd"
 #  endif
 
 #  ifndef T_ATTRD
 #    define T_ATTRD     "attrd"
 #  endif
 
 #  define CIB_OPTIONS_FIRST "cib-bootstrap-options"
 
 #  define F_CRM_DATA			"crm_xml"
 
 /*---- Common tags/attrs */
 #  define XML_DIFF_MARKER		"__crm_diff_marker__"
 #  define XML_TAG_FAILED		"failed"
 
 #  define XML_TAG_OPTIONS		"options"
 
 /*---- top level tags/attrs */
 #  define XML_PING_ATTR_PACEMAKERDSTATE_INIT "init"
 #  define XML_PING_ATTR_PACEMAKERDSTATE_STARTINGDAEMONS "starting_daemons"
 #  define XML_PING_ATTR_PACEMAKERDSTATE_WAITPING "wait_for_ping"
 #  define XML_PING_ATTR_PACEMAKERDSTATE_RUNNING "running"
 #  define XML_PING_ATTR_PACEMAKERDSTATE_SHUTTINGDOWN "shutting_down"
 #  define XML_PING_ATTR_PACEMAKERDSTATE_SHUTDOWNCOMPLETE "shutdown_complete"
 #  define XML_PING_ATTR_PACEMAKERDSTATE_REMOTE "remote"
 
 #  define XML_FAIL_TAG_CIB		"failed_update"
 
 /*---- CIB specific tags/attrs */
 #  define XML_CIB_TAG_SECTION_ALL	"all"
 
 #  define XML_NODE_IS_REMOTE    	"remote_node"
 #  define XML_NODE_IS_FENCED		"node_fenced"
 #  define XML_NODE_IS_MAINTENANCE   "node_in_maintenance"
 
 #  define XML_CIB_ATTR_SHUTDOWN       	"shutdown"
 
 #  define XML_NODE_ATTR_RSC_DISCOVERY   "resource-discovery-enabled"
 
-#  define XML_ACL_TAG_USER		"acl_target"
+#  define XML_ACL_TAG_USER		PCMK_XE_ACL_TARGET
 #  define XML_ACL_TAG_USERv1		"acl_user"
 #  define XML_ACL_TAG_GROUP		"acl_group"
 #  define XML_ACL_TAG_ROLE		"acl_role"
 #  define XML_ACL_TAG_PERMISSION	"acl_permission"
 #  define XML_ACL_TAG_ROLE_REFv1	"role_ref"
 #  define XML_ACL_TAG_READ		"read"
 #  define XML_ACL_TAG_WRITE		"write"
 #  define XML_ACL_TAG_DENY		"deny"
 
 #  define XML_CIB_TAG_TICKETS		"tickets"
 #  define XML_CIB_TAG_TICKET_STATE	"ticket_state"
 
 #  define XML_CIB_TAG_TAGS   		"tags"
 #  define XML_CIB_TAG_TAG   		"tag"
 #  define XML_CIB_TAG_OBJ_REF 		"obj_ref"
 
 #  define XML_TAG_FENCING_TOPOLOGY      "fencing-topology"
 #  define XML_TAG_FENCING_LEVEL         "fencing-level"
 
 #  define XML_TAG_DIFF                  "diff"
 #  define XML_DIFF_VERSION              PCMK_XE_VERSION
 #  define XML_DIFF_VSOURCE              "source"
 #  define XML_DIFF_VTARGET              "target"
 #  define XML_DIFF_CHANGE               "change"
 #  define XML_DIFF_LIST                 "change-list"
 #  define XML_DIFF_ATTR                 "change-attr"
 #  define XML_DIFF_RESULT               "change-result"
 #  define XML_DIFF_POSITION             "position"
 
 #  define ID(x) crm_element_value(x, PCMK_XA_ID)
 
 #ifdef __cplusplus
 }
 #endif
 
 #endif
diff --git a/lib/cib/cib_file.c b/lib/cib/cib_file.c
index 882c6242c4..27189fbe44 100644
--- a/lib/cib/cib_file.c
+++ b/lib/cib/cib_file.c
@@ -1,1177 +1,1177 @@
 /*
  * Original copyright 2004 International Business Machines
  * Later changes copyright 2008-2024 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>
 #include <unistd.h>
 #include <limits.h>
 #include <stdlib.h>
 #include <stdint.h>
 #include <stdio.h>
 #include <stdarg.h>
 #include <string.h>
 #include <pwd.h>
 
 #include <sys/stat.h>
 #include <sys/types.h>
 #include <glib.h>
 
 #include <crm/crm.h>
 #include <crm/cib/internal.h>
 #include <crm/msg_xml.h>
 #include <crm/common/ipc.h>
 #include <crm/common/xml.h>
 #include <crm/common/xml_internal.h>
 
 #define CIB_SERIES "cib"
 #define CIB_SERIES_MAX 100
 #define CIB_SERIES_BZIP FALSE /* Must be false because archived copies are
                                  created with hard links
                                */
 
 #define CIB_LIVE_NAME CIB_SERIES ".xml"
 
 // key: client ID (const char *) -> value: client (cib_t *)
 static GHashTable *client_table = NULL;
 
 enum cib_file_flags {
     cib_file_flag_dirty = (1 << 0),
     cib_file_flag_live  = (1 << 1),
 };
 
 typedef struct cib_file_opaque_s {
     char *id;
     char *filename;
     uint32_t flags; // Group of enum cib_file_flags
     xmlNode *cib_xml;
 } cib_file_opaque_t;
 
 static int cib_file_process_commit_transaction(const char *op, int options,
                                                const char *section,
                                                xmlNode *req, xmlNode *input,
                                                xmlNode *existing_cib,
                                                xmlNode **result_cib,
                                                xmlNode **answer);
 
 /*!
  * \internal
  * \brief Add a CIB file client to client table
  *
  * \param[in] cib  CIB client
  */
 static void
 register_client(const cib_t *cib)
 {
     cib_file_opaque_t *private = cib->variant_opaque;
 
     if (client_table == NULL) {
         client_table = pcmk__strkey_table(NULL, NULL);
     }
     g_hash_table_insert(client_table, private->id, (gpointer) cib);
 }
 
 /*!
  * \internal
  * \brief Remove a CIB file client from client table
  *
  * \param[in] cib  CIB client
  */
 static void
 unregister_client(const cib_t *cib)
 {
     cib_file_opaque_t *private = cib->variant_opaque;
 
     if (client_table == NULL) {
         return;
     }
 
     g_hash_table_remove(client_table, private->id);
 
     /* @COMPAT: Add to crm_exit() when libcib and libcrmcommon are merged,
      * instead of destroying the client table when there are no more clients.
      */
     if (g_hash_table_size(client_table) == 0) {
         g_hash_table_destroy(client_table);
         client_table = NULL;
     }
 }
 
 /*!
  * \internal
  * \brief Look up a CIB file client by its ID
  *
  * \param[in] client_id  CIB client ID
  *
  * \return CIB client with matching ID if found, or \p NULL otherwise
  */
 static cib_t *
 get_client(const char *client_id)
 {
     if (client_table == NULL) {
         return NULL;
     }
     return g_hash_table_lookup(client_table, (gpointer) client_id);
 }
 
 static const cib__op_fn_t cib_op_functions[] = {
     [cib__op_apply_patch]      = cib_process_diff,
     [cib__op_bump]             = cib_process_bump,
     [cib__op_commit_transact]  = cib_file_process_commit_transaction,
     [cib__op_create]           = cib_process_create,
     [cib__op_delete]           = cib_process_delete,
     [cib__op_erase]            = cib_process_erase,
     [cib__op_modify]           = cib_process_modify,
     [cib__op_query]            = cib_process_query,
     [cib__op_replace]          = cib_process_replace,
     [cib__op_upgrade]          = cib_process_upgrade,
 };
 
 /* cib_file_backup() and cib_file_write_with_digest() need to chown the
  * written files only in limited circumstances, so these variables allow
  * that to be indicated without affecting external callers
  */
 static uid_t cib_file_owner = 0;
 static uid_t cib_file_group = 0;
 static gboolean cib_do_chown = FALSE;
 
 #define cib_set_file_flags(cibfile, flags_to_set) do {                  \
         (cibfile)->flags = pcmk__set_flags_as(__func__, __LINE__,       \
                                               LOG_TRACE, "CIB file",    \
                                               cibfile->filename,        \
                                               (cibfile)->flags,         \
                                               (flags_to_set),           \
                                               #flags_to_set);           \
     } while (0)
 
 #define cib_clear_file_flags(cibfile, flags_to_clear) do {              \
         (cibfile)->flags = pcmk__clear_flags_as(__func__, __LINE__,     \
                                                 LOG_TRACE, "CIB file",  \
                                                 cibfile->filename,      \
                                                 (cibfile)->flags,       \
                                                 (flags_to_clear),       \
                                                 #flags_to_clear);       \
     } while (0)
 
 /*!
  * \internal
  * \brief Get the function that performs a given CIB file operation
  *
  * \param[in] operation  Operation whose function to look up
  *
  * \return Function that performs \p operation for a CIB file client
  */
 static cib__op_fn_t
 file_get_op_function(const cib__operation_t *operation)
 {
     enum cib__op_type type = operation->type;
 
     CRM_ASSERT(type >= 0);
 
     if (type >= PCMK__NELEM(cib_op_functions)) {
         return NULL;
     }
     return cib_op_functions[type];
 }
 
 /*!
  * \internal
  * \brief Check whether a file is the live CIB
  *
  * \param[in] filename Name of file to check
  *
  * \return TRUE if file exists and its real path is same as live CIB's
  */
 static gboolean
 cib_file_is_live(const char *filename)
 {
     gboolean same = FALSE;
 
     if (filename != NULL) {
         // Canonicalize file names for true comparison
         char *real_filename = NULL;
 
         if (pcmk__real_path(filename, &real_filename) == pcmk_rc_ok) {
             char *real_livename = NULL;
 
             if (pcmk__real_path(CRM_CONFIG_DIR "/" CIB_LIVE_NAME,
                                 &real_livename) == pcmk_rc_ok) {
                 same = !strcmp(real_filename, real_livename);
                 free(real_livename);
             }
             free(real_filename);
         }
     }
     return same;
 }
 
 static int
 cib_file_process_request(cib_t *cib, xmlNode *request, xmlNode **output)
 {
     int rc = pcmk_ok;
     const cib__operation_t *operation = NULL;
     cib__op_fn_t op_function = NULL;
 
     int call_id = 0;
     int call_options = cib_none;
     const char *op = crm_element_value(request, F_CIB_OPERATION);
     const char *section = crm_element_value(request, F_CIB_SECTION);
     xmlNode *data = get_message_xml(request, F_CIB_CALLDATA);
 
     bool changed = false;
     bool read_only = false;
     xmlNode *result_cib = NULL;
     xmlNode *cib_diff = NULL;
 
     cib_file_opaque_t *private = cib->variant_opaque;
 
     // We error checked these in callers
     cib__get_operation(op, &operation);
     op_function = file_get_op_function(operation);
 
     crm_element_value_int(request, F_CIB_CALLID, &call_id);
     crm_element_value_int(request, F_CIB_CALLOPTS, &call_options);
 
     read_only = !pcmk_is_set(operation->flags, cib__op_attr_modifies);
 
     // Mirror the logic in prepare_input() in pacemaker-based
     if ((section != NULL) && pcmk__xe_is(data, PCMK_XE_CIB)) {
 
         data = pcmk_find_cib_element(data, section);
     }
 
     rc = cib_perform_op(op, call_options, op_function, read_only, section,
                         request, data, true, &changed, &private->cib_xml,
                         &result_cib, &cib_diff, output);
 
     if (pcmk_is_set(call_options, cib_transaction)) {
         /* The rest of the logic applies only to the transaction as a whole, not
          * to individual requests.
          */
         goto done;
     }
 
     if (rc == -pcmk_err_schema_validation) {
         validate_xml_verbose(result_cib);
 
     } else if ((rc == pcmk_ok) && !read_only) {
         pcmk__log_xml_patchset(LOG_DEBUG, cib_diff);
 
         if (result_cib != private->cib_xml) {
             free_xml(private->cib_xml);
             private->cib_xml = result_cib;
         }
         cib_set_file_flags(private, cib_file_flag_dirty);
     }
 
     // Global operation callback (deprecated)
     if (cib->op_callback != NULL) {
         cib->op_callback(NULL, call_id, rc, *output);
     }
 
 done:
     if ((result_cib != private->cib_xml) && (result_cib != *output)) {
         free_xml(result_cib);
     }
     free_xml(cib_diff);
     return rc;
 }
 
 static int
 cib_file_perform_op_delegate(cib_t *cib, const char *op, const char *host,
                              const char *section, xmlNode *data,
                              xmlNode **output_data, int call_options,
                              const char *user_name)
 {
     int rc = pcmk_ok;
     xmlNode *request = NULL;
     xmlNode *output = NULL;
     cib_file_opaque_t *private = cib->variant_opaque;
 
     const cib__operation_t *operation = NULL;
 
     crm_info("Handling %s operation for %s as %s",
              pcmk__s(op, "invalid"), pcmk__s(section, "entire CIB"),
              pcmk__s(user_name, "default user"));
 
     if (output_data != NULL) {
         *output_data = NULL;
     }
 
     if (cib->state == cib_disconnected) {
         return -ENOTCONN;
     }
 
     rc = cib__get_operation(op, &operation);
     rc = pcmk_rc2legacy(rc);
     if (rc != pcmk_ok) {
         // @COMPAT: At compatibility break, use rc directly
         return -EPROTONOSUPPORT;
     }
 
     if (file_get_op_function(operation) == NULL) {
         // @COMPAT: At compatibility break, use EOPNOTSUPP
         crm_err("Operation %s is not supported by CIB file clients", op);
         return -EPROTONOSUPPORT;
     }
 
     cib__set_call_options(call_options, "file operation", cib_no_mtime);
 
     rc = cib__create_op(cib, op, host, section, data, call_options, user_name,
                         NULL, &request);
     if (rc != pcmk_ok) {
         return rc;
     }
-    crm_xml_add(request, XML_ACL_TAG_USER, user_name);
+    crm_xml_add(request, PCMK_XE_ACL_TARGET, user_name);
     crm_xml_add(request, F_CIB_CLIENTID, private->id);
 
     if (pcmk_is_set(call_options, cib_transaction)) {
         rc = cib__extend_transaction(cib, request);
         goto done;
     }
 
     rc = cib_file_process_request(cib, request, &output);
 
     if ((output_data != NULL) && (output != NULL)) {
         if (output->doc == private->cib_xml->doc) {
             *output_data = copy_xml(output);
         } else {
             *output_data = output;
         }
     }
 
 done:
     if ((output != NULL)
         && (output->doc != private->cib_xml->doc)
         && ((output_data == NULL) || (output != *output_data))) {
 
         free_xml(output);
     }
     free_xml(request);
     return rc;
 }
 
 /*!
  * \internal
  * \brief Read CIB from disk and validate it against XML schema
  *
  * \param[in]   filename  Name of file to read CIB from
  * \param[out]  output    Where to store the read CIB XML
  *
  * \return pcmk_ok on success,
  *         -ENXIO if file does not exist (or stat() otherwise fails), or
  *         -pcmk_err_schema_validation if XML doesn't parse or validate
  * \note If filename is the live CIB, this will *not* verify its digest,
  *       though that functionality would be trivial to add here.
  *       Also, this will *not* verify that the file is writable,
  *       because some callers might not need to write.
  */
 static int
 load_file_cib(const char *filename, xmlNode **output)
 {
     struct stat buf;
     xmlNode *root = NULL;
 
     /* Ensure file is readable */
     if (strcmp(filename, "-") && (stat(filename, &buf) < 0)) {
         return -ENXIO;
     }
 
     /* Parse XML from file */
     root = filename2xml(filename);
     if (root == NULL) {
         return -pcmk_err_schema_validation;
     }
 
     /* Add a status section if not already present */
     if (find_xml_node(root, PCMK_XE_STATUS, FALSE) == NULL) {
         create_xml_node(root, PCMK_XE_STATUS);
     }
 
     /* Validate XML against its specified schema */
     if (validate_xml(root, NULL, TRUE) == FALSE) {
         const char *schema = crm_element_value(root, PCMK_XA_VALIDATE_WITH);
 
         crm_err("CIB does not validate against %s", schema);
         free_xml(root);
         return -pcmk_err_schema_validation;
     }
 
     /* Remember the parsed XML for later use */
     *output = root;
     return pcmk_ok;
 }
 
 static int
 cib_file_signon(cib_t *cib, const char *name, enum cib_conn_type type)
 {
     int rc = pcmk_ok;
     cib_file_opaque_t *private = cib->variant_opaque;
 
     if (private->filename == NULL) {
         rc = -EINVAL;
     } else {
         rc = load_file_cib(private->filename, &private->cib_xml);
     }
 
     if (rc == pcmk_ok) {
         crm_debug("Opened connection to local file '%s' for %s",
                   private->filename, name);
         cib->state = cib_connected_command;
         cib->type = cib_command;
         register_client(cib);
 
     } else {
         crm_info("Connection to local file '%s' for %s (client %s) failed: %s",
                  private->filename, name, private->id, pcmk_strerror(rc));
     }
     return rc;
 }
 
 /*!
  * \internal
  * \brief Write out the in-memory CIB to a live CIB file
  *
  * param[in]     cib_root  Root of XML tree to write
  * param[in,out] path      Full path to file to write
  *
  * \return 0 on success, -1 on failure
  */
 static int
 cib_file_write_live(xmlNode *cib_root, char *path)
 {
     uid_t uid = geteuid();
     struct passwd *daemon_pwent;
     char *sep = strrchr(path, '/');
     const char *cib_dirname, *cib_filename;
     int rc = 0;
 
     /* Get the desired uid/gid */
     errno = 0;
     daemon_pwent = getpwnam(CRM_DAEMON_USER);
     if (daemon_pwent == NULL) {
         crm_perror(LOG_ERR, "Could not find %s user", CRM_DAEMON_USER);
         return -1;
     }
 
     /* If we're root, we can change the ownership;
      * if we're daemon, anything we create will be OK;
      * otherwise, block access so we don't create wrong owner
      */
     if ((uid != 0) && (uid != daemon_pwent->pw_uid)) {
         crm_perror(LOG_ERR, "Must be root or %s to modify live CIB",
                    CRM_DAEMON_USER);
         return 0;
     }
 
     /* fancy footwork to separate dirname from filename
      * (we know the canonical name maps to the live CIB,
      * but the given name might be relative, or symlinked)
      */
     if (sep == NULL) { /* no directory component specified */
         cib_dirname = "./";
         cib_filename = path;
     } else if (sep == path) { /* given name is in / */
         cib_dirname = "/";
         cib_filename = path + 1;
     } else { /* typical case; split given name into parts */
         *sep = '\0';
         cib_dirname = path;
         cib_filename = sep + 1;
     }
 
     /* if we're root, we want to update the file ownership */
     if (uid == 0) {
         cib_file_owner = daemon_pwent->pw_uid;
         cib_file_group = daemon_pwent->pw_gid;
         cib_do_chown = TRUE;
     }
 
     /* write the file */
     if (cib_file_write_with_digest(cib_root, cib_dirname,
                                    cib_filename) != pcmk_ok) {
         rc = -1;
     }
 
     /* turn off file ownership changes, for other callers */
     if (uid == 0) {
         cib_do_chown = FALSE;
     }
 
     /* undo fancy stuff */
     if ((sep != NULL) && (*sep == '\0')) {
         *sep = '/';
     }
 
     return rc;
 }
 
 /*!
  * \internal
  * \brief Sign-off method for CIB file variants
  *
  * This will write the file to disk if needed, and free the in-memory CIB. If
  * the file is the live CIB, it will compute and write a signature as well.
  *
  * \param[in,out] cib  CIB object to sign off
  *
  * \return pcmk_ok on success, pcmk_err_generic on failure
  * \todo This method should refuse to write the live CIB if the CIB manager is
  *       running.
  */
 static int
 cib_file_signoff(cib_t *cib)
 {
     int rc = pcmk_ok;
     cib_file_opaque_t *private = cib->variant_opaque;
 
     crm_debug("Disconnecting from the CIB manager");
     cib->state = cib_disconnected;
     cib->type = cib_no_connection;
     unregister_client(cib);
     cib->cmds->end_transaction(cib, false, cib_none);
 
     /* If the in-memory CIB has been changed, write it to disk */
     if (pcmk_is_set(private->flags, cib_file_flag_dirty)) {
 
         /* If this is the live CIB, write it out with a digest */
         if (pcmk_is_set(private->flags, cib_file_flag_live)) {
             if (cib_file_write_live(private->cib_xml, private->filename) < 0) {
                 rc = pcmk_err_generic;
             }
 
         /* Otherwise, it's a simple write */
         } else {
             gboolean do_bzip = pcmk__ends_with_ext(private->filename, ".bz2");
 
             if (write_xml_file(private->cib_xml, private->filename,
                                do_bzip) <= 0) {
                 rc = pcmk_err_generic;
             }
         }
 
         if (rc == pcmk_ok) {
             crm_info("Wrote CIB to %s", private->filename);
             cib_clear_file_flags(private, cib_file_flag_dirty);
         } else {
             crm_err("Could not write CIB to %s", private->filename);
         }
     }
 
     /* Free the in-memory CIB */
     free_xml(private->cib_xml);
     private->cib_xml = NULL;
     return rc;
 }
 
 static int
 cib_file_free(cib_t *cib)
 {
     int rc = pcmk_ok;
 
     if (cib->state != cib_disconnected) {
         rc = cib_file_signoff(cib);
     }
 
     if (rc == pcmk_ok) {
         cib_file_opaque_t *private = cib->variant_opaque;
 
         free(private->id);
         free(private->filename);
         free(private);
         free(cib->cmds);
         free(cib->user);
         free(cib);
 
     } else {
         fprintf(stderr, "Couldn't sign off: %d\n", rc);
     }
 
     return rc;
 }
 
 static int
 cib_file_inputfd(cib_t *cib)
 {
     return -EPROTONOSUPPORT;
 }
 
 static int
 cib_file_register_notification(cib_t *cib, const char *callback, int enabled)
 {
     return -EPROTONOSUPPORT;
 }
 
 static int
 cib_file_set_connection_dnotify(cib_t *cib,
                                 void (*dnotify) (gpointer user_data))
 {
     return -EPROTONOSUPPORT;
 }
 
 /*!
  * \internal
  * \brief Get the given CIB connection's unique client identifier
  *
  * \param[in]  cib       CIB connection
  * \param[out] async_id  If not \p NULL, where to store asynchronous client ID
  * \param[out] sync_id   If not \p NULL, where to store synchronous client ID
  *
  * \return Legacy Pacemaker return code
  *
  * \note This is the \p cib_file variant implementation of
  *       \p cib_api_operations_t:client_id().
  */
 static int
 cib_file_client_id(const cib_t *cib, const char **async_id,
                    const char **sync_id)
 {
     cib_file_opaque_t *private = cib->variant_opaque;
 
     if (async_id != NULL) {
         *async_id = private->id;
     }
     if (sync_id != NULL) {
         *sync_id = private->id;
     }
     return pcmk_ok;
 }
 
 cib_t *
 cib_file_new(const char *cib_location)
 {
     cib_file_opaque_t *private = NULL;
     cib_t *cib = cib_new_variant();
 
     if (cib == NULL) {
         return NULL;
     }
 
     private = calloc(1, sizeof(cib_file_opaque_t));
 
     if (private == NULL) {
         free(cib);
         return NULL;
     }
     private->id = crm_generate_uuid();
 
     cib->variant = cib_file;
     cib->variant_opaque = private;
 
     if (cib_location == NULL) {
         cib_location = getenv("CIB_file");
         CRM_CHECK(cib_location != NULL, return NULL); // Shouldn't be possible
     }
     private->flags = 0;
     if (cib_file_is_live(cib_location)) {
         cib_set_file_flags(private, cib_file_flag_live);
         crm_trace("File %s detected as live CIB", cib_location);
     }
     private->filename = strdup(cib_location);
 
     /* assign variant specific ops */
     cib->delegate_fn = cib_file_perform_op_delegate;
     cib->cmds->signon = cib_file_signon;
     cib->cmds->signoff = cib_file_signoff;
     cib->cmds->free = cib_file_free;
     cib->cmds->inputfd = cib_file_inputfd; // Deprecated method
 
     cib->cmds->register_notification = cib_file_register_notification;
     cib->cmds->set_connection_dnotify = cib_file_set_connection_dnotify;
 
     cib->cmds->client_id = cib_file_client_id;
 
     return cib;
 }
 
 /*!
  * \internal
  * \brief Compare the calculated digest of an XML tree against a signature file
  *
  * \param[in] root     Root of XML tree to compare
  * \param[in] sigfile  Name of signature file containing digest to compare
  *
  * \return TRUE if digests match or signature file does not exist, else FALSE
  */
 static gboolean
 cib_file_verify_digest(xmlNode *root, const char *sigfile)
 {
     gboolean passed = FALSE;
     char *expected;
     int rc = pcmk__file_contents(sigfile, &expected);
 
     switch (rc) {
         case pcmk_rc_ok:
             if (expected == NULL) {
                 crm_err("On-disk digest at %s is empty", sigfile);
                 return FALSE;
             }
             break;
         case ENOENT:
             crm_warn("No on-disk digest present at %s", sigfile);
             return TRUE;
         default:
             crm_err("Could not read on-disk digest from %s: %s",
                     sigfile, pcmk_rc_str(rc));
             return FALSE;
     }
     passed = pcmk__verify_digest(root, expected);
     free(expected);
     return passed;
 }
 
 /*!
  * \internal
  * \brief Read an XML tree from a file and verify its digest
  *
  * \param[in]  filename  Name of XML file to read
  * \param[in]  sigfile   Name of signature file containing digest to compare
  * \param[out] root      If non-NULL, will be set to pointer to parsed XML tree
  *
  * \return 0 if file was successfully read, parsed and verified, otherwise:
  *         -errno on stat() failure,
  *         -pcmk_err_cib_corrupt if file size is 0 or XML is not parseable, or
  *         -pcmk_err_cib_modified if digests do not match
  * \note If root is non-NULL, it is the caller's responsibility to free *root on
  *       successful return.
  */
 int
 cib_file_read_and_verify(const char *filename, const char *sigfile, xmlNode **root)
 {
     int s_res;
     struct stat buf;
     char *local_sigfile = NULL;
     xmlNode *local_root = NULL;
 
     CRM_ASSERT(filename != NULL);
     if (root) {
         *root = NULL;
     }
 
     /* Verify that file exists and its size is nonzero */
     s_res = stat(filename, &buf);
     if (s_res < 0) {
         crm_perror(LOG_WARNING, "Could not verify cluster configuration file %s", filename);
         return -errno;
     } else if (buf.st_size == 0) {
         crm_warn("Cluster configuration file %s is corrupt (size is zero)", filename);
         return -pcmk_err_cib_corrupt;
     }
 
     /* Parse XML */
     local_root = filename2xml(filename);
     if (local_root == NULL) {
         crm_warn("Cluster configuration file %s is corrupt (unparseable as XML)", filename);
         return -pcmk_err_cib_corrupt;
     }
 
     /* If sigfile is not specified, use original file name plus .sig */
     if (sigfile == NULL) {
         sigfile = local_sigfile = crm_strdup_printf("%s.sig", filename);
     }
 
     /* Verify that digests match */
     if (cib_file_verify_digest(local_root, sigfile) == FALSE) {
         free(local_sigfile);
         free_xml(local_root);
         return -pcmk_err_cib_modified;
     }
 
     free(local_sigfile);
     if (root) {
         *root = local_root;
     } else {
         free_xml(local_root);
     }
     return pcmk_ok;
 }
 
 /*!
  * \internal
  * \brief Back up a CIB
  *
  * \param[in] cib_dirname Directory containing CIB file and backups
  * \param[in] cib_filename Name (relative to cib_dirname) of CIB file to back up
  *
  * \return 0 on success, -1 on error
  */
 static int
 cib_file_backup(const char *cib_dirname, const char *cib_filename)
 {
     int rc = 0;
     unsigned int seq;
     char *cib_path = crm_strdup_printf("%s/%s", cib_dirname, cib_filename);
     char *cib_digest = crm_strdup_printf("%s.sig", cib_path);
     char *backup_path;
     char *backup_digest;
 
     // Determine backup and digest file names
     if (pcmk__read_series_sequence(cib_dirname, CIB_SERIES,
                                    &seq) != pcmk_rc_ok) {
         // @TODO maybe handle errors better ...
         seq = 0;
     }
     backup_path = pcmk__series_filename(cib_dirname, CIB_SERIES, seq,
                                         CIB_SERIES_BZIP);
     backup_digest = crm_strdup_printf("%s.sig", backup_path);
 
     /* Remove the old backups if they exist */
     unlink(backup_path);
     unlink(backup_digest);
 
     /* Back up the CIB, by hard-linking it to the backup name */
     if ((link(cib_path, backup_path) < 0) && (errno != ENOENT)) {
         crm_perror(LOG_ERR, "Could not archive %s by linking to %s",
                    cib_path, backup_path);
         rc = -1;
 
     /* Back up the CIB signature similarly */
     } else if ((link(cib_digest, backup_digest) < 0) && (errno != ENOENT)) {
         crm_perror(LOG_ERR, "Could not archive %s by linking to %s",
                    cib_digest, backup_digest);
         rc = -1;
 
     /* Update the last counter and ensure everything is sync'd to media */
     } else {
         pcmk__write_series_sequence(cib_dirname, CIB_SERIES, ++seq,
                                     CIB_SERIES_MAX);
         if (cib_do_chown) {
             int rc2;
 
             if ((chown(backup_path, cib_file_owner, cib_file_group) < 0)
                     && (errno != ENOENT)) {
                 crm_perror(LOG_ERR, "Could not set owner of %s", backup_path);
                 rc = -1;
             }
             if ((chown(backup_digest, cib_file_owner, cib_file_group) < 0)
                     && (errno != ENOENT)) {
                 crm_perror(LOG_ERR, "Could not set owner of %s", backup_digest);
                 rc = -1;
             }
             rc2 = pcmk__chown_series_sequence(cib_dirname, CIB_SERIES,
                                               cib_file_owner, cib_file_group);
             if (rc2 != pcmk_rc_ok) {
                 crm_err("Could not set owner of sequence file in %s: %s",
                         cib_dirname, pcmk_rc_str(rc2));
                 rc = -1;
             }
         }
         pcmk__sync_directory(cib_dirname);
         crm_info("Archived previous version as %s", backup_path);
     }
 
     free(cib_path);
     free(cib_digest);
     free(backup_path);
     free(backup_digest);
     return rc;
 }
 
 /*!
  * \internal
  * \brief Prepare CIB XML to be written to disk
  *
  * Set \c PCMK_XA_NUM_UPDATES to 0, set \c PCMK_XA_CIB_LAST_WRITTEN to the
  * current timestamp, and strip out the status section.
  *
  * \param[in,out] root  Root of CIB XML tree
  *
  * \return void
  */
 static void
 cib_file_prepare_xml(xmlNode *root)
 {
     xmlNode *cib_status_root = NULL;
 
     /* Always write out with num_updates=0 and current last-written timestamp */
     crm_xml_add(root, PCMK_XA_NUM_UPDATES, "0");
     pcmk__xe_add_last_written(root);
 
     /* Delete status section before writing to file, because
      * we discard it on startup anyway, and users get confused by it */
     cib_status_root = find_xml_node(root, PCMK_XE_STATUS, TRUE);
     CRM_LOG_ASSERT(cib_status_root != NULL);
     if (cib_status_root != NULL) {
         free_xml(cib_status_root);
     }
 }
 
 /*!
  * \internal
  * \brief Write CIB to disk, along with a signature file containing its digest
  *
  * \param[in,out] cib_root      Root of XML tree to write
  * \param[in]     cib_dirname   Directory containing CIB and signature files
  * \param[in]     cib_filename  Name (relative to cib_dirname) of file to write
  *
  * \return pcmk_ok on success,
  *         pcmk_err_cib_modified if existing cib_filename doesn't match digest,
  *         pcmk_err_cib_backup if existing cib_filename couldn't be backed up,
  *         or pcmk_err_cib_save if new cib_filename couldn't be saved
  */
 int
 cib_file_write_with_digest(xmlNode *cib_root, const char *cib_dirname,
                            const char *cib_filename)
 {
     int exit_rc = pcmk_ok;
     int rc, fd;
     char *digest = NULL;
 
     /* Detect CIB version for diagnostic purposes */
     const char *epoch = crm_element_value(cib_root, PCMK_XA_EPOCH);
     const char *admin_epoch = crm_element_value(cib_root, PCMK_XA_ADMIN_EPOCH);
 
     /* Determine full CIB and signature pathnames */
     char *cib_path = crm_strdup_printf("%s/%s", cib_dirname, cib_filename);
     char *digest_path = crm_strdup_printf("%s.sig", cib_path);
 
     /* Create temporary file name patterns for writing out CIB and signature */
     char *tmp_cib = crm_strdup_printf("%s/cib.XXXXXX", cib_dirname);
     char *tmp_digest = crm_strdup_printf("%s/cib.XXXXXX", cib_dirname);
 
     CRM_ASSERT((cib_path != NULL) && (digest_path != NULL)
                && (tmp_cib != NULL) && (tmp_digest != NULL));
 
     /* Ensure the admin didn't modify the existing CIB underneath us */
     crm_trace("Reading cluster configuration file %s", cib_path);
     rc = cib_file_read_and_verify(cib_path, NULL, NULL);
     if ((rc != pcmk_ok) && (rc != -ENOENT)) {
         crm_err("%s was manually modified while the cluster was active!",
                 cib_path);
         exit_rc = pcmk_err_cib_modified;
         goto cleanup;
     }
 
     /* Back up the existing CIB */
     if (cib_file_backup(cib_dirname, cib_filename) < 0) {
         exit_rc = pcmk_err_cib_backup;
         goto cleanup;
     }
 
     crm_debug("Writing CIB to disk");
     umask(S_IWGRP | S_IWOTH | S_IROTH);
     cib_file_prepare_xml(cib_root);
 
     /* Write the CIB to a temporary file, so we can deploy (near) atomically */
     fd = mkstemp(tmp_cib);
     if (fd < 0) {
         crm_perror(LOG_ERR, "Couldn't open temporary file %s for writing CIB",
                    tmp_cib);
         exit_rc = pcmk_err_cib_save;
         goto cleanup;
     }
 
     /* Protect the temporary file */
     if (fchmod(fd, S_IRUSR | S_IWUSR) < 0) {
         crm_perror(LOG_ERR, "Couldn't protect temporary file %s for writing CIB",
                    tmp_cib);
         exit_rc = pcmk_err_cib_save;
         goto cleanup;
     }
     if (cib_do_chown && (fchown(fd, cib_file_owner, cib_file_group) < 0)) {
         crm_perror(LOG_ERR, "Couldn't protect temporary file %s for writing CIB",
                    tmp_cib);
         exit_rc = pcmk_err_cib_save;
         goto cleanup;
     }
 
     /* Write out the CIB */
     if (write_xml_fd(cib_root, tmp_cib, fd, FALSE) <= 0) {
         crm_err("Changes couldn't be written to %s", tmp_cib);
         exit_rc = pcmk_err_cib_save;
         goto cleanup;
     }
 
     /* Calculate CIB digest */
     digest = calculate_on_disk_digest(cib_root);
     CRM_ASSERT(digest != NULL);
     crm_info("Wrote version %s.%s.0 of the CIB to disk (digest: %s)",
              (admin_epoch ? admin_epoch : "0"), (epoch ? epoch : "0"), digest);
 
     /* Write the CIB digest to a temporary file */
     fd = mkstemp(tmp_digest);
     if (fd < 0) {
         crm_perror(LOG_ERR, "Could not create temporary file for CIB digest");
         exit_rc = pcmk_err_cib_save;
         goto cleanup;
     }
     if (cib_do_chown && (fchown(fd, cib_file_owner, cib_file_group) < 0)) {
         crm_perror(LOG_ERR, "Couldn't protect temporary file %s for writing CIB",
                    tmp_cib);
         exit_rc = pcmk_err_cib_save;
         close(fd);
         goto cleanup;
     }
     rc = pcmk__write_sync(fd, digest);
     if (rc != pcmk_rc_ok) {
         crm_err("Could not write digest to %s: %s",
                 tmp_digest, pcmk_rc_str(rc));
         exit_rc = pcmk_err_cib_save;
         close(fd);
         goto cleanup;
     }
     close(fd);
     crm_debug("Wrote digest %s to disk", digest);
 
     /* Verify that what we wrote is sane */
     crm_info("Reading cluster configuration file %s (digest: %s)",
              tmp_cib, tmp_digest);
     rc = cib_file_read_and_verify(tmp_cib, tmp_digest, NULL);
     CRM_ASSERT(rc == 0);
 
     /* Rename temporary files to live, and sync directory changes to media */
     crm_debug("Activating %s", tmp_cib);
     if (rename(tmp_cib, cib_path) < 0) {
         crm_perror(LOG_ERR, "Couldn't rename %s as %s", tmp_cib, cib_path);
         exit_rc = pcmk_err_cib_save;
     }
     if (rename(tmp_digest, digest_path) < 0) {
         crm_perror(LOG_ERR, "Couldn't rename %s as %s", tmp_digest,
                    digest_path);
         exit_rc = pcmk_err_cib_save;
     }
     pcmk__sync_directory(cib_dirname);
 
   cleanup:
     free(cib_path);
     free(digest_path);
     free(digest);
     free(tmp_digest);
     free(tmp_cib);
     return exit_rc;
 }
 
 /*!
  * \internal
  * \brief Process requests in a CIB transaction
  *
  * Stop when a request fails or when all requests have been processed.
  *
  * \param[in,out] cib          CIB client
  * \param[in,out] transaction  CIB transaction
  *
  * \return Standard Pacemaker return code
  */
 static int
 cib_file_process_transaction_requests(cib_t *cib, xmlNode *transaction)
 {
     cib_file_opaque_t *private = cib->variant_opaque;
 
     for (xmlNode *request = first_named_child(transaction, T_CIB_COMMAND);
          request != NULL; request = crm_next_same_xml(request)) {
 
         xmlNode *output = NULL;
         const char *op = crm_element_value(request, F_CIB_OPERATION);
 
         int rc = cib_file_process_request(cib, request, &output);
 
         rc = pcmk_legacy2rc(rc);
         if (rc != pcmk_rc_ok) {
             crm_err("Aborting transaction for CIB file client (%s) on file "
                     "'%s' due to failed %s request: %s",
                     private->id, private->filename, op, pcmk_rc_str(rc));
             crm_log_xml_info(request, "Failed request");
             return rc;
         }
 
         crm_trace("Applied %s request to transaction working CIB for CIB file "
                   "client (%s) on file '%s'",
                   op, private->id, private->filename);
         crm_log_xml_trace(request, "Successful request");
     }
 
     return pcmk_rc_ok;
 }
 
 /*!
  * \internal
  * \brief Commit a given CIB file client's transaction to a working CIB copy
  *
  * \param[in,out] cib          CIB file client
  * \param[in]     transaction  CIB transaction
  * \param[in,out] result_cib   Where to store result CIB
  *
  * \return Standard Pacemaker return code
  *
  * \note The caller is responsible for replacing the \p cib argument's
  *       \p private->cib_xml with \p result_cib on success, and for freeing
  *       \p result_cib using \p free_xml() on failure.
  */
 static int
 cib_file_commit_transaction(cib_t *cib, xmlNode *transaction,
                             xmlNode **result_cib)
 {
     int rc = pcmk_rc_ok;
     cib_file_opaque_t *private = cib->variant_opaque;
     xmlNode *saved_cib = private->cib_xml;
 
     CRM_CHECK(pcmk__xe_is(transaction, T_CIB_TRANSACTION),
               return pcmk_rc_no_transaction);
 
     /* *result_cib should be a copy of private->cib_xml (created by
      * cib_perform_op()). If not, make a copy now. Change tracking isn't
      * strictly required here because:
      * * Each request in the transaction will have changes tracked and ACLs
      *   checked if appropriate.
      * * cib_perform_op() will infer changes for the commit request at the end.
      */
     CRM_CHECK((*result_cib != NULL) && (*result_cib != private->cib_xml),
               *result_cib = copy_xml(private->cib_xml));
 
     crm_trace("Committing transaction for CIB file client (%s) on file '%s' to "
               "working CIB",
               private->id, private->filename);
 
     // Apply all changes to a working copy of the CIB
     private->cib_xml = *result_cib;
 
     rc = cib_file_process_transaction_requests(cib, transaction);
 
     crm_trace("Transaction commit %s for CIB file client (%s) on file '%s'",
               ((rc == pcmk_rc_ok)? "succeeded" : "failed"),
               private->id, private->filename);
 
     /* Some request types (for example, erase) may have freed private->cib_xml
      * (the working copy) and pointed it at a new XML object. In that case, it
      * follows that *result_cib (the working copy) was freed.
      *
      * Point *result_cib at the updated working copy stored in private->cib_xml.
      */
     *result_cib = private->cib_xml;
 
     // Point private->cib_xml back to the unchanged original copy
     private->cib_xml = saved_cib;
 
     return rc;
 }
 
 static int
 cib_file_process_commit_transaction(const char *op, int options,
                                     const char *section, xmlNode *req,
                                     xmlNode *input, xmlNode *existing_cib,
                                     xmlNode **result_cib, xmlNode **answer)
 {
     int rc = pcmk_rc_ok;
     const char *client_id = crm_element_value(req, F_CIB_CLIENTID);
     cib_t *cib = NULL;
 
     CRM_CHECK(client_id != NULL, return -EINVAL);
 
     cib = get_client(client_id);
     CRM_CHECK(cib != NULL, return -EINVAL);
 
     rc = cib_file_commit_transaction(cib, input, result_cib);
     if (rc != pcmk_rc_ok) {
         cib_file_opaque_t *private = cib->variant_opaque;
 
         crm_err("Could not commit transaction for CIB file client (%s) on "
                 "file '%s': %s",
                 private->id, private->filename, pcmk_rc_str(rc));
     }
     return pcmk_rc2legacy(rc);
 }
diff --git a/lib/common/acl.c b/lib/common/acl.c
index 3397b5e41d..a0e167f0b5 100644
--- a/lib/common/acl.c
+++ b/lib/common/acl.c
@@ -1,855 +1,855 @@
 /*
  * Copyright 2004-2024 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>
 
 #include <stdio.h>
 #include <sys/types.h>
 #include <pwd.h>
 #include <string.h>
 #include <stdlib.h>
 #include <stdarg.h>
 
 #include <libxml/tree.h>
 
 #include <crm/crm.h>
 #include <crm/msg_xml.h>
 #include <crm/common/xml.h>
 #include <crm/common/xml_internal.h>
 #include "crmcommon_private.h"
 
 typedef struct xml_acl_s {
         enum xml_private_flags mode;
         gchar *xpath;
 } xml_acl_t;
 
 static void
 free_acl(void *data)
 {
     if (data) {
         xml_acl_t *acl = data;
 
         g_free(acl->xpath);
         free(acl);
     }
 }
 
 void
 pcmk__free_acls(GList *acls)
 {
     g_list_free_full(acls, free_acl);
 }
 
 static GList *
 create_acl(const xmlNode *xml, GList *acls, enum xml_private_flags mode)
 {
     xml_acl_t *acl = NULL;
 
     const char *tag = crm_element_value(xml, PCMK_XA_OBJECT_TYPE);
     const char *ref = crm_element_value(xml, PCMK_XA_REFERENCE);
     const char *xpath = crm_element_value(xml, PCMK_XA_XPATH);
     const char *attr = crm_element_value(xml, PCMK_XA_ATTRIBUTE);
 
     if (tag == NULL) {
         // @COMPAT Deprecated since 1.1.12 (needed for rolling upgrades)
         tag = crm_element_value(xml, PCMK__XA_TAG);
     }
     if (ref == NULL) {
         // @COMPAT Deprecated since 1.1.12 (needed for rolling upgrades)
         ref = crm_element_value(xml, PCMK__XA_REF);
     }
 
     if ((tag == NULL) && (ref == NULL) && (xpath == NULL)) {
         // Schema should prevent this, but to be safe ...
         crm_trace("Ignoring ACL <%s> element without selection criteria",
                   xml->name);
         return NULL;
     }
 
     acl = calloc(1, sizeof (xml_acl_t));
     CRM_ASSERT(acl != NULL);
 
     acl->mode = mode;
     if (xpath) {
         acl->xpath = g_strdup(xpath);
         crm_trace("Unpacked ACL <%s> element using xpath: %s",
                   xml->name, acl->xpath);
 
     } else {
         GString *buf = g_string_sized_new(128);
 
         if ((ref != NULL) && (attr != NULL)) {
             // NOTE: schema currently does not allow this
             pcmk__g_strcat(buf, "//", pcmk__s(tag, "*"), "[@" PCMK_XA_ID "='",
                            ref, "' and @", attr, "]", NULL);
 
         } else if (ref != NULL) {
             pcmk__g_strcat(buf, "//", pcmk__s(tag, "*"), "[@" PCMK_XA_ID "='",
                            ref, "']", NULL);
 
         } else if (attr != NULL) {
             pcmk__g_strcat(buf, "//", pcmk__s(tag, "*"), "[@", attr, "]", NULL);
 
         } else {
             pcmk__g_strcat(buf, "//", pcmk__s(tag, "*"), NULL);
         }
 
         acl->xpath = buf->str;
 
         g_string_free(buf, FALSE);
         crm_trace("Unpacked ACL <%s> element as xpath: %s",
                   xml->name, acl->xpath);
     }
 
     return g_list_append(acls, acl);
 }
 
 /*!
  * \internal
  * \brief Unpack a user, group, or role subtree of the ACLs section
  *
  * \param[in]     acl_top    XML of entire ACLs section
  * \param[in]     acl_entry  XML of ACL element being unpacked
  * \param[in,out] acls       List of ACLs unpacked so far
  *
  * \return New head of (possibly modified) acls
  *
  * \note This function is recursive
  */
 static GList *
 parse_acl_entry(const xmlNode *acl_top, const xmlNode *acl_entry, GList *acls)
 {
     xmlNode *child = NULL;
 
     for (child = pcmk__xe_first_child(acl_entry); child;
          child = pcmk__xe_next(child)) {
         const char *tag = (const char *) child->name;
         const char *kind = crm_element_value(child, PCMK_XA_KIND);
 
         if (pcmk__xe_is(child, XML_ACL_TAG_PERMISSION)) {
             CRM_ASSERT(kind != NULL);
             crm_trace("Unpacking ACL <%s> element of kind '%s'", tag, kind);
             tag = kind;
         } else {
             crm_trace("Unpacking ACL <%s> element", tag);
         }
 
         if (pcmk__str_any_of(tag, PCMK_XE_ROLE, XML_ACL_TAG_ROLE_REFv1, NULL)) {
             const char *ref_role = crm_element_value(child, PCMK_XA_ID);
 
             if (ref_role) {
                 xmlNode *role = NULL;
 
                 for (role = pcmk__xe_first_child(acl_top); role;
                      role = pcmk__xe_next(role)) {
                     if (!strcmp(XML_ACL_TAG_ROLE, (const char *) role->name)) {
                         const char *role_id = crm_element_value(role,
                                                                 PCMK_XA_ID);
 
                         if (role_id && strcmp(ref_role, role_id) == 0) {
                             crm_trace("Unpacking referenced role '%s' in ACL <%s> element",
                                       role_id, acl_entry->name);
                             acls = parse_acl_entry(acl_top, role, acls);
                             break;
                         }
                     }
                 }
             }
 
         } else if (strcmp(XML_ACL_TAG_READ, tag) == 0) {
             acls = create_acl(child, acls, pcmk__xf_acl_read);
 
         } else if (strcmp(XML_ACL_TAG_WRITE, tag) == 0) {
             acls = create_acl(child, acls, pcmk__xf_acl_write);
 
         } else if (strcmp(XML_ACL_TAG_DENY, tag) == 0) {
             acls = create_acl(child, acls, pcmk__xf_acl_deny);
 
         } else {
             crm_warn("Ignoring unknown ACL %s '%s'",
                      (kind? "kind" : "element"), tag);
         }
     }
 
     return acls;
 }
 
 /*
     <acls>
       <acl_target id="l33t-haxor"><role id="auto-l33t-haxor"/></acl_target>
       <acl_role id="auto-l33t-haxor">
         <acl_permission id="crook-nothing" kind="deny" xpath="/cib"/>
       </acl_role>
       <acl_target id="niceguy">
         <role id="observer"/>
       </acl_target>
       <acl_role id="observer">
         <acl_permission id="observer-read-1" kind="read" xpath="/cib"/>
         <acl_permission id="observer-write-1" kind="write" xpath="//nvpair[@name='stonith-enabled']"/>
         <acl_permission id="observer-write-2" kind="write" xpath="//nvpair[@name='target-role']"/>
       </acl_role>
       <acl_target id="badidea"><role id="auto-badidea"/></acl_target>
       <acl_role id="auto-badidea">
         <acl_permission id="badidea-resources" kind="read" xpath="//meta_attributes"/>
         <acl_permission id="badidea-resources-2" kind="deny" reference="dummy-meta_attributes"/>
       </acl_role>
     </acls>
 */
 
 static const char *
 acl_to_text(enum xml_private_flags flags)
 {
     if (pcmk_is_set(flags, pcmk__xf_acl_deny)) {
         return "deny";
 
     } else if (pcmk_any_flags_set(flags, pcmk__xf_acl_write|pcmk__xf_acl_create)) {
         return "read/write";
 
     } else if (pcmk_is_set(flags, pcmk__xf_acl_read)) {
         return "read";
     }
     return "none";
 }
 
 void
 pcmk__apply_acl(xmlNode *xml)
 {
     GList *aIter = NULL;
     xml_doc_private_t *docpriv = xml->doc->_private;
     xml_node_private_t *nodepriv;
     xmlXPathObjectPtr xpathObj = NULL;
 
     if (!xml_acl_enabled(xml)) {
         crm_trace("Skipping ACLs for user '%s' because not enabled for this XML",
                   docpriv->user);
         return;
     }
 
     for (aIter = docpriv->acls; aIter != NULL; aIter = aIter->next) {
         int max = 0, lpc = 0;
         xml_acl_t *acl = aIter->data;
 
         xpathObj = xpath_search(xml, acl->xpath);
         max = numXpathResults(xpathObj);
 
         for (lpc = 0; lpc < max; lpc++) {
             xmlNode *match = getXpathResult(xpathObj, lpc);
 
             nodepriv = match->_private;
             pcmk__set_xml_flags(nodepriv, acl->mode);
 
             // Build a GString only if tracing is enabled
             pcmk__if_tracing(
                 {
                     GString *path = pcmk__element_xpath(match);
                     crm_trace("Applying %s ACL to %s matched by %s",
                               acl_to_text(acl->mode), path->str, acl->xpath);
                     g_string_free(path, TRUE);
                 },
                 {}
             );
         }
         crm_trace("Applied %s ACL %s (%d match%s)",
                   acl_to_text(acl->mode), acl->xpath, max,
                   ((max == 1)? "" : "es"));
         freeXpathObject(xpathObj);
     }
 }
 
 /*!
  * \internal
  * \brief Unpack ACLs for a given user into the
  * metadata of the target XML tree
  *
  * Taking the description of ACLs from the source XML tree and
  * marking up the target XML tree with access information for the
  * given user by tacking it onto the relevant nodes
  *
  * \param[in]     source  XML with ACL definitions
  * \param[in,out] target  XML that ACLs will be applied to
  * \param[in]     user    Username whose ACLs need to be unpacked
  */
 void
 pcmk__unpack_acl(xmlNode *source, xmlNode *target, const char *user)
 {
     xml_doc_private_t *docpriv = NULL;
 
     if ((target == NULL) || (target->doc == NULL)
         || (target->doc->_private == NULL)) {
         return;
     }
 
     docpriv = target->doc->_private;
     if (!pcmk_acl_required(user)) {
         crm_trace("Not unpacking ACLs because not required for user '%s'",
                   user);
 
     } else if (docpriv->acls == NULL) {
         xmlNode *acls = get_xpath_object("//" PCMK_XE_ACLS, source, LOG_NEVER);
 
         pcmk__str_update(&docpriv->user, user);
 
         if (acls) {
             xmlNode *child = NULL;
 
             for (child = pcmk__xe_first_child(acls); child;
                  child = pcmk__xe_next(child)) {
 
-                if (pcmk__xe_is(child, XML_ACL_TAG_USER)
+                if (pcmk__xe_is(child, PCMK_XE_ACL_TARGET)
                     || pcmk__xe_is(child, XML_ACL_TAG_USERv1)) {
                     const char *id = crm_element_value(child, PCMK_XA_NAME);
 
                     if (id == NULL) {
                         id = crm_element_value(child, PCMK_XA_ID);
                     }
 
                     if (id && strcmp(id, user) == 0) {
                         crm_debug("Unpacking ACLs for user '%s'", id);
                         docpriv->acls = parse_acl_entry(acls, child, docpriv->acls);
                     }
                 } else if (pcmk__xe_is(child, XML_ACL_TAG_GROUP)) {
                     const char *id = crm_element_value(child, PCMK_XA_NAME);
 
                     if (id == NULL) {
                         id = crm_element_value(child, PCMK_XA_ID);
                     }
 
                     if (id && pcmk__is_user_in_group(user,id)) {
                         crm_debug("Unpacking ACLs for group '%s'", id);
                         docpriv->acls = parse_acl_entry(acls, child, docpriv->acls);
                     }
                 }
             }
         }
     }
 }
 
 /*!
  * \internal
  * \brief Copy source to target and set xf_acl_enabled flag in target
  *
  * \param[in]     acl_source    XML with ACL definitions
  * \param[in,out] target        XML that ACLs will be applied to
  * \param[in]     user          Username whose ACLs need to be set
  */
 void
 pcmk__enable_acl(xmlNode *acl_source, xmlNode *target, const char *user)
 {
     pcmk__unpack_acl(acl_source, target, user);
     pcmk__set_xml_doc_flag(target, pcmk__xf_acl_enabled);
     pcmk__apply_acl(target);
 }
 
 static inline bool
 test_acl_mode(enum xml_private_flags allowed, enum xml_private_flags requested)
 {
     if (pcmk_is_set(allowed, pcmk__xf_acl_deny)) {
         return false;
 
     } else if (pcmk_all_flags_set(allowed, requested)) {
         return true;
 
     } else if (pcmk_is_set(requested, pcmk__xf_acl_read)
                && pcmk_is_set(allowed, pcmk__xf_acl_write)) {
         return true;
 
     } else if (pcmk_is_set(requested, pcmk__xf_acl_create)
                && pcmk_any_flags_set(allowed, pcmk__xf_acl_write|pcmk__xf_created)) {
         return true;
     }
     return false;
 }
 
 /*!
  * \internal
  * \brief Rid XML tree of all unreadable nodes and node properties
  *
  * \param[in,out] xml   Root XML node to be purged of attributes
  *
  * \return true if this node or any of its children are readable
  *         if false is returned, xml will be freed
  *
  * \note This function is recursive
  */
 static bool
 purge_xml_attributes(xmlNode *xml)
 {
     xmlNode *child = NULL;
     xmlAttr *xIter = NULL;
     bool readable_children = false;
     xml_node_private_t *nodepriv = xml->_private;
 
     if (test_acl_mode(nodepriv->flags, pcmk__xf_acl_read)) {
         crm_trace("%s[@" PCMK_XA_ID "=%s] is readable",
                   xml->name, ID(xml));
         return true;
     }
 
     xIter = xml->properties;
     while (xIter != NULL) {
         xmlAttr *tmp = xIter;
         const char *prop_name = (const char *)xIter->name;
 
         xIter = xIter->next;
         if (strcmp(prop_name, PCMK_XA_ID) == 0) {
             continue;
         }
 
         xmlUnsetProp(xml, tmp->name);
     }
 
     child = pcmk__xml_first_child(xml);
     while ( child != NULL ) {
         xmlNode *tmp = child;
 
         child = pcmk__xml_next(child);
         readable_children |= purge_xml_attributes(tmp);
     }
 
     if (!readable_children) {
         free_xml(xml); /* Nothing readable under here, purge completely */
     }
     return readable_children;
 }
 
 /*!
  * \brief Copy ACL-allowed portions of specified XML
  *
  * \param[in]  user        Username whose ACLs should be used
  * \param[in]  acl_source  XML containing ACLs
  * \param[in]  xml         XML to be copied
  * \param[out] result      Copy of XML portions readable via ACLs
  *
  * \return true if xml exists and ACLs are required for user, false otherwise
  * \note If this returns true, caller should use \p result rather than \p xml
  */
 bool
 xml_acl_filtered_copy(const char *user, xmlNode *acl_source, xmlNode *xml,
                       xmlNode **result)
 {
     GList *aIter = NULL;
     xmlNode *target = NULL;
     xml_doc_private_t *docpriv = NULL;
 
     *result = NULL;
     if ((xml == NULL) || !pcmk_acl_required(user)) {
         crm_trace("Not filtering XML because ACLs not required for user '%s'",
                   user);
         return false;
     }
 
     crm_trace("Filtering XML copy using user '%s' ACLs", user);
     target = copy_xml(xml);
     if (target == NULL) {
         return true;
     }
 
     pcmk__enable_acl(acl_source, target, user);
 
     docpriv = target->doc->_private;
     for(aIter = docpriv->acls; aIter != NULL && target; aIter = aIter->next) {
         int max = 0;
         xml_acl_t *acl = aIter->data;
 
         if (acl->mode != pcmk__xf_acl_deny) {
             /* Nothing to do */
 
         } else if (acl->xpath) {
             int lpc = 0;
             xmlXPathObjectPtr xpathObj = xpath_search(target, acl->xpath);
 
             max = numXpathResults(xpathObj);
             for(lpc = 0; lpc < max; lpc++) {
                 xmlNode *match = getXpathResult(xpathObj, lpc);
 
                 if (!purge_xml_attributes(match) && (match == target)) {
                     crm_trace("ACLs deny user '%s' access to entire XML document",
                               user);
                     freeXpathObject(xpathObj);
                     return true;
                 }
             }
             crm_trace("ACLs deny user '%s' access to %s (%d %s)",
                       user, acl->xpath, max,
                       pcmk__plural_alt(max, "match", "matches"));
             freeXpathObject(xpathObj);
         }
     }
 
     if (!purge_xml_attributes(target)) {
         crm_trace("ACLs deny user '%s' access to entire XML document", user);
         return true;
     }
 
     if (docpriv->acls) {
         g_list_free_full(docpriv->acls, free_acl);
         docpriv->acls = NULL;
 
     } else {
         crm_trace("User '%s' without ACLs denied access to entire XML document",
                   user);
         free_xml(target);
         target = NULL;
     }
 
     if (target) {
         *result = target;
     }
 
     return true;
 }
 
 /*!
  * \internal
  * \brief Check whether creation of an XML element is implicitly allowed
  *
  * Check whether XML is a "scaffolding" element whose creation is implicitly
  * allowed regardless of ACLs (that is, it is not in the ACL section and has
  * no attributes other than \c PCMK_XA_ID).
  *
  * \param[in] xml  XML element to check
  *
  * \return true if XML element is implicitly allowed, false otherwise
  */
 static bool
 implicitly_allowed(const xmlNode *xml)
 {
     GString *path = NULL;
 
     for (xmlAttr *prop = xml->properties; prop != NULL; prop = prop->next) {
         if (strcmp((const char *) prop->name, PCMK_XA_ID) != 0) {
             return false;
         }
     }
 
     path = pcmk__element_xpath(xml);
     CRM_ASSERT(path != NULL);
 
     if (strstr((const char *) path->str, "/" PCMK_XE_ACLS "/") != NULL) {
         g_string_free(path, TRUE);
         return false;
     }
 
     g_string_free(path, TRUE);
     return true;
 }
 
 #define display_id(xml) (ID(xml)? ID(xml) : "<unset>")
 
 /*!
  * \internal
  * \brief Drop XML nodes created in violation of ACLs
  *
  * Given an XML element, free all of its descendant nodes created in violation
  * of ACLs, with the exception of allowing "scaffolding" elements (i.e. those
  * that aren't in the ACL section and don't have any attributes other than
  * \c PCMK_XA_ID).
  *
  * \param[in,out] xml        XML to check
  * \param[in]     check_top  Whether to apply checks to argument itself
  *                           (if true, xml might get freed)
  *
  * \note This function is recursive
  */
 void
 pcmk__apply_creation_acl(xmlNode *xml, bool check_top)
 {
     xml_node_private_t *nodepriv = xml->_private;
 
     if (pcmk_is_set(nodepriv->flags, pcmk__xf_created)) {
         if (implicitly_allowed(xml)) {
             crm_trace("Creation of <%s> scaffolding with id=\"%s\""
                       " is implicitly allowed",
                       xml->name, display_id(xml));
 
         } else if (pcmk__check_acl(xml, NULL, pcmk__xf_acl_write)) {
             crm_trace("ACLs allow creation of <%s> with id=\"%s\"",
                       xml->name, display_id(xml));
 
         } else if (check_top) {
             crm_trace("ACLs disallow creation of <%s> with id=\"%s\"",
                       xml->name, display_id(xml));
             pcmk_free_xml_subtree(xml);
             return;
 
         } else {
             crm_notice("ACLs would disallow creation of %s<%s> with id=\"%s\"",
                        ((xml == xmlDocGetRootElement(xml->doc))? "root element " : ""),
                        xml->name, display_id(xml));
         }
     }
 
     for (xmlNode *cIter = pcmk__xml_first_child(xml); cIter != NULL; ) {
         xmlNode *child = cIter;
         cIter = pcmk__xml_next(cIter); /* In case it is free'd */
         pcmk__apply_creation_acl(child, true);
     }
 }
 
 /*!
  * \brief Check whether or not an XML node is ACL-denied
  *
  * \param[in]  xml node to check
  *
  * \return true if XML node exists and is ACL-denied, false otherwise
  */
 bool
 xml_acl_denied(const xmlNode *xml)
 {
     if (xml && xml->doc && xml->doc->_private){
         xml_doc_private_t *docpriv = xml->doc->_private;
 
         return pcmk_is_set(docpriv->flags, pcmk__xf_acl_denied);
     }
     return false;
 }
 
 void
 xml_acl_disable(xmlNode *xml)
 {
     if (xml_acl_enabled(xml)) {
         xml_doc_private_t *docpriv = xml->doc->_private;
 
         /* Catch anything that was created but shouldn't have been */
         pcmk__apply_acl(xml);
         pcmk__apply_creation_acl(xml, false);
         pcmk__clear_xml_flags(docpriv, pcmk__xf_acl_enabled);
     }
 }
 
 /*!
  * \brief Check whether or not an XML node is ACL-enabled
  *
  * \param[in]  xml node to check
  *
  * \return true if XML node exists and is ACL-enabled, false otherwise
  */
 bool
 xml_acl_enabled(const xmlNode *xml)
 {
     if (xml && xml->doc && xml->doc->_private){
         xml_doc_private_t *docpriv = xml->doc->_private;
 
         return pcmk_is_set(docpriv->flags, pcmk__xf_acl_enabled);
     }
     return false;
 }
 
 bool
 pcmk__check_acl(xmlNode *xml, const char *name, enum xml_private_flags mode)
 {
     CRM_ASSERT(xml);
     CRM_ASSERT(xml->doc);
     CRM_ASSERT(xml->doc->_private);
 
     if (pcmk__tracking_xml_changes(xml, false) && xml_acl_enabled(xml)) {
         xmlNode *parent = xml;
         xml_doc_private_t *docpriv = xml->doc->_private;
         GString *xpath = NULL;
 
         if (docpriv->acls == NULL) {
             pcmk__set_xml_doc_flag(xml, pcmk__xf_acl_denied);
 
             pcmk__if_tracing({}, return false);
             xpath = pcmk__element_xpath(xml);
             if (name != NULL) {
                 pcmk__g_strcat(xpath, "[@", name, "]", NULL);
             }
 
             qb_log_from_external_source(__func__, __FILE__,
                                         "User '%s' without ACLs denied %s "
                                         "access to %s", LOG_TRACE, __LINE__, 0,
                                         docpriv->user, acl_to_text(mode),
                                         (const char *) xpath->str);
             g_string_free(xpath, TRUE);
             return false;
         }
 
         /* Walk the tree upwards looking for xml_acl_* flags
          * - Creating an attribute requires write permissions for the node
          * - Creating a child requires write permissions for the parent
          */
 
         if (name) {
             xmlAttr *attr = xmlHasProp(xml, (pcmkXmlStr) name);
 
             if (attr && mode == pcmk__xf_acl_create) {
                 mode = pcmk__xf_acl_write;
             }
         }
 
         while (parent && parent->_private) {
             xml_node_private_t *nodepriv = parent->_private;
             if (test_acl_mode(nodepriv->flags, mode)) {
                 return true;
 
             } else if (pcmk_is_set(nodepriv->flags, pcmk__xf_acl_deny)) {
                 pcmk__set_xml_doc_flag(xml, pcmk__xf_acl_denied);
 
                 pcmk__if_tracing({}, return false);
                 xpath = pcmk__element_xpath(xml);
                 if (name != NULL) {
                     pcmk__g_strcat(xpath, "[@", name, "]", NULL);
                 }
 
                 qb_log_from_external_source(__func__, __FILE__,
                                             "%sACL denies user '%s' %s access "
                                             "to %s", LOG_TRACE, __LINE__, 0,
                                             (parent != xml)? "Parent ": "",
                                             docpriv->user, acl_to_text(mode),
                                             (const char *) xpath->str);
                 g_string_free(xpath, TRUE);
                 return false;
             }
             parent = parent->parent;
         }
 
         pcmk__set_xml_doc_flag(xml, pcmk__xf_acl_denied);
 
         pcmk__if_tracing({}, return false);
         xpath = pcmk__element_xpath(xml);
         if (name != NULL) {
             pcmk__g_strcat(xpath, "[@", name, "]", NULL);
         }
 
         qb_log_from_external_source(__func__, __FILE__,
                                     "Default ACL denies user '%s' %s access to "
                                     "%s", LOG_TRACE, __LINE__, 0,
                                     docpriv->user, acl_to_text(mode),
                                     (const char *) xpath->str);
         g_string_free(xpath, TRUE);
         return false;
     }
 
     return true;
 }
 
 /*!
  * \brief Check whether ACLs are required for a given user
  *
  * \param[in]  User name to check
  *
  * \return true if the user requires ACLs, false otherwise
  */
 bool
 pcmk_acl_required(const char *user)
 {
     if (pcmk__str_empty(user)) {
         crm_trace("ACLs not required because no user set");
         return false;
 
     } else if (!strcmp(user, CRM_DAEMON_USER) || !strcmp(user, "root")) {
         crm_trace("ACLs not required for privileged user %s", user);
         return false;
     }
     crm_trace("ACLs required for %s", user);
     return true;
 }
 
 char *
 pcmk__uid2username(uid_t uid)
 {
     char *result = NULL;
     struct passwd *pwent = getpwuid(uid);
 
     if (pwent == NULL) {
         crm_perror(LOG_INFO, "Cannot get user details for user ID %d", uid);
         return NULL;
     }
     pcmk__str_update(&result, pwent->pw_name);
     return result;
 }
 
 /*!
  * \internal
  * \brief Set the ACL user field properly on an XML request
  *
  * Multiple user names are potentially involved in an XML request: the effective
  * user of the current process; the user name known from an IPC client
  * connection; and the user name obtained from the request itself, whether by
  * the current standard XML attribute name or an older legacy attribute name.
  * This function chooses the appropriate one that should be used for ACLs, sets
  * it in the request (using the standard attribute name, and the legacy name if
  * given), and returns it.
  *
  * \param[in,out] request    XML request to update
  * \param[in]     field      Alternate name for ACL user name XML attribute
  * \param[in]     peer_user  User name as known from IPC connection
  *
  * \return ACL user name actually used
  */
 const char *
 pcmk__update_acl_user(xmlNode *request, const char *field,
                       const char *peer_user)
 {
     static const char *effective_user = NULL;
     const char *requested_user = NULL;
     const char *user = NULL;
 
     if (effective_user == NULL) {
         effective_user = pcmk__uid2username(geteuid());
         if (effective_user == NULL) {
             effective_user = strdup("#unprivileged");
             CRM_CHECK(effective_user != NULL, return NULL);
             crm_err("Unable to determine effective user, assuming unprivileged for ACLs");
         }
     }
 
-    requested_user = crm_element_value(request, XML_ACL_TAG_USER);
+    requested_user = crm_element_value(request, PCMK_XE_ACL_TARGET);
     if (requested_user == NULL) {
         /* @COMPAT rolling upgrades <=1.1.11
          *
          * field is checked for backward compatibility with older versions that
-         * did not use XML_ACL_TAG_USER.
+         * did not use PCMK_XE_ACL_TARGET.
          */
         requested_user = crm_element_value(request, field);
     }
 
     if (!pcmk__is_privileged(effective_user)) {
         /* We're not running as a privileged user, set or overwrite any existing
-         * value for $XML_ACL_TAG_USER
+         * value for PCMK_XE_ACL_TARGET
          */
         user = effective_user;
 
     } else if (peer_user == NULL && requested_user == NULL) {
         /* No user known or requested, use 'effective_user' and make sure one is
          * set for the request
          */
         user = effective_user;
 
     } else if (peer_user == NULL) {
         /* No user known, trusting 'requested_user' */
         user = requested_user;
 
     } else if (!pcmk__is_privileged(peer_user)) {
         /* The peer is not a privileged user, set or overwrite any existing
-         * value for $XML_ACL_TAG_USER
+         * value for PCMK_XE_ACL_TARGET
          */
         user = peer_user;
 
     } else if (requested_user == NULL) {
         /* Even if we're privileged, make sure there is always a value set */
         user = peer_user;
 
     } else {
         /* Legal delegation to 'requested_user' */
         user = requested_user;
     }
 
     // This requires pointer comparison, not string comparison
-    if (user != crm_element_value(request, XML_ACL_TAG_USER)) {
-        crm_xml_add(request, XML_ACL_TAG_USER, user);
+    if (user != crm_element_value(request, PCMK_XE_ACL_TARGET)) {
+        crm_xml_add(request, PCMK_XE_ACL_TARGET, user);
     }
 
     if (field != NULL && user != crm_element_value(request, field)) {
         crm_xml_add(request, field, user);
     }
 
     return requested_user;
 }