diff --git a/lib/common/ipc_server.c b/lib/common/ipc_server.c
index 79cf17f549..2213f1163d 100644
--- a/lib/common/ipc_server.c
+++ b/lib/common/ipc_server.c
@@ -1,1014 +1,1014 @@
 /*
  * Copyright 2004-2022 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 <errno.h>
 #include <bzlib.h>
 #include <sys/stat.h>
 #include <sys/types.h>
 
 #include <crm/crm.h>
 #include <crm/msg_xml.h>
 #include <crm/common/ipc.h>
 #include <crm/common/ipc_internal.h>
 #include "crmcommon_private.h"
 
 /* Evict clients whose event queue grows this large (by default) */
 #define PCMK_IPC_DEFAULT_QUEUE_MAX 500
 
 static GHashTable *client_connections = NULL;
 
 /*!
  * \internal
  * \brief Count IPC clients
  *
  * \return Number of active IPC client connections
  */
 guint
 pcmk__ipc_client_count(void)
 {
     return client_connections? g_hash_table_size(client_connections) : 0;
 }
 
 /*!
  * \internal
  * \brief Execute a function for each active IPC client connection
  *
  * \param[in]     func       Function to call
  * \param[in,out] user_data  Pointer to pass to function
  *
  * \note The parameters are the same as for g_hash_table_foreach().
  */
 void
 pcmk__foreach_ipc_client(GHFunc func, gpointer user_data)
 {
     if ((func != NULL) && (client_connections != NULL)) {
         g_hash_table_foreach(client_connections, func, user_data);
     }
 }
 
 pcmk__client_t *
 pcmk__find_client(const qb_ipcs_connection_t *c)
 {
     if (client_connections) {
         return g_hash_table_lookup(client_connections, c);
     }
 
     crm_trace("No client found for %p", c);
     return NULL;
 }
 
 pcmk__client_t *
 pcmk__find_client_by_id(const char *id)
 {
     if ((client_connections != NULL) && (id != NULL)) {
         gpointer key;
         pcmk__client_t *client = NULL;
         GHashTableIter iter;
 
         g_hash_table_iter_init(&iter, client_connections);
         while (g_hash_table_iter_next(&iter, &key, (gpointer *) & client)) {
             if (strcmp(client->id, id) == 0) {
                 return client;
             }
         }
     }
     crm_trace("No client found with id='%s'", pcmk__s(id, ""));
     return NULL;
 }
 
 /*!
  * \internal
  * \brief Get a client identifier for use in log messages
  *
  * \param[in] c  Client
  *
  * \return Client's name, client's ID, or a string literal, as available
  * \note This is intended to be used in format strings like "client %s".
  */
 const char *
 pcmk__client_name(const pcmk__client_t *c)
 {
     if (c == NULL) {
         return "(unspecified)";
 
     } else if (c->name != NULL) {
         return c->name;
 
     } else if (c->id != NULL) {
         return c->id;
 
     } else {
         return "(unidentified)";
     }
 }
 
 void
 pcmk__client_cleanup(void)
 {
     if (client_connections != NULL) {
         int active = g_hash_table_size(client_connections);
 
         if (active > 0) {
             crm_warn("Exiting with %d active IPC client%s",
                      active, pcmk__plural_s(active));
         }
         g_hash_table_destroy(client_connections);
         client_connections = NULL;
     }
 }
 
 void
 pcmk__drop_all_clients(qb_ipcs_service_t *service)
 {
     qb_ipcs_connection_t *c = NULL;
 
     if (service == NULL) {
         return;
     }
 
     c = qb_ipcs_connection_first_get(service);
 
     while (c != NULL) {
         qb_ipcs_connection_t *last = c;
 
         c = qb_ipcs_connection_next_get(service, last);
 
         /* There really shouldn't be anyone connected at this point */
         crm_notice("Disconnecting client %p, pid=%d...",
                    last, pcmk__client_pid(last));
         qb_ipcs_disconnect(last);
         qb_ipcs_connection_unref(last);
     }
 }
 
 /*!
  * \internal
  * \brief Allocate a new pcmk__client_t object based on an IPC connection
  *
- * \param[in,out] c           IPC connection (NULL to allocate generic client)
- * \param[in]     key         Connection table key (NULL to use sane default)
- * \param[in]     uid_client  UID corresponding to c (ignored if c is NULL)
+ * \param[in] c           IPC connection (NULL to allocate generic client)
+ * \param[in] key         Connection table key (NULL to use sane default)
+ * \param[in] uid_client  UID corresponding to c (ignored if c is NULL)
  *
  * \return Pointer to new pcmk__client_t (or NULL on error)
  */
 static pcmk__client_t *
 client_from_connection(qb_ipcs_connection_t *c, void *key, uid_t uid_client)
 {
     pcmk__client_t *client = calloc(1, sizeof(pcmk__client_t));
 
     if (client == NULL) {
         crm_perror(LOG_ERR, "Allocating client");
         return NULL;
     }
 
     if (c) {
         client->user = pcmk__uid2username(uid_client);
         if (client->user == NULL) {
             client->user = strdup("#unprivileged");
             CRM_CHECK(client->user != NULL, free(client); return NULL);
             crm_err("Unable to enforce ACLs for user ID %d, assuming unprivileged",
                     uid_client);
         }
         client->ipcs = c;
         pcmk__set_client_flags(client, pcmk__client_ipc);
         client->pid = pcmk__client_pid(c);
         if (key == NULL) {
             key = c;
         }
     }
 
     client->id = crm_generate_uuid();
     if (client->id == NULL) {
         crm_err("Could not generate UUID for client");
         free(client->user);
         free(client);
         return NULL;
     }
     if (key == NULL) {
         key = client->id;
     }
     if (client_connections == NULL) {
         crm_trace("Creating IPC client table");
         client_connections = g_hash_table_new(g_direct_hash, g_direct_equal);
     }
     g_hash_table_insert(client_connections, key, client);
     return client;
 }
 
 /*!
  * \brief Allocate a new pcmk__client_t object and generate its ID
  *
  * \param[in] key  What to use as connections hash table key (NULL to use ID)
  *
  * \return Pointer to new pcmk__client_t (asserts on failure)
  */
 pcmk__client_t *
 pcmk__new_unauth_client(void *key)
 {
     pcmk__client_t *client = client_from_connection(NULL, key, 0);
 
     CRM_ASSERT(client != NULL);
     return client;
 }
 
 pcmk__client_t *
 pcmk__new_client(qb_ipcs_connection_t *c, uid_t uid_client, gid_t gid_client)
 {
     gid_t uid_cluster = 0;
     gid_t gid_cluster = 0;
 
     pcmk__client_t *client = NULL;
 
     CRM_CHECK(c != NULL, return NULL);
 
     if (pcmk_daemon_user(&uid_cluster, &gid_cluster) < 0) {
         static bool need_log = TRUE;
 
         if (need_log) {
             crm_warn("Could not find user and group IDs for user %s",
                      CRM_DAEMON_USER);
             need_log = FALSE;
         }
     }
 
     if (uid_client != 0) {
         crm_trace("Giving group %u access to new IPC connection", gid_cluster);
         /* Passing -1 to chown(2) means don't change */
         qb_ipcs_connection_auth_set(c, -1, gid_cluster, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP);
     }
 
     /* TODO: Do our own auth checking, return NULL if unauthorized */
     client = client_from_connection(c, NULL, uid_client);
     if (client == NULL) {
         return NULL;
     }
 
     if ((uid_client == 0) || (uid_client == uid_cluster)) {
         /* Remember when a connection came from root or hacluster */
         pcmk__set_client_flags(client, pcmk__client_privileged);
     }
 
     crm_debug("New IPC client %s for PID %u with uid %d and gid %d",
               client->id, client->pid, uid_client, gid_client);
     return client;
 }
 
 static struct iovec *
 pcmk__new_ipc_event(void)
 {
     struct iovec *iov = calloc(2, sizeof(struct iovec));
 
     CRM_ASSERT(iov != NULL);
     return iov;
 }
 
 /*!
  * \brief Free an I/O vector created by pcmk__ipc_prepare_iov()
  *
  * \param[in,out] event  I/O vector to free
  */
 void
 pcmk_free_ipc_event(struct iovec *event)
 {
     if (event != NULL) {
         free(event[0].iov_base);
         free(event[1].iov_base);
         free(event);
     }
 }
 
 static void
 free_event(gpointer data)
 {
     pcmk_free_ipc_event((struct iovec *) data);
 }
 
 static void
 add_event(pcmk__client_t *c, struct iovec *iov)
 {
     if (c->event_queue == NULL) {
         c->event_queue = g_queue_new();
     }
     g_queue_push_tail(c->event_queue, iov);
 }
 
 void
 pcmk__free_client(pcmk__client_t *c)
 {
     if (c == NULL) {
         return;
     }
 
     if (client_connections) {
         if (c->ipcs) {
             crm_trace("Destroying %p/%p (%d remaining)",
                       c, c->ipcs, g_hash_table_size(client_connections) - 1);
             g_hash_table_remove(client_connections, c->ipcs);
 
         } else {
             crm_trace("Destroying remote connection %p (%d remaining)",
                       c, g_hash_table_size(client_connections) - 1);
             g_hash_table_remove(client_connections, c->id);
         }
     }
 
     if (c->event_timer) {
         g_source_remove(c->event_timer);
     }
 
     if (c->event_queue) {
         crm_debug("Destroying %d events", g_queue_get_length(c->event_queue));
         g_queue_free_full(c->event_queue, free_event);
     }
 
     free(c->id);
     free(c->name);
     free(c->user);
     if (c->remote) {
         if (c->remote->auth_timeout) {
             g_source_remove(c->remote->auth_timeout);
         }
         free(c->remote->buffer);
         free(c->remote);
     }
     free(c);
 }
 
 /*!
  * \internal
  * \brief Raise IPC eviction threshold for a client, if allowed
  *
  * \param[in,out] client     Client to modify
  * \param[in]     qmax       New threshold (as non-NULL string)
  *
  * \return true if change was allowed, false otherwise
  */
 bool
 pcmk__set_client_queue_max(pcmk__client_t *client, const char *qmax)
 {
     if (pcmk_is_set(client->flags, pcmk__client_privileged)) {
         long long qmax_ll;
 
         if ((pcmk__scan_ll(qmax, &qmax_ll, 0LL) == pcmk_rc_ok)
             && (qmax_ll > 0LL) && (qmax_ll <= UINT_MAX)) {
             client->queue_max = (unsigned int) qmax_ll;
             return true;
         }
     }
     return false;
 }
 
 int
 pcmk__client_pid(qb_ipcs_connection_t *c)
 {
     struct qb_ipcs_connection_stats stats;
 
     stats.client_pid = 0;
     qb_ipcs_connection_stats_get(c, &stats, 0);
     return stats.client_pid;
 }
 
 /*!
  * \internal
  * \brief Retrieve message XML from data read from client IPC
  *
  * \param[in,out]  c       IPC client connection
  * \param[in]      data    Data read from client connection
  * \param[out]     id      Where to store message ID from libqb header
  * \param[out]     flags   Where to store flags from libqb header
  *
  * \return Message XML on success, NULL otherwise
  */
 xmlNode *
 pcmk__client_data2xml(pcmk__client_t *c, void *data, uint32_t *id,
                       uint32_t *flags)
 {
     xmlNode *xml = NULL;
     char *uncompressed = NULL;
     char *text = ((char *)data) + sizeof(pcmk__ipc_header_t);
     pcmk__ipc_header_t *header = data;
 
     if (!pcmk__valid_ipc_header(header)) {
         return NULL;
     }
 
     if (id) {
         *id = ((struct qb_ipc_response_header *)data)->id;
     }
     if (flags) {
         *flags = header->flags;
     }
 
     if (pcmk_is_set(header->flags, crm_ipc_proxied)) {
         /* Mark this client as being the endpoint of a proxy connection.
          * Proxy connections responses are sent on the event channel, to avoid
          * blocking the controller serving as proxy.
          */
         pcmk__set_client_flags(c, pcmk__client_proxied);
     }
 
     if (header->size_compressed) {
         int rc = 0;
         unsigned int size_u = 1 + header->size_uncompressed;
         uncompressed = calloc(1, size_u);
 
         crm_trace("Decompressing message data %u bytes into %u bytes",
                   header->size_compressed, size_u);
 
         rc = BZ2_bzBuffToBuffDecompress(uncompressed, &size_u, text, header->size_compressed, 1, 0);
         text = uncompressed;
 
         if (rc != BZ_OK) {
             crm_err("Decompression failed: %s " CRM_XS " bzerror=%d",
                     bz2_strerror(rc), rc);
             free(uncompressed);
             return NULL;
         }
     }
 
     CRM_ASSERT(text[header->size_uncompressed - 1] == 0);
 
     xml = string2xml(text);
     crm_log_xml_trace(xml, "[IPC received]");
 
     free(uncompressed);
     return xml;
 }
 
 static int crm_ipcs_flush_events(pcmk__client_t *c);
 
 static gboolean
 crm_ipcs_flush_events_cb(gpointer data)
 {
     pcmk__client_t *c = data;
 
     c->event_timer = 0;
     crm_ipcs_flush_events(c);
     return FALSE;
 }
 
 /*!
  * \internal
  * \brief Add progressive delay before next event queue flush
  *
  * \param[in,out] c          Client connection to add delay to
  * \param[in]     queue_len  Current event queue length
  */
 static inline void
 delay_next_flush(pcmk__client_t *c, unsigned int queue_len)
 {
     /* Delay a maximum of 1.5 seconds */
     guint delay = (queue_len < 5)? (1000 + 100 * queue_len) : 1500;
 
     c->event_timer = g_timeout_add(delay, crm_ipcs_flush_events_cb, c);
 }
 
 /*!
  * \internal
  * \brief Send client any messages in its queue
  *
  * \param[in,out] c  Client to flush
  *
  * \return Standard Pacemaker return value
  */
 static int
 crm_ipcs_flush_events(pcmk__client_t *c)
 {
     int rc = pcmk_rc_ok;
     ssize_t qb_rc = 0;
     unsigned int sent = 0;
     unsigned int queue_len = 0;
 
     if (c == NULL) {
         return rc;
 
     } else if (c->event_timer) {
         /* There is already a timer, wait until it goes off */
         crm_trace("Timer active for %p - %d", c->ipcs, c->event_timer);
         return rc;
     }
 
     if (c->event_queue) {
         queue_len = g_queue_get_length(c->event_queue);
     }
     while (sent < 100) {
         pcmk__ipc_header_t *header = NULL;
         struct iovec *event = NULL;
 
         if (c->event_queue) {
             // We don't pop unless send is successful
             event = g_queue_peek_head(c->event_queue);
         }
         if (event == NULL) { // Queue is empty
             break;
         }
 
         qb_rc = qb_ipcs_event_sendv(c->ipcs, event, 2);
         if (qb_rc < 0) {
             rc = (int) -qb_rc;
             break;
         }
         event = g_queue_pop_head(c->event_queue);
 
         sent++;
         header = event[0].iov_base;
         if (header->size_compressed) {
             crm_trace("Event %d to %p[%d] (%lld compressed bytes) sent",
                       header->qb.id, c->ipcs, c->pid, (long long) qb_rc);
         } else {
             crm_trace("Event %d to %p[%d] (%lld bytes) sent: %.120s",
                       header->qb.id, c->ipcs, c->pid, (long long) qb_rc,
                       (char *) (event[1].iov_base));
         }
         pcmk_free_ipc_event(event);
     }
 
     queue_len -= sent;
     if (sent > 0 || queue_len) {
         crm_trace("Sent %d events (%d remaining) for %p[%d]: %s (%lld)",
                   sent, queue_len, c->ipcs, c->pid,
                   pcmk_rc_str(rc), (long long) qb_rc);
     }
 
     if (queue_len) {
 
         /* Allow clients to briefly fall behind on processing incoming messages,
          * but drop completely unresponsive clients so the connection doesn't
          * consume resources indefinitely.
          */
         if (queue_len > QB_MAX(c->queue_max, PCMK_IPC_DEFAULT_QUEUE_MAX)) {
             if ((c->queue_backlog <= 1) || (queue_len < c->queue_backlog)) {
                 /* Don't evict for a new or shrinking backlog */
                 crm_warn("Client with process ID %u has a backlog of %u messages "
                          CRM_XS " %p", c->pid, queue_len, c->ipcs);
             } else {
                 crm_err("Evicting client with process ID %u due to backlog of %u messages "
                          CRM_XS " %p", c->pid, queue_len, c->ipcs);
                 c->queue_backlog = 0;
                 qb_ipcs_disconnect(c->ipcs);
                 return rc;
             }
         }
 
         c->queue_backlog = queue_len;
         delay_next_flush(c, queue_len);
 
     } else {
         /* Event queue is empty, there is no backlog */
         c->queue_backlog = 0;
     }
 
     return rc;
 }
 
 /*!
  * \internal
  * \brief Create an I/O vector for sending an IPC XML message
  *
  * \param[in]     request        Identifier for libqb response header
  * \param[in,out] message        XML message to send
  * \param[in]     max_send_size  If 0, default IPC buffer size is used
  * \param[out]    result         Where to store prepared I/O vector
  * \param[out]    bytes          Size of prepared data in bytes
  *
  * \return Standard Pacemaker return code
  */
 int
 pcmk__ipc_prepare_iov(uint32_t request, xmlNode *message,
                       uint32_t max_send_size, struct iovec **result,
                       ssize_t *bytes)
 {
     static unsigned int biggest = 0;
     struct iovec *iov;
     unsigned int total = 0;
     char *compressed = NULL;
     char *buffer = NULL;
     pcmk__ipc_header_t *header = NULL;
 
     if ((message == NULL) || (result == NULL)) {
         return EINVAL;
     }
 
     header = calloc(1, sizeof(pcmk__ipc_header_t));
     if (header == NULL) {
        return ENOMEM; /* errno mightn't be set by allocator */
     }
 
     buffer = dump_xml_unformatted(message);
 
     if (max_send_size == 0) {
         max_send_size = crm_ipc_default_buffer_size();
     }
     CRM_LOG_ASSERT(max_send_size != 0);
 
     *result = NULL;
     iov = pcmk__new_ipc_event();
     iov[0].iov_len = sizeof(pcmk__ipc_header_t);
     iov[0].iov_base = header;
 
     header->version = PCMK__IPC_VERSION;
     header->size_uncompressed = 1 + strlen(buffer);
     total = iov[0].iov_len + header->size_uncompressed;
 
     if (total < max_send_size) {
         iov[1].iov_base = buffer;
         iov[1].iov_len = header->size_uncompressed;
 
     } else {
         unsigned int new_size = 0;
 
         if (pcmk__compress(buffer, (unsigned int) header->size_uncompressed,
                            (unsigned int) max_send_size, &compressed,
                            &new_size) == pcmk_rc_ok) {
 
             pcmk__set_ipc_flags(header->flags, "send data", crm_ipc_compressed);
             header->size_compressed = new_size;
 
             iov[1].iov_len = header->size_compressed;
             iov[1].iov_base = compressed;
 
             free(buffer);
 
             biggest = QB_MAX(header->size_compressed, biggest);
 
         } else {
             crm_log_xml_trace(message, "EMSGSIZE");
             biggest = QB_MAX(header->size_uncompressed, biggest);
 
             crm_err("Could not compress %u-byte message into less than IPC "
                     "limit of %u bytes; set PCMK_ipc_buffer to higher value "
                     "(%u bytes suggested)",
                     header->size_uncompressed, max_send_size, 4 * biggest);
 
             free(compressed);
             free(buffer);
             pcmk_free_ipc_event(iov);
             return EMSGSIZE;
         }
     }
 
     header->qb.size = iov[0].iov_len + iov[1].iov_len;
     header->qb.id = (int32_t)request;    /* Replying to a specific request */
 
     *result = iov;
     CRM_ASSERT(header->qb.size > 0);
     if (bytes != NULL) {
         *bytes = header->qb.size;
     }
     return pcmk_rc_ok;
 }
 
 int
 pcmk__ipc_send_iov(pcmk__client_t *c, struct iovec *iov, uint32_t flags)
 {
     int rc = pcmk_rc_ok;
     static uint32_t id = 1;
     pcmk__ipc_header_t *header = iov[0].iov_base;
 
     if (c->flags & pcmk__client_proxied) {
         /* _ALL_ replies to proxied connections need to be sent as events */
         if (!pcmk_is_set(flags, crm_ipc_server_event)) {
             /* The proxied flag lets us know this was originally meant to be a
              * response, even though we're sending it over the event channel.
              */
             pcmk__set_ipc_flags(flags, "server event",
                                 crm_ipc_server_event
                                 |crm_ipc_proxied_relay_response);
         }
     }
 
     pcmk__set_ipc_flags(header->flags, "server event", flags);
     if (flags & crm_ipc_server_event) {
         header->qb.id = id++;   /* We don't really use it, but doesn't hurt to set one */
 
         if (flags & crm_ipc_server_free) {
             crm_trace("Sending the original to %p[%d]", c->ipcs, c->pid);
             add_event(c, iov);
 
         } else {
             struct iovec *iov_copy = pcmk__new_ipc_event();
 
             crm_trace("Sending a copy to %p[%d]", c->ipcs, c->pid);
             iov_copy[0].iov_len = iov[0].iov_len;
             iov_copy[0].iov_base = malloc(iov[0].iov_len);
             memcpy(iov_copy[0].iov_base, iov[0].iov_base, iov[0].iov_len);
 
             iov_copy[1].iov_len = iov[1].iov_len;
             iov_copy[1].iov_base = malloc(iov[1].iov_len);
             memcpy(iov_copy[1].iov_base, iov[1].iov_base, iov[1].iov_len);
 
             add_event(c, iov_copy);
         }
 
     } else {
         ssize_t qb_rc;
 
         CRM_LOG_ASSERT(header->qb.id != 0);     /* Replying to a specific request */
 
         qb_rc = qb_ipcs_response_sendv(c->ipcs, iov, 2);
         if (qb_rc < header->qb.size) {
             if (qb_rc < 0) {
                 rc = (int) -qb_rc;
             }
             crm_notice("Response %d to pid %d failed: %s "
                        CRM_XS " bytes=%u rc=%lld ipcs=%p",
                        header->qb.id, c->pid, pcmk_rc_str(rc),
                        header->qb.size, (long long) qb_rc, c->ipcs);
 
         } else {
             crm_trace("Response %d sent, %lld bytes to %p[%d]",
                       header->qb.id, (long long) qb_rc, c->ipcs, c->pid);
         }
 
         if (flags & crm_ipc_server_free) {
             pcmk_free_ipc_event(iov);
         }
     }
 
     if (flags & crm_ipc_server_event) {
         rc = crm_ipcs_flush_events(c);
     } else {
         crm_ipcs_flush_events(c);
     }
 
     if ((rc == EPIPE) || (rc == ENOTCONN)) {
         crm_trace("Client %p disconnected", c->ipcs);
     }
     return rc;
 }
 
 int
 pcmk__ipc_send_xml(pcmk__client_t *c, uint32_t request, xmlNode *message,
                    uint32_t flags)
 {
     struct iovec *iov = NULL;
     int rc = pcmk_rc_ok;
 
     if (c == NULL) {
         return EINVAL;
     }
     rc = pcmk__ipc_prepare_iov(request, message, crm_ipc_default_buffer_size(),
                                &iov, NULL);
     if (rc == pcmk_rc_ok) {
         pcmk__set_ipc_flags(flags, "send data", crm_ipc_server_free);
         rc = pcmk__ipc_send_iov(c, iov, flags);
     } else {
         pcmk_free_ipc_event(iov);
         crm_notice("IPC message to pid %d failed: %s " CRM_XS " rc=%d",
                    c->pid, pcmk_rc_str(rc), rc);
     }
     return rc;
 }
 
 /*!
  * \internal
  * \brief Create an acknowledgement with a status code to send to a client
  *
  * \param[in] function  Calling function
  * \param[in] line      Source file line within calling function
  * \param[in] flags     IPC flags to use when sending
  * \param[in] tag       Element name to use for acknowledgement
  * \param[in] ver       IPC protocol version (can be NULL)
  * \param[in] status    Exit status code to add to ack
  *
  * \return Newly created XML for ack
  * \note The caller is responsible for freeing the return value with free_xml().
  */
 xmlNode *
 pcmk__ipc_create_ack_as(const char *function, int line, uint32_t flags,
                         const char *tag, const char *ver, crm_exit_t status)
 {
     xmlNode *ack = NULL;
 
     if (pcmk_is_set(flags, crm_ipc_client_response)) {
         ack = create_xml_node(NULL, tag);
         crm_xml_add(ack, "function", function);
         crm_xml_add_int(ack, "line", line);
         crm_xml_add_int(ack, "status", (int) status);
         crm_xml_add(ack, PCMK__XA_IPC_PROTO_VERSION, ver);
     }
     return ack;
 }
 
 /*!
  * \internal
  * \brief Send an acknowledgement with a status code to a client
  *
  * \param[in] function  Calling function
  * \param[in] line      Source file line within calling function
  * \param[in] c         Client to send ack to
  * \param[in] request   Request ID being replied to
  * \param[in] flags     IPC flags to use when sending
  * \param[in] tag       Element name to use for acknowledgement
  * \param[in] ver       IPC protocol version (can be NULL)
  * \param[in] status    Status code to send with acknowledgement
  *
  * \return Standard Pacemaker return code
  */
 int
 pcmk__ipc_send_ack_as(const char *function, int line, pcmk__client_t *c,
                       uint32_t request, uint32_t flags, const char *tag,
                       const char *ver, crm_exit_t status)
 {
     int rc = pcmk_rc_ok;
     xmlNode *ack = pcmk__ipc_create_ack_as(function, line, flags, tag, ver, status);
 
     if (ack != NULL) {
         crm_trace("Ack'ing IPC message from client %s as <%s status=%d>",
                   pcmk__client_name(c), tag, status);
         c->request_id = 0;
         rc = pcmk__ipc_send_xml(c, request, ack, flags);
         free_xml(ack);
     }
     return rc;
 }
 
 /*!
  * \internal
  * \brief Add an IPC server to the main loop for the pacemaker-based API
  *
  * \param[out] ipcs_ro   New IPC server for read-only pacemaker-based API
  * \param[out] ipcs_rw   New IPC server for read/write pacemaker-based API
  * \param[out] ipcs_shm  New IPC server for shared-memory pacemaker-based API
  * \param[in]  ro_cb     IPC callbacks for read-only API
  * \param[in]  rw_cb     IPC callbacks for read/write and shared-memory APIs
  *
  * \note This function exits fatally if unable to create the servers.
  */
 void pcmk__serve_based_ipc(qb_ipcs_service_t **ipcs_ro,
                            qb_ipcs_service_t **ipcs_rw,
                            qb_ipcs_service_t **ipcs_shm,
                            struct qb_ipcs_service_handlers *ro_cb,
                            struct qb_ipcs_service_handlers *rw_cb)
 {
     *ipcs_ro = mainloop_add_ipc_server(PCMK__SERVER_BASED_RO,
                                        QB_IPC_NATIVE, ro_cb);
 
     *ipcs_rw = mainloop_add_ipc_server(PCMK__SERVER_BASED_RW,
                                        QB_IPC_NATIVE, rw_cb);
 
     *ipcs_shm = mainloop_add_ipc_server(PCMK__SERVER_BASED_SHM,
                                         QB_IPC_SHM, rw_cb);
 
     if (*ipcs_ro == NULL || *ipcs_rw == NULL || *ipcs_shm == NULL) {
         crm_err("Failed to create the CIB manager: exiting and inhibiting respawn");
         crm_warn("Verify pacemaker and pacemaker_remote are not both enabled");
         crm_exit(CRM_EX_FATAL);
     }
 }
 
 /*!
  * \internal
  * \brief Destroy IPC servers for pacemaker-based API
  *
  * \param[out] ipcs_ro   IPC server for read-only pacemaker-based API
  * \param[out] ipcs_rw   IPC server for read/write pacemaker-based API
  * \param[out] ipcs_shm  IPC server for shared-memory pacemaker-based API
  *
  * \note This is a convenience function for calling qb_ipcs_destroy() for each
  *       argument.
  */
 void
 pcmk__stop_based_ipc(qb_ipcs_service_t *ipcs_ro,
                      qb_ipcs_service_t *ipcs_rw,
                      qb_ipcs_service_t *ipcs_shm)
 {
     qb_ipcs_destroy(ipcs_ro);
     qb_ipcs_destroy(ipcs_rw);
     qb_ipcs_destroy(ipcs_shm);
 }
 
 /*!
  * \internal
  * \brief Add an IPC server to the main loop for the pacemaker-controld API
  *
  * \param[in] cb  IPC callbacks
  *
  * \return Newly created IPC server
  */
 qb_ipcs_service_t *
 pcmk__serve_controld_ipc(struct qb_ipcs_service_handlers *cb)
 {
     return mainloop_add_ipc_server(CRM_SYSTEM_CRMD, QB_IPC_NATIVE, cb);
 }
 
 /*!
  * \internal
  * \brief Add an IPC server to the main loop for the pacemaker-attrd API
  *
  * \param[out] ipcs  Where to store newly created IPC server
  * \param[in] cb  IPC callbacks
  *
  * \note This function exits fatally if unable to create the servers.
  */
 void
 pcmk__serve_attrd_ipc(qb_ipcs_service_t **ipcs,
                       struct qb_ipcs_service_handlers *cb)
 {
     *ipcs = mainloop_add_ipc_server(T_ATTRD, QB_IPC_NATIVE, cb);
 
     if (*ipcs == NULL) {
         crm_err("Failed to create pacemaker-attrd server: exiting and inhibiting respawn");
         crm_warn("Verify pacemaker and pacemaker_remote are not both enabled.");
         crm_exit(CRM_EX_FATAL);
     }
 }
 
 /*!
  * \internal
  * \brief Add an IPC server to the main loop for the pacemaker-fenced API
  *
  * \param[out] ipcs  Where to store newly created IPC server
  * \param[in]  cb    IPC callbacks
  *
  * \note This function exits fatally if unable to create the servers.
  */
 void
 pcmk__serve_fenced_ipc(qb_ipcs_service_t **ipcs,
                        struct qb_ipcs_service_handlers *cb)
 {
     *ipcs = mainloop_add_ipc_server_with_prio("stonith-ng", QB_IPC_NATIVE, cb,
                                               QB_LOOP_HIGH);
 
     if (*ipcs == NULL) {
         crm_err("Failed to create fencer: exiting and inhibiting respawn.");
         crm_warn("Verify pacemaker and pacemaker_remote are not both enabled.");
         crm_exit(CRM_EX_FATAL);
     }
 }
 
 /*!
  * \internal
  * \brief Add an IPC server to the main loop for the pacemakerd API
  *
  * \param[out] ipcs  Where to store newly created IPC server
  * \param[in]  cb    IPC callbacks
  *
  * \note This function exits with CRM_EX_OSERR if unable to create the servers.
  */
 void
 pcmk__serve_pacemakerd_ipc(qb_ipcs_service_t **ipcs,
                        struct qb_ipcs_service_handlers *cb)
 {
     *ipcs = mainloop_add_ipc_server(CRM_SYSTEM_MCP, QB_IPC_NATIVE, cb);
 
     if (*ipcs == NULL) {
         crm_err("Couldn't start pacemakerd IPC server");
         crm_warn("Verify pacemaker and pacemaker_remote are not both enabled.");
         /* sub-daemons are observed by pacemakerd. Thus we exit CRM_EX_FATAL
          * if we want to prevent pacemakerd from restarting them.
          * With pacemakerd we leave the exit-code shown to e.g. systemd
          * to what it was prior to moving the code here from pacemakerd.c
          */
         crm_exit(CRM_EX_OSERR);
     }
 }
 
 /*!
  * \internal
  * \brief Add an IPC server to the main loop for the pacemaker-schedulerd API
  *
  * \param[in] cb  IPC callbacks
  *
  * \return Newly created IPC server
  * \note This function exits fatally if unable to create the servers.
  */
 qb_ipcs_service_t *
 pcmk__serve_schedulerd_ipc(struct qb_ipcs_service_handlers *cb)
 {
     return mainloop_add_ipc_server(CRM_SYSTEM_PENGINE, QB_IPC_NATIVE, cb);
 }
 
 /*!
  * \brief Check whether string represents a client name used by cluster daemons
  *
  * \param[in] name  String to check
  *
  * \return true if name is standard client name used by daemons, false otherwise
  *
  * \note This is provided by the client, and so cannot be used by itself as a
  *       secure means of authentication.
  */
 bool
 crm_is_daemon_name(const char *name)
 {
     name = pcmk__message_name(name);
     return (!strcmp(name, CRM_SYSTEM_CRMD)
             || !strcmp(name, CRM_SYSTEM_STONITHD)
             || !strcmp(name, "stonith-ng")
             || !strcmp(name, "attrd")
             || !strcmp(name, CRM_SYSTEM_CIB)
             || !strcmp(name, CRM_SYSTEM_MCP)
             || !strcmp(name, CRM_SYSTEM_DC)
             || !strcmp(name, CRM_SYSTEM_TENGINE)
             || !strcmp(name, CRM_SYSTEM_LRMD));
 }
diff --git a/lib/common/options.c b/lib/common/options.c
index 1d6393b4f4..1d69af7276 100644
--- a/lib/common/options.c
+++ b/lib/common/options.c
@@ -1,721 +1,721 @@
 /*
  * Copyright 2004-2022 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 _GNU_SOURCE
 #  define _GNU_SOURCE
 #endif
 
 #include <crm_internal.h>
 
 #include <stdio.h>
 #include <string.h>
 #include <stdlib.h>
 #include <sys/types.h>
 #include <sys/stat.h>
 
 #ifdef HAVE_GETOPT_H
 #  include <getopt.h>
 #endif
 
 #include <crm/crm.h>
 
 
 /*
  * Command-line option handling
  */
 
 static char *crm_short_options = NULL;
 static const pcmk__cli_option_t *crm_long_options = NULL;
 static const char *crm_app_description = NULL;
 static const char *crm_app_usage = NULL;
 
 void
 pcmk__cli_option_cleanup(void)
 {
     free(crm_short_options);
     crm_short_options = NULL;
 }
 
 static struct option *
 create_long_opts(const pcmk__cli_option_t *long_options)
 {
     struct option *long_opts = NULL;
 
 #ifdef HAVE_GETOPT_H
     int index = 0, lpc = 0;
 
     /*
      * A previous, possibly poor, choice of '?' as the short form of --help
      * means that getopt_long() returns '?' for both --help and for "unknown option"
      *
      * This dummy entry allows us to differentiate between the two in
      * pcmk__next_cli_option() and exit with the correct error code.
      */
     long_opts = pcmk__realloc(long_opts, (index + 1) * sizeof(struct option));
     long_opts[index].name = "__dummmy__";
     long_opts[index].has_arg = 0;
     long_opts[index].flag = 0;
     long_opts[index].val = '_';
     index++;
 
     // cppcheck seems not to understand the abort-logic in pcmk__realloc
     // cppcheck-suppress memleak
     for (lpc = 0; long_options[lpc].name != NULL; lpc++) {
         if (long_options[lpc].name[0] == '-') {
             continue;
         }
 
         long_opts = pcmk__realloc(long_opts, (index + 1) * sizeof(struct option));
         /*fprintf(stderr, "Creating %d %s = %c\n", index,
          * long_options[lpc].name, long_options[lpc].val);      */
         long_opts[index].name = long_options[lpc].name;
         long_opts[index].has_arg = long_options[lpc].has_arg;
         long_opts[index].flag = long_options[lpc].flag;
         long_opts[index].val = long_options[lpc].val;
         index++;
     }
 
     /* Now create the list terminator */
     long_opts = pcmk__realloc(long_opts, (index + 1) * sizeof(struct option));
     long_opts[index].name = NULL;
     long_opts[index].has_arg = 0;
     long_opts[index].flag = 0;
     long_opts[index].val = 0;
 #endif
 
     return long_opts;
 }
 
 /*!
  * \internal
  * \brief Define the command-line options a daemon or tool accepts
  *
  * \param[in] short_options  getopt(3)-style short option list
  * \param[in] app_usage      summary of how command is invoked (for help)
  * \param[in] long_options   definition of options accepted
  * \param[in] app_desc       brief command description (for help)
  */
 void
 pcmk__set_cli_options(const char *short_options, const char *app_usage,
                       const pcmk__cli_option_t *long_options,
                       const char *app_desc)
 {
     if (short_options) {
         crm_short_options = strdup(short_options);
 
     } else if (long_options) {
         int lpc = 0;
         int opt_string_len = 0;
         char *local_short_options = NULL;
 
         for (lpc = 0; long_options[lpc].name != NULL; lpc++) {
             if (long_options[lpc].val && long_options[lpc].val != '-' && long_options[lpc].val < UCHAR_MAX) {
                 local_short_options = pcmk__realloc(local_short_options,
                                                     opt_string_len + 4);
                 local_short_options[opt_string_len++] = long_options[lpc].val;
                 /* getopt(3) says: Two colons mean an option takes an optional arg; */
                 if (long_options[lpc].has_arg == optional_argument) {
                     local_short_options[opt_string_len++] = ':';
                 }
                 if (long_options[lpc].has_arg >= required_argument) {
                     local_short_options[opt_string_len++] = ':';
                 }
                 local_short_options[opt_string_len] = 0;
             }
         }
         crm_short_options = local_short_options;
         crm_trace("Generated short option string: '%s'", local_short_options);
     }
 
     if (long_options) {
         crm_long_options = long_options;
     }
     if (app_desc) {
         crm_app_description = app_desc;
     }
     if (app_usage) {
         crm_app_usage = app_usage;
     }
 }
 
 int
 pcmk__next_cli_option(int argc, char **argv, int *index, const char **longname)
 {
 #ifdef HAVE_GETOPT_H
     static struct option *long_opts = NULL;
 
     if (long_opts == NULL && crm_long_options) {
         long_opts = create_long_opts(crm_long_options);
     }
 
     *index = 0;
     if (long_opts) {
         int flag = getopt_long(argc, argv, crm_short_options, long_opts, index);
 
         switch (flag) {
             case 0:
                 if (long_opts[*index].val) {
                     return long_opts[*index].val;
                 } else if (longname) {
                     *longname = long_opts[*index].name;
                 } else {
                     crm_notice("Unhandled option --%s", long_opts[*index].name);
                     return flag;
                 }
             case -1:           /* End of option processing */
                 break;
             case ':':
                 crm_trace("Missing argument");
                 pcmk__cli_help('?', CRM_EX_USAGE);
                 break;
             case '?':
                 pcmk__cli_help('?', (*index? CRM_EX_OK : CRM_EX_USAGE));
                 break;
         }
         return flag;
     }
 #endif
 
     if (crm_short_options) {
         return getopt(argc, argv, crm_short_options);
     }
 
     return -1;
 }
 
 void
 pcmk__cli_help(char cmd, crm_exit_t exit_code)
 {
     int i = 0;
     FILE *stream = (exit_code ? stderr : stdout);
 
     if (cmd == 'v' || cmd == '$') {
         fprintf(stream, "Pacemaker %s\n", PACEMAKER_VERSION);
         fprintf(stream, "Written by Andrew Beekhof and "
                         "the Pacemaker project contributors\n");
         goto out;
     }
 
     if (cmd == '!') {
         fprintf(stream, "Pacemaker %s (Build: %s): %s\n", PACEMAKER_VERSION, BUILD_VERSION, CRM_FEATURES);
         goto out;
     }
 
     fprintf(stream, "%s - %s\n", crm_system_name, crm_app_description);
 
     if (crm_app_usage) {
         fprintf(stream, "Usage: %s %s\n", crm_system_name, crm_app_usage);
     }
 
     if (crm_long_options) {
         fprintf(stream, "Options:\n");
         for (i = 0; crm_long_options[i].name != NULL; i++) {
             if (crm_long_options[i].flags & pcmk__option_hidden) {
 
             } else if (crm_long_options[i].flags & pcmk__option_paragraph) {
                 fprintf(stream, "%s\n\n", crm_long_options[i].desc);
 
             } else if (crm_long_options[i].flags & pcmk__option_example) {
                 fprintf(stream, "\t#%s\n\n", crm_long_options[i].desc);
 
             } else if (crm_long_options[i].val == '-' && crm_long_options[i].desc) {
                 fprintf(stream, "%s\n", crm_long_options[i].desc);
 
             } else {
                 /* is val printable as char ? */
                 if (crm_long_options[i].val && crm_long_options[i].val <= UCHAR_MAX) {
                     fprintf(stream, " -%c,", crm_long_options[i].val);
                 } else {
                     fputs("    ", stream);
                 }
                 fprintf(stream, " --%s%s\t%s\n", crm_long_options[i].name,
                         crm_long_options[i].has_arg == optional_argument ? "[=value]" :
                         crm_long_options[i].has_arg == required_argument ? "=value" : "",
                         crm_long_options[i].desc ? crm_long_options[i].desc : "");
             }
         }
 
     } else if (crm_short_options) {
         fprintf(stream, "Usage: %s - %s\n", crm_system_name, crm_app_description);
         for (i = 0; crm_short_options[i] != 0; i++) {
             int has_arg = no_argument /* 0 */;
 
             if (crm_short_options[i + 1] == ':') {
                 if (crm_short_options[i + 2] == ':')
                     has_arg = optional_argument /* 2 */;
                 else
                     has_arg = required_argument /* 1 */;
             }
 
             fprintf(stream, " -%c %s\n", crm_short_options[i],
                     has_arg == optional_argument ? "[value]" :
                     has_arg == required_argument ? "{value}" : "");
             i += has_arg;
         }
     }
 
     fprintf(stream, "\nReport bugs to %s\n", PACKAGE_BUGREPORT);
 
   out:
     crm_exit(exit_code);
     while(1); // above does not return
 }
 
 
 /*
  * Environment variable option handling
  */
 
 /*!
  * \internal
  * \brief Get the value of a Pacemaker environment variable option
  *
  * If an environment variable option is set, with either a PCMK_ or (for
  * backward compatibility) HA_ prefix, log and return the value.
  *
  * \param[in] option  Environment variable name (without prefix)
  *
  * \return Value of environment variable option, or NULL in case of
  *         option name too long or value not found
  */
 const char *
 pcmk__env_option(const char *option)
 {
     const char *const prefixes[] = {"PCMK_", "HA_"};
     char env_name[NAME_MAX];
     const char *value = NULL;
 
     CRM_CHECK(!pcmk__str_empty(option), return NULL);
 
     for (int i = 0; i < PCMK__NELEM(prefixes); i++) {
         int rv = snprintf(env_name, NAME_MAX, "%s%s", prefixes[i], option);
 
         if (rv < 0) {
             crm_err("Failed to write %s%s to buffer: %s", prefixes[i], option,
                     strerror(errno));
             return NULL;
         }
 
         if (rv >= sizeof(env_name)) {
             crm_trace("\"%s%s\" is too long", prefixes[i], option);
             continue;
         }
 
         value = getenv(env_name);
         if (value != NULL) {
             crm_trace("Found %s = %s", env_name, value);
             return value;
         }
     }
 
     crm_trace("Nothing found for %s", option);
     return NULL;
 }
 
 /*!
  * \brief Set or unset a Pacemaker environment variable option
  *
  * Set an environment variable option with both a PCMK_ and (for
  * backward compatibility) HA_ prefix.
  *
  * \param[in] option  Environment variable name (without prefix)
  * \param[in] value   New value (or NULL to unset)
  */
 void
 pcmk__set_env_option(const char *option, const char *value)
 {
     const char *const prefixes[] = {"PCMK_", "HA_"};
     char env_name[NAME_MAX];
 
     CRM_CHECK(!pcmk__str_empty(option) && (strchr(option, '=') == NULL),
               return);
 
     for (int i = 0; i < PCMK__NELEM(prefixes); i++) {
         int rv = snprintf(env_name, NAME_MAX, "%s%s", prefixes[i], option);
 
         if (rv < 0) {
             crm_err("Failed to write %s%s to buffer: %s", prefixes[i], option,
                     strerror(errno));
             return;
         }
 
         if (rv >= sizeof(env_name)) {
             crm_trace("\"%s%s\" is too long", prefixes[i], option);
             continue;
         }
 
         if (value != NULL) {
             crm_trace("Setting %s to %s", env_name, value);
             rv = setenv(env_name, value, 1);
         } else {
             crm_trace("Unsetting %s", env_name);
             rv = unsetenv(env_name);
         }
 
         if (rv < 0) {
             crm_err("Failed to %sset %s: %s", (value != NULL)? "" : "un",
                     env_name, strerror(errno));
         }
     }
 }
 
 /*!
  * \internal
  * \brief Check whether Pacemaker environment variable option is enabled
  *
  * Given a Pacemaker environment variable option that can either be boolean
  * or a list of daemon names, return true if the option is enabled for a given
  * daemon.
  *
  * \param[in] daemon   Daemon name (can be NULL)
  * \param[in] option   Pacemaker environment variable name
  *
  * \return true if variable is enabled for daemon, otherwise false
  */
 bool
 pcmk__env_option_enabled(const char *daemon, const char *option)
 {
     const char *value = pcmk__env_option(option);
 
     return (value != NULL)
         && (crm_is_true(value)
             || ((daemon != NULL) && (strstr(value, daemon) != NULL)));
 }
 
 
 /*
  * Cluster option handling
  */
 
 bool
 pcmk__valid_interval_spec(const char *value)
 {
     (void) crm_parse_interval_spec(value);
     return errno == 0;
 }
 
 bool
 pcmk__valid_boolean(const char *value)
 {
     int tmp;
 
     return crm_str_to_boolean(value, &tmp) == 1;
 }
 
 bool
 pcmk__valid_number(const char *value)
 {
     if (value == NULL) {
         return false;
 
     } else if (pcmk_str_is_minus_infinity(value) ||
                pcmk_str_is_infinity(value)) {
         return true;
     }
 
     return pcmk__scan_ll(value, NULL, 0LL) == pcmk_rc_ok;
 }
 
 bool
 pcmk__valid_positive_number(const char *value)
 {
     long long num = 0LL;
 
     return pcmk_str_is_infinity(value)
            || ((pcmk__scan_ll(value, &num, 0LL) == pcmk_rc_ok) && (num > 0));
 }
 
 bool
 pcmk__valid_quorum(const char *value)
 {
     return pcmk__strcase_any_of(value, "stop", "freeze", "ignore", "demote", "suicide", NULL);
 }
 
 bool
 pcmk__valid_script(const char *value)
 {
     struct stat st;
 
     if (pcmk__str_eq(value, "/dev/null", pcmk__str_casei)) {
         return true;
     }
 
     if (stat(value, &st) != 0) {
         crm_err("Script %s does not exist", value);
         return false;
     }
 
     if (S_ISREG(st.st_mode) == 0) {
         crm_err("Script %s is not a regular file", value);
         return false;
     }
 
     if ((st.st_mode & (S_IXUSR | S_IXGRP)) == 0) {
         crm_err("Script %s is not executable", value);
         return false;
     }
 
     return true;
 }
 
 bool
 pcmk__valid_percentage(const char *value)
 {
     char *end = NULL;
     long number = strtol(value, &end, 10);
 
     if (end && (end[0] != '%')) {
         return false;
     }
     return number >= 0;
 }
 
 /*!
  * \internal
  * \brief Check a table of configured options for a particular option
  *
  * \param[in,out] options    Name/value pairs for configured options
  * \param[in]     validate   If not NULL, validator function for option value
  * \param[in]     name       Option name to look for
  * \param[in]     old_name   Alternative option name to look for
  * \param[in]     def_value  Default to use if option not configured
  *
  * \return Option value (from supplied options table or default value)
  */
 static const char *
 cluster_option_value(GHashTable *options, bool (*validate)(const char *),
                      const char *name, const char *old_name,
                      const char *def_value)
 {
     const char *value = NULL;
     char *new_value = NULL;
 
     CRM_ASSERT(name != NULL);
 
     if (options) {
         value = g_hash_table_lookup(options, name);
 
         if ((value == NULL) && old_name) {
             value = g_hash_table_lookup(options, old_name);
             if (value != NULL) {
                 pcmk__config_warn("Support for legacy name '%s' for cluster "
                                   "option '%s' is deprecated and will be "
                                   "removed in a future release",
                                   old_name, name);
 
                 // Inserting copy with current name ensures we only warn once
                 new_value = strdup(value);
                 g_hash_table_insert(options, strdup(name), new_value);
                 value = new_value;
             }
         }
 
         if (value && validate && (validate(value) == FALSE)) {
             pcmk__config_err("Using default value for cluster option '%s' "
                              "because '%s' is invalid", name, value);
             value = NULL;
         }
 
         if (value) {
             return value;
         }
     }
 
     // No value found, use default
     value = def_value;
 
     if (value == NULL) {
         crm_trace("No value or default provided for cluster option '%s'",
                   name);
         return NULL;
     }
 
     if (validate) {
         CRM_CHECK(validate(value) != FALSE,
                   crm_err("Bug: default value for cluster option '%s' is invalid", name);
                   return NULL);
     }
 
     crm_trace("Using default value '%s' for cluster option '%s'",
               value, name);
     if (options) {
         new_value = strdup(value);
         g_hash_table_insert(options, strdup(name), new_value);
         value = new_value;
     }
     return value;
 }
 
 /*!
  * \internal
  * \brief Get the value of a cluster option
  *
- * \param[in] options      Name/value pairs for configured options
- * \param[in] option_list  Possible cluster options
- * \param[in] name         (Primary) option name to look for
+ * \param[in,out] options      Name/value pairs for configured options
+ * \param[in]     option_list  Possible cluster options
+ * \param[in]     name         (Primary) option name to look for
  *
  * \return Option value
  */
 const char *
 pcmk__cluster_option(GHashTable *options,
                      const pcmk__cluster_option_t *option_list,
                      int len, const char *name)
 {
     const char *value = NULL;
 
     for (int lpc = 0; lpc < len; lpc++) {
         if (pcmk__str_eq(name, option_list[lpc].name, pcmk__str_casei)) {
             value = cluster_option_value(options, option_list[lpc].is_valid,
                                          option_list[lpc].name,
                                          option_list[lpc].alt_name,
                                          option_list[lpc].default_value);
             return value;
         }
     }
     CRM_CHECK(FALSE, crm_err("Bug: looking for unknown option '%s'", name));
     return NULL;
 }
 
 /*!
  * \internal
  * \brief Add a description element to a meta-data string
  *
  * \param[in,out] s       Meta-data string to add to
  * \param[in]     tag     Name of element to add ("longdesc" or "shortdesc")
  * \param[in]     desc    Textual description to add
  * \param[in]     values  If not NULL, the allowed values for the parameter
  */
 static void
 add_desc(GString *s, const char *tag, const char *desc, const char *values,
          const char *spaces)
 {
     char *escaped_en = crm_xml_escape(desc);
 
     g_string_append_printf(s, "<%s lang=\"en\">%s",
                            tag, escaped_en);
     if (values != NULL) {
         g_string_append_printf(s, "  Allowed values: %s", values);
     }
     g_string_append_printf(s, "</%s>\n", tag);
 
 #ifdef ENABLE_NLS
     {
         static const char *locale = NULL;
 
         char *localized = crm_xml_escape(_(desc));
 
         if (strcmp(escaped_en, localized) != 0) {
             if (locale == NULL) {
                 locale = strtok(setlocale(LC_ALL, NULL), "_");
             }
 
 	    if (spaces != NULL) {
                 g_string_append_printf(s, "%s", spaces);
             }
             g_string_append_printf(s, "<%s lang=\"%s\">%s",
                                    tag, locale, localized);
             if (values != NULL) {
                 g_string_append(s, _("  Allowed values: "));
                 g_string_append_printf(s, "%s", _(values));
             }
             g_string_append_printf(s, "</%s>\n", tag);
         }
         free(localized);
     }
 #endif
 
     free(escaped_en);
 }
 
 char *
 pcmk__format_option_metadata(const char *name, const char *desc_short,
                              const char *desc_long,
                              pcmk__cluster_option_t *option_list, int len)
 {
     char *retval;
     /* big enough to hold "pacemaker-schedulerd metadata" output */
     GString *s = g_string_sized_new(13000);
     int lpc = 0;
 
     g_string_append_printf(s, "<?xml version=\"1.0\"?>"
                               "<resource-agent name=\"%s\">\n"
                               "  <version>%s</version>\n",
                               name, PCMK_OCF_VERSION);
 
     g_string_append(s, "  ");
     add_desc(s, "longdesc", desc_long, NULL, "  ");
 
     g_string_append(s, "  ");
     add_desc(s, "shortdesc", desc_short, NULL, "  ");
 
     g_string_append(s, "  <parameters>\n");
 
     for (lpc = 0; lpc < len; lpc++) {
         const char *long_desc = option_list[lpc].description_long;
 
         if (long_desc == NULL) {
             long_desc = option_list[lpc].description_short;
             if (long_desc == NULL) {
                 continue; // The standard requires a parameter description
             }
         }
 
         g_string_append_printf(s, "    <parameter name=\"%s\">\n",
                                option_list[lpc].name);
 
         g_string_append(s, "      ");
         add_desc(s, "longdesc", long_desc, option_list[lpc].values, "      ");
 
         g_string_append(s, "      ");
         add_desc(s, "shortdesc", option_list[lpc].description_short, NULL, "      ");
 
         if (option_list[lpc].values && !strcmp(option_list[lpc].type, "select")) {
             char *str = strdup(option_list[lpc].values);
             char delim[] = ", ";
             char *ptr = strtok(str, delim);
 
             g_string_append_printf(s, "      <content type=\"%s\" default=\"%s\">\n",
                                    option_list[lpc].type,
                                    option_list[lpc].default_value);
 
             while (ptr != NULL) {
                 g_string_append_printf(s, "        <option value=\"%s\" />\n", ptr);
                 ptr = strtok(NULL, delim);
             }
 
             g_string_append_printf(s, "      </content>\n");
             free(str);
 
         } else {
             g_string_append_printf(s, "      <content type=\"%s\" default=\"%s\"/>\n",
                                    option_list[lpc].type,
                                    option_list[lpc].default_value
             );
         }
 
         g_string_append_printf(s, "    </parameter>\n");
     }
     g_string_append_printf(s, "  </parameters>\n</resource-agent>\n");
 
     retval = s->str;
     g_string_free(s, FALSE);
     return retval;
 }
 
 void
 pcmk__validate_cluster_options(GHashTable *options,
                                pcmk__cluster_option_t *option_list, int len)
 {
     for (int lpc = 0; lpc < len; lpc++) {
         cluster_option_value(options, option_list[lpc].is_valid,
                              option_list[lpc].name,
                              option_list[lpc].alt_name,
                              option_list[lpc].default_value);
     }
 }
diff --git a/lib/common/output.c b/lib/common/output.c
index 887b2d49b5..cd413b9db1 100644
--- a/lib/common/output.c
+++ b/lib/common/output.c
@@ -1,254 +1,254 @@
 /*
  * Copyright 2019-2022 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 <crm/common/util.h>
 #include <crm/common/xml.h>
 #include <libxml/tree.h>
 
 static GHashTable *formatters = NULL;
 
 #if defined(PCMK__UNIT_TESTING)
 GHashTable *
 pcmk__output_formatters(void) {
     return formatters;
 }
 #endif
 
 void
 pcmk__output_free(pcmk__output_t *out) {
     if (out == NULL) {
         return;
     }
 
     out->free_priv(out);
 
     if (out->messages != NULL) {
         g_hash_table_destroy(out->messages);
     }
 
     g_free(out->request);
     free(out);
 }
 
 int
 pcmk__output_new(pcmk__output_t **out, const char *fmt_name, const char *filename,
                  char **argv) {
     pcmk__output_factory_t create = NULL;
 
     CRM_ASSERT(formatters != NULL && out != NULL);
 
     /* If no name was given, just try "text".  It's up to each tool to register
      * what it supports so this also may not be valid.
      */
     if (fmt_name == NULL) {
         create = g_hash_table_lookup(formatters, "text");
     } else {
         create = g_hash_table_lookup(formatters, fmt_name);
     }
 
     if (create == NULL) {
         return pcmk_rc_unknown_format;
     }
 
     *out = create(argv);
     if (*out == NULL) {
         return ENOMEM;
     }
 
     if (pcmk__str_eq(filename, "-", pcmk__str_null_matches)) {
         (*out)->dest = stdout;
     } else {
         (*out)->dest = fopen(filename, "w");
         if ((*out)->dest == NULL) {
             pcmk__output_free(*out);
             *out = NULL;
             return errno;
         }
     }
 
     (*out)->quiet = false;
     (*out)->messages = pcmk__strkey_table(free, NULL);
 
     if ((*out)->init(*out) == false) {
         pcmk__output_free(*out);
         return ENOMEM;
     }
 
     setenv("OCF_OUTPUT_FORMAT", (*out)->fmt_name, 1);
 
     return pcmk_rc_ok;
 }
 
 int
 pcmk__register_format(GOptionGroup *group, const char *name,
                       pcmk__output_factory_t create,
                       const GOptionEntry *options)
 {
     CRM_ASSERT(create != NULL && !pcmk__str_empty(name));
 
     if (formatters == NULL) {
         formatters = pcmk__strkey_table(free, NULL);
     }
 
     if (options != NULL && group != NULL) {
         g_option_group_add_entries(group, options);
     }
 
     g_hash_table_insert(formatters, strdup(name), create);
     return pcmk_rc_ok;
 }
 
 void
 pcmk__register_formats(GOptionGroup *group,
                        const pcmk__supported_format_t *formats)
 {
     if (formats == NULL) {
         return;
     }
     for (const pcmk__supported_format_t *entry = formats; entry->name != NULL;
          entry++) {
         pcmk__register_format(group, entry->name, entry->create, entry->options);
     }
 }
 
 void
 pcmk__unregister_formats() {
     if (formatters != NULL) {
         g_hash_table_destroy(formatters);
         formatters = NULL;
     }
 }
 
 int
 pcmk__call_message(pcmk__output_t *out, const char *message_id, ...) {
     va_list args;
     int rc = pcmk_rc_ok;
     pcmk__message_fn_t fn;
 
     CRM_ASSERT(out != NULL && !pcmk__str_empty(message_id));
 
     fn = g_hash_table_lookup(out->messages, message_id);
     if (fn == NULL) {
         crm_debug("Called unknown output message '%s' for format '%s'",
                   message_id, out->fmt_name);
         return EINVAL;
     }
 
     va_start(args, message_id);
     rc = fn(out, args);
     va_end(args);
 
     return rc;
 }
 
 void
 pcmk__register_message(pcmk__output_t *out, const char *message_id,
                        pcmk__message_fn_t fn) {
     CRM_ASSERT(out != NULL && !pcmk__str_empty(message_id) && fn != NULL);
 
     g_hash_table_replace(out->messages, strdup(message_id), fn);
 }
 
 void
 pcmk__register_messages(pcmk__output_t *out, const pcmk__message_entry_t *table)
 {
     for (const pcmk__message_entry_t *entry = table; entry->message_id != NULL;
          entry++) {
         if (pcmk__strcase_any_of(entry->fmt_name, "default", out->fmt_name, NULL)) {
             pcmk__register_message(out, entry->message_id, entry->fn);
         }
     }
 }
 
 void
 pcmk__output_and_clear_error(GError *error, pcmk__output_t *out)
 {
     if (error == NULL) {
         return;
     }
 
     if (out != NULL) {
         out->err(out, "%s: %s", g_get_prgname(), error->message);
     } else {
         fprintf(stderr, "%s: %s\n", g_get_prgname(), error->message);
     }
 
     g_clear_error(&error);
 }
 
 /*!
  * \internal
  * \brief Create an XML-only output object
  *
  * Create an output object that supports only the XML format, and free
  * existing XML if supplied (particularly useful for libpacemaker public API
  * functions that want to free any previous result supplied by the caller).
  *
  * \param[out]     out  Where to put newly created output object
  * \param[in,out]  xml  If non-NULL, this will be freed
  *
  * \return Standard Pacemaker return code
  */
 int
 pcmk__xml_output_new(pcmk__output_t **out, xmlNodePtr *xml) {
     pcmk__supported_format_t xml_format[] = {
         PCMK__SUPPORTED_FORMAT_XML,
         { NULL, NULL, NULL }
     };
 
     if (*xml != NULL) {
         xmlFreeNode(*xml);
         *xml = NULL;
     }
     pcmk__register_formats(NULL, xml_format);
     return pcmk__output_new(out, "xml", NULL, NULL);
 }
 
 /*!
  * \internal
  * \brief  Finish and free an XML-only output object
  *
  * \param[in,out] out  Output object to free
- * \param[out]    xml  Where to store XML output
+ * \param[out]    xml  If not NULL, where to store XML output
  */
 void
 pcmk__xml_output_finish(pcmk__output_t *out, xmlNodePtr *xml) {
     out->finish(out, 0, FALSE, (void **) xml);
     pcmk__output_free(out);
 }
 
 /*!
  * \internal
  * \brief Create a new output object using the "log" format
  *
  * \param[out] out  Where to store newly allocated output object
  *
  * \return Standard Pacemaker return code
  */
 int
 pcmk__log_output_new(pcmk__output_t **out)
 {
     int rc = pcmk_rc_ok;
     const char* argv[] = { "", NULL };
     pcmk__supported_format_t formats[] = {
         PCMK__SUPPORTED_FORMAT_LOG,
         { NULL, NULL, NULL }
     };
 
     pcmk__register_formats(NULL, formats);
     rc = pcmk__output_new(out, "log", NULL, (char **) argv);
     if ((rc != pcmk_rc_ok) || (*out == NULL)) {
         crm_err("Can't log certain messages due to internal error: %s",
                 pcmk_rc_str(rc));
         return rc;
     }
     return pcmk_rc_ok;
 }