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;
(4) Event example_assign: Example 2: Assigning: "rc" = return value from "cib__get_operation(op, &operation)".
Also see events: [check_return][example_assign][example_checked][example_checked][example_assign][example_checked][example_assign][example_checked]
76   	        int rc = cib__get_operation(op, &operation);
77   	
(5) Event example_checked: Example 2 (cont.): "rc" has its value checked in "rc == pcmk_rc_ok".
Also see events: [check_return][example_assign][example_checked][example_assign][example_assign][example_checked][example_assign][example_checked]
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