1 /*
2 * Copyright 2023-2024 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 <glib.h>
13 #include <libxml/tree.h>
14
15 #include "pacemaker-based.h"
16
17 /*!
18 * \internal
19 * \brief Create a string describing the source of a commit-transaction request
20 *
21 * \param[in] client CIB client
22 * \param[in] origin Host where the commit request originated
23 *
24 * \return String describing the request source
25 *
26 * \note The caller is responsible for freeing the return value using \c free().
27 */
28 char *
29 based_transaction_source_str(const pcmk__client_t *client, const char *origin)
/* ![[previous]](../icons/n_left.png)
![[next]](../icons/right.png)
![[first]](../icons/n_first.png)
![[last]](../icons/last.png)
![[top]](../icons/top.png)
![[bottom]](../icons/bottom.png)
![[index]](../icons/index.png)
*/
30 {
31 if (client != NULL) {
32 return crm_strdup_printf("client %s (%s)%s%s",
33 pcmk__client_name(client),
34 pcmk__s(client->id, "unidentified"),
35 ((origin != NULL)? " on " : ""),
36 pcmk__s(origin, ""));
37 } else {
38 return pcmk__str_copy(pcmk__s(origin, "unknown source"));
39 }
40 }
41
42 /*!
43 * \internal
44 * \brief Process requests in a transaction
45 *
46 * Stop when a request fails or when all requests have been processed.
47 *
48 * \param[in,out] transaction Transaction to process
49 * \param[in] client CIB client
50 * \param[in] source String describing the commit request source
51 *
52 * \return Standard Pacemaker return code
53 */
54 static int
55 process_transaction_requests(xmlNodePtr transaction,
/* ![[previous]](../icons/left.png)
![[next]](../icons/right.png)
![[first]](../icons/first.png)
![[last]](../icons/last.png)
![[top]](../icons/top.png)
![[bottom]](../icons/bottom.png)
![[index]](../icons/index.png)
*/
56 const pcmk__client_t *client, const char *source)
57 {
58 for (xmlNode *request = pcmk__xe_first_child(transaction,
59 PCMK__XE_CIB_COMMAND, NULL,
60 NULL);
61 request != NULL;
62 request = pcmk__xe_next(request, PCMK__XE_CIB_COMMAND)) {
63
64 const char *op = crm_element_value(request, PCMK__XA_CIB_OP);
65 const char *host = crm_element_value(request, PCMK__XA_CIB_HOST);
66 const cib__operation_t *operation = NULL;
67 int rc = cib__get_operation(op, &operation);
68
69 if (rc == pcmk_rc_ok) {
70 if (!pcmk_is_set(operation->flags, cib__op_attr_transaction)
71 || (host != NULL)) {
72
73 rc = EOPNOTSUPP;
74 } else {
75 /* Commit-transaction is a privileged operation. If we reached
76 * this point, the request came from a privileged connection.
77 */
78 rc = cib_process_request(request, TRUE, client);
79 rc = pcmk_legacy2rc(rc);
80 }
81 }
82
83 if (rc != pcmk_rc_ok) {
84 crm_err("Aborting CIB transaction for %s due to failed %s request: "
85 "%s",
86 source, op, pcmk_rc_str(rc));
87 crm_log_xml_info(request, "Failed request");
88 return rc;
89 }
90
91 crm_trace("Applied %s request to transaction working CIB for %s",
92 op, source);
93 crm_log_xml_trace(request, "Successful request");
94 }
95
96 return pcmk_rc_ok;
97 }
98
99 /*!
100 * \internal
101 * \brief Commit a given CIB client's transaction to a working CIB copy
102 *
103 * \param[in] transaction Transaction to commit
104 * \param[in] client CIB client
105 * \param[in] origin Host where the commit request originated
106 * \param[in,out] result_cib Where to store result CIB
107 *
108 * \return Standard Pacemaker return code
109 *
110 * \note This function is expected to be called only by
111 * \p cib_process_commit_transaction().
112 * \note \p result_cib is expected to be a copy of the current CIB as created by
113 * \p cib_perform_op().
114 * \note The caller is responsible for activating and syncing \p result_cib on
115 * success, and for freeing it on failure.
116 */
117 int
118 based_commit_transaction(xmlNodePtr transaction, const pcmk__client_t *client,
/* ![[previous]](../icons/left.png)
![[next]](../icons/n_right.png)
![[first]](../icons/first.png)
![[last]](../icons/n_last.png)
![[top]](../icons/top.png)
![[bottom]](../icons/bottom.png)
![[index]](../icons/index.png)
*/
119 const char *origin, xmlNodePtr *result_cib)
120 {
121 xmlNodePtr saved_cib = the_cib;
122 int rc = pcmk_rc_ok;
123 char *source = NULL;
124
125 pcmk__assert(result_cib != NULL);
126
127 CRM_CHECK(pcmk__xe_is(transaction, PCMK__XE_CIB_TRANSACTION),
128 return pcmk_rc_no_transaction);
129
130 /* *result_cib should be a copy of the_cib (created by cib_perform_op()). If
131 * not, make a copy now. Change tracking isn't strictly required here
132 * because:
133 * * Each request in the transaction will have changes tracked and ACLs
134 * checked if appropriate.
135 * * cib_perform_op() will infer changes for the commit request at the end.
136 */
137 CRM_CHECK((*result_cib != NULL) && (*result_cib != the_cib),
138 *result_cib = pcmk__xml_copy(NULL, the_cib));
139
140 source = based_transaction_source_str(client, origin);
141 crm_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 crm_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 }