1 /*
2 * Copyright 2023-2026 the Pacemaker project contributors
3 *
4 * The version control history for this file may have further details.
5 *
6 * This source code is licensed under the GNU General Public License version 2
7 * or later (GPLv2+) WITHOUT ANY WARRANTY.
8 */
9
10 #include <crm_internal.h>
11
12 #include <errno.h> // EOPNOTSUPP
13 #include <stdbool.h>
14 #include <stddef.h> // NULL
15 #include <stdlib.h> // free
16
17 #include <libxml/tree.h> // xmlNode
18
19 #include <crm/cib/internal.h> // cib__*
20 #include <crm/common/internal.h> // pcmk__client_t, pcmk__s, pcmk__xe_*, etc.
21 #include <crm/common/logging.h> // CRM_CHECK
22 #include <crm/common/results.h> // pcmk_rc_*
23
24 #include "pacemaker-based.h"
25
26 /*!
27 * \internal
28 * \brief Create a string describing the source of a commit-transaction request
29 *
30 * \param[in] client CIB client
31 * \param[in] origin Host where the commit request originated
32 *
33 * \return String describing the request source
34 *
35 * \note The caller is responsible for freeing the return value using \c free().
36 */
37 char *
38 based_transaction_source_str(const pcmk__client_t *client, const char *origin)
39 {
40 if (client != NULL) {
41 return pcmk__assert_asprintf("client %s (%s)%s%s",
42 pcmk__client_name(client),
43 pcmk__s(client->id, "unidentified"),
44 ((origin != NULL)? " on " : ""),
45 pcmk__s(origin, ""));
46 } else {
47 return pcmk__str_copy(pcmk__s(origin, "unknown source"));
48 }
49 }
50
51 /*!
52 * \internal
53 * \brief Process requests in a transaction
54 *
55 * Stop when a request fails or when all requests have been processed.
56 *
57 * \param[in,out] transaction Transaction to process
58 * \param[in] client CIB client
59 * \param[in] source String describing the commit request source
60 *
61 * \return Standard Pacemaker return code
62 */
63 static int
64 process_transaction_requests(xmlNode *transaction, const pcmk__client_t *client,
65 const char *source)
66 {
67 for (xmlNode *request = pcmk__xe_first_child(transaction,
68 PCMK__XE_CIB_COMMAND, NULL,
69 NULL);
70 request != NULL;
71 request = pcmk__xe_next(request, PCMK__XE_CIB_COMMAND)) {
72
73 const char *op = pcmk__xe_get(request, PCMK__XA_CIB_OP);
74 const char *host = pcmk__xe_get(request, PCMK__XA_CIB_HOST);
75 const cib__operation_t *operation = NULL;
76 int rc = cib__get_operation(op, &operation);
77
78 if (rc == pcmk_rc_ok) {
79 if (!pcmk__is_set(operation->flags, cib__op_attr_transaction)
80 || (host != NULL)) {
81
82 rc = EOPNOTSUPP;
83 } else {
84 /* Commit-transaction is a privileged operation. If we reached
85 * this point, the request came from a privileged connection.
86 */
87 rc = based_process_request(request, true, client);
88 }
89 }
90
91 if (rc != pcmk_rc_ok) {
92 pcmk__err("Aborting CIB transaction for %s due to failed %s "
93 "request: %s",
94 source, op, pcmk_rc_str(rc));
95 pcmk__log_xml_info(request, "Failed request");
96 return rc;
97 }
98
99 pcmk__trace("Applied %s request to transaction working CIB for %s", op,
100 source);
101 pcmk__log_xml_trace(request, "Successful request");
102 }
103
104 return pcmk_rc_ok;
105 }
106
107 /*!
108 * \internal
109 * \brief Commit a given CIB client's transaction to a working CIB copy
110 *
111 * \param[in] transaction Transaction to commit
112 * \param[in] client CIB client
113 * \param[in] origin Host where the commit request originated
114 * \param[in,out] result_cib Where to store result CIB
115 *
116 * \return Standard Pacemaker return code
117 *
118 * \note This function is expected to be called only by
119 * \p based_process_commit_transact().
120 * \note \p result_cib is expected to be a copy of the current CIB as created by
121 * \p cib__perform_op_rw().
122 * \note The caller is responsible for activating and syncing \p result_cib on
123 * success, and for freeing it on failure.
124 */
125 int
126 based_commit_transaction(xmlNode *transaction, const pcmk__client_t *client,
127 const char *origin, xmlNode **result_cib)
128 {
129 xmlNode *saved_cib = the_cib;
130 int rc = pcmk_rc_ok;
131 char *source = NULL;
132
133 // *result_cib should be a copy of the_cib (created by cib__perform_op_rw())
134 pcmk__assert((result_cib != NULL) && (*result_cib != NULL)
135 && (*result_cib != the_cib));
136
137 CRM_CHECK(pcmk__xe_is(transaction, PCMK__XE_CIB_TRANSACTION),
138 return pcmk_rc_no_transaction);
139
140 source = based_transaction_source_str(client, origin);
141 pcmk__trace("Committing transaction for %s to working CIB", source);
142
143 // Apply all changes to a working copy of the CIB
144 the_cib = *result_cib;
145
146 rc = process_transaction_requests(transaction, client, origin);
147
148 pcmk__trace("Transaction commit %s for %s",
149 ((rc == pcmk_rc_ok)? "succeeded" : "failed"), source);
150
151 /* Some request types (for example, erase) may have freed the_cib (the
152 * working copy) and pointed it at a new XML object. In that case, it
153 * follows that *result_cib (the working copy) was freed.
154 *
155 * Point *result_cib at the updated working copy stored in the_cib.
156 */
157 *result_cib = the_cib;
158
159 // Point the_cib back to the unchanged original copy
160 the_cib = saved_cib;
161
162 free(source);
163 return rc;
164 }
165