/src/pacemaker/lib/cib/cib_file.c
Line | Count | Source |
1 | | /* |
2 | | * Original copyright 2004 International Business Machines |
3 | | * Later changes copyright 2008-2025 the Pacemaker project contributors |
4 | | * |
5 | | * The version control history for this file may have further details. |
6 | | * |
7 | | * This source code is licensed under the GNU Lesser General Public License |
8 | | * version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY. |
9 | | */ |
10 | | |
11 | | #include <crm_internal.h> |
12 | | #include <unistd.h> |
13 | | #include <limits.h> |
14 | | #include <stdbool.h> |
15 | | #include <stdlib.h> |
16 | | #include <stdint.h> |
17 | | #include <stdio.h> |
18 | | #include <stdarg.h> |
19 | | #include <string.h> |
20 | | #include <pwd.h> |
21 | | |
22 | | #include <sys/stat.h> |
23 | | #include <sys/types.h> |
24 | | #include <glib.h> |
25 | | |
26 | | #include <crm/crm.h> |
27 | | #include <crm/cib/internal.h> |
28 | | #include <crm/common/ipc.h> |
29 | | #include <crm/common/xml.h> |
30 | | #include <crm/common/xml_internal.h> |
31 | | |
32 | 0 | #define CIB_SERIES "cib" |
33 | 0 | #define CIB_SERIES_MAX 100 |
34 | 0 | #define CIB_SERIES_BZIP FALSE /* Must be false because archived copies are |
35 | | created with hard links |
36 | | */ |
37 | | |
38 | 1 | #define CIB_LIVE_NAME CIB_SERIES ".xml" |
39 | | |
40 | | // key: client ID (const char *) -> value: client (cib_t *) |
41 | | static GHashTable *client_table = NULL; |
42 | | |
43 | | enum cib_file_flags { |
44 | | cib_file_flag_dirty = (UINT32_C(1) << 0), |
45 | | cib_file_flag_live = (UINT32_C(1) << 1), |
46 | | }; |
47 | | |
48 | | typedef struct cib_file_opaque_s { |
49 | | char *id; |
50 | | char *filename; |
51 | | uint32_t flags; // Group of enum cib_file_flags |
52 | | xmlNode *cib_xml; |
53 | | } cib_file_opaque_t; |
54 | | |
55 | | static int cib_file_process_commit_transaction(const char *op, int options, |
56 | | const char *section, |
57 | | xmlNode *req, xmlNode *input, |
58 | | xmlNode *existing_cib, |
59 | | xmlNode **result_cib, |
60 | | xmlNode **answer); |
61 | | |
62 | | /*! |
63 | | * \internal |
64 | | * \brief Add a CIB file client to client table |
65 | | * |
66 | | * \param[in] cib CIB client |
67 | | */ |
68 | | static void |
69 | | register_client(const cib_t *cib) |
70 | 0 | { |
71 | 0 | cib_file_opaque_t *private = cib->variant_opaque; |
72 | |
|
73 | 0 | if (client_table == NULL) { |
74 | 0 | client_table = pcmk__strkey_table(NULL, NULL); |
75 | 0 | } |
76 | 0 | g_hash_table_insert(client_table, private->id, (gpointer) cib); |
77 | 0 | } |
78 | | |
79 | | /*! |
80 | | * \internal |
81 | | * \brief Remove a CIB file client from client table |
82 | | * |
83 | | * \param[in] cib CIB client |
84 | | */ |
85 | | static void |
86 | | unregister_client(const cib_t *cib) |
87 | 0 | { |
88 | 0 | cib_file_opaque_t *private = cib->variant_opaque; |
89 | |
|
90 | 0 | if (client_table == NULL) { |
91 | 0 | return; |
92 | 0 | } |
93 | | |
94 | 0 | g_hash_table_remove(client_table, private->id); |
95 | | |
96 | | /* @COMPAT: Add to crm_exit() when libcib and libcrmcommon are merged, |
97 | | * instead of destroying the client table when there are no more clients. |
98 | | */ |
99 | 0 | if (g_hash_table_size(client_table) == 0) { |
100 | 0 | g_hash_table_destroy(client_table); |
101 | 0 | client_table = NULL; |
102 | 0 | } |
103 | 0 | } |
104 | | |
105 | | /*! |
106 | | * \internal |
107 | | * \brief Look up a CIB file client by its ID |
108 | | * |
109 | | * \param[in] client_id CIB client ID |
110 | | * |
111 | | * \return CIB client with matching ID if found, or \p NULL otherwise |
112 | | */ |
113 | | static cib_t * |
114 | | get_client(const char *client_id) |
115 | 0 | { |
116 | 0 | if (client_table == NULL) { |
117 | 0 | return NULL; |
118 | 0 | } |
119 | 0 | return g_hash_table_lookup(client_table, (gpointer) client_id); |
120 | 0 | } |
121 | | |
122 | | static const cib__op_fn_t cib_op_functions[] = { |
123 | | [cib__op_apply_patch] = cib_process_diff, |
124 | | [cib__op_bump] = cib_process_bump, |
125 | | [cib__op_commit_transact] = cib_file_process_commit_transaction, |
126 | | [cib__op_create] = cib_process_create, |
127 | | [cib__op_delete] = cib_process_delete, |
128 | | [cib__op_erase] = cib_process_erase, |
129 | | [cib__op_modify] = cib_process_modify, |
130 | | [cib__op_query] = cib_process_query, |
131 | | [cib__op_replace] = cib_process_replace, |
132 | | [cib__op_upgrade] = cib_process_upgrade, |
133 | | }; |
134 | | |
135 | | /* cib_file_backup() and cib_file_write_with_digest() need to chown the |
136 | | * written files only in limited circumstances, so these variables allow |
137 | | * that to be indicated without affecting external callers |
138 | | */ |
139 | | static uid_t cib_file_owner = 0; |
140 | | static uid_t cib_file_group = 0; |
141 | | static gboolean cib_do_chown = FALSE; |
142 | | |
143 | 0 | #define cib_set_file_flags(cibfile, flags_to_set) do { \ |
144 | 0 | (cibfile)->flags = pcmk__set_flags_as(__func__, __LINE__, \ |
145 | 0 | LOG_TRACE, "CIB file", \ |
146 | 0 | cibfile->filename, \ |
147 | 0 | (cibfile)->flags, \ |
148 | 0 | (flags_to_set), \ |
149 | 0 | #flags_to_set); \ |
150 | 0 | } while (0) |
151 | | |
152 | 0 | #define cib_clear_file_flags(cibfile, flags_to_clear) do { \ |
153 | 0 | (cibfile)->flags = pcmk__clear_flags_as(__func__, __LINE__, \ |
154 | 0 | LOG_TRACE, "CIB file", \ |
155 | 0 | cibfile->filename, \ |
156 | 0 | (cibfile)->flags, \ |
157 | 0 | (flags_to_clear), \ |
158 | 0 | #flags_to_clear); \ |
159 | 0 | } while (0) |
160 | | |
161 | | /*! |
162 | | * \internal |
163 | | * \brief Get the function that performs a given CIB file operation |
164 | | * |
165 | | * \param[in] operation Operation whose function to look up |
166 | | * |
167 | | * \return Function that performs \p operation for a CIB file client |
168 | | */ |
169 | | static cib__op_fn_t |
170 | | file_get_op_function(const cib__operation_t *operation) |
171 | 0 | { |
172 | 0 | enum cib__op_type type = operation->type; |
173 | |
|
174 | 0 | pcmk__assert(type >= 0); |
175 | |
|
176 | 0 | if (type >= PCMK__NELEM(cib_op_functions)) { |
177 | 0 | return NULL; |
178 | 0 | } |
179 | 0 | return cib_op_functions[type]; |
180 | 0 | } |
181 | | |
182 | | /*! |
183 | | * \internal |
184 | | * \brief Check whether a file is the live CIB |
185 | | * |
186 | | * \param[in] filename Name of file to check |
187 | | * |
188 | | * \return TRUE if file exists and its real path is same as live CIB's |
189 | | */ |
190 | | static gboolean |
191 | | cib_file_is_live(const char *filename) |
192 | 23 | { |
193 | 23 | gboolean same = FALSE; |
194 | | |
195 | 23 | if (filename != NULL) { |
196 | | // Canonicalize file names for true comparison |
197 | 23 | char *real_filename = NULL; |
198 | | |
199 | 23 | if (pcmk__real_path(filename, &real_filename) == pcmk_rc_ok) { |
200 | 1 | char *real_livename = NULL; |
201 | | |
202 | 1 | if (pcmk__real_path(CRM_CONFIG_DIR "/" CIB_LIVE_NAME, |
203 | 1 | &real_livename) == pcmk_rc_ok) { |
204 | 0 | same = !strcmp(real_filename, real_livename); |
205 | 0 | free(real_livename); |
206 | 0 | } |
207 | 1 | free(real_filename); |
208 | 1 | } |
209 | 23 | } |
210 | 23 | return same; |
211 | 23 | } |
212 | | |
213 | | static int |
214 | | cib_file_process_request(cib_t *cib, xmlNode *request, xmlNode **output) |
215 | 0 | { |
216 | 0 | int rc = pcmk_ok; |
217 | 0 | const cib__operation_t *operation = NULL; |
218 | 0 | cib__op_fn_t op_function = NULL; |
219 | |
|
220 | 0 | int call_id = 0; |
221 | 0 | uint32_t call_options = cib_none; |
222 | 0 | const char *op = pcmk__xe_get(request, PCMK__XA_CIB_OP); |
223 | 0 | const char *section = pcmk__xe_get(request, PCMK__XA_CIB_SECTION); |
224 | 0 | xmlNode *wrapper = pcmk__xe_first_child(request, PCMK__XE_CIB_CALLDATA, |
225 | 0 | NULL, NULL); |
226 | 0 | xmlNode *data = pcmk__xe_first_child(wrapper, NULL, NULL, NULL); |
227 | |
|
228 | 0 | bool changed = false; |
229 | 0 | bool read_only = false; |
230 | 0 | xmlNode *result_cib = NULL; |
231 | 0 | xmlNode *cib_diff = NULL; |
232 | |
|
233 | 0 | cib_file_opaque_t *private = cib->variant_opaque; |
234 | | |
235 | | // We error checked these in callers |
236 | 0 | cib__get_operation(op, &operation); |
237 | 0 | op_function = file_get_op_function(operation); |
238 | |
|
239 | 0 | pcmk__xe_get_int(request, PCMK__XA_CIB_CALLID, &call_id); |
240 | 0 | rc = pcmk__xe_get_flags(request, PCMK__XA_CIB_CALLOPT, &call_options, |
241 | 0 | cib_none); |
242 | 0 | if (rc != pcmk_rc_ok) { |
243 | 0 | pcmk__warn("Couldn't parse options from request: %s", pcmk_rc_str(rc)); |
244 | 0 | } |
245 | |
|
246 | 0 | read_only = !pcmk__is_set(operation->flags, cib__op_attr_modifies); |
247 | | |
248 | | // Mirror the logic in prepare_input() in the CIB manager |
249 | 0 | if ((section != NULL) && pcmk__xe_is(data, PCMK_XE_CIB)) { |
250 | |
|
251 | 0 | data = pcmk_find_cib_element(data, section); |
252 | 0 | } |
253 | |
|
254 | 0 | rc = cib_perform_op(cib, op, call_options, op_function, read_only, section, |
255 | 0 | request, data, true, &changed, &private->cib_xml, |
256 | 0 | &result_cib, &cib_diff, output); |
257 | |
|
258 | 0 | if (pcmk__is_set(call_options, cib_transaction)) { |
259 | | /* The rest of the logic applies only to the transaction as a whole, not |
260 | | * to individual requests. |
261 | | */ |
262 | 0 | goto done; |
263 | 0 | } |
264 | | |
265 | 0 | if (rc == -pcmk_err_schema_validation) { |
266 | | // Show validation errors to stderr |
267 | 0 | pcmk__validate_xml(result_cib, NULL, NULL, NULL); |
268 | |
|
269 | 0 | } else if ((rc == pcmk_ok) && !read_only) { |
270 | 0 | pcmk__log_xml_patchset(LOG_DEBUG, cib_diff); |
271 | | |
272 | 0 | if (result_cib != private->cib_xml) { |
273 | 0 | pcmk__xml_free(private->cib_xml); |
274 | 0 | private->cib_xml = result_cib; |
275 | 0 | } |
276 | 0 | cib_set_file_flags(private, cib_file_flag_dirty); |
277 | 0 | } |
278 | | |
279 | 0 | done: |
280 | 0 | if ((result_cib != private->cib_xml) && (result_cib != *output)) { |
281 | 0 | pcmk__xml_free(result_cib); |
282 | 0 | } |
283 | 0 | pcmk__xml_free(cib_diff); |
284 | 0 | return rc; |
285 | 0 | } |
286 | | |
287 | | static int |
288 | | cib_file_perform_op_delegate(cib_t *cib, const char *op, const char *host, |
289 | | const char *section, xmlNode *data, |
290 | | xmlNode **output_data, int call_options, |
291 | | const char *user_name) |
292 | 0 | { |
293 | 0 | int rc = pcmk_ok; |
294 | 0 | xmlNode *request = NULL; |
295 | 0 | xmlNode *output = NULL; |
296 | 0 | cib_file_opaque_t *private = cib->variant_opaque; |
297 | |
|
298 | 0 | const cib__operation_t *operation = NULL; |
299 | |
|
300 | 0 | pcmk__info("Handling %s operation for %s as %s", |
301 | 0 | pcmk__s(op, "invalid"), pcmk__s(section, "entire CIB"), |
302 | 0 | pcmk__s(user_name, "default user")); |
303 | |
|
304 | 0 | if (output_data != NULL) { |
305 | 0 | *output_data = NULL; |
306 | 0 | } |
307 | |
|
308 | 0 | if (cib->state == cib_disconnected) { |
309 | 0 | return -ENOTCONN; |
310 | 0 | } |
311 | | |
312 | 0 | rc = cib__get_operation(op, &operation); |
313 | 0 | rc = pcmk_rc2legacy(rc); |
314 | 0 | if (rc != pcmk_ok) { |
315 | | // @COMPAT: At compatibility break, use rc directly |
316 | 0 | return -EPROTONOSUPPORT; |
317 | 0 | } |
318 | | |
319 | 0 | if (file_get_op_function(operation) == NULL) { |
320 | | // @COMPAT: At compatibility break, use EOPNOTSUPP |
321 | 0 | pcmk__err("Operation %s is not supported by CIB file clients", op); |
322 | 0 | return -EPROTONOSUPPORT; |
323 | 0 | } |
324 | | |
325 | 0 | cib__set_call_options(call_options, "file operation", cib_no_mtime); |
326 | |
|
327 | 0 | rc = cib__create_op(cib, op, host, section, data, call_options, user_name, |
328 | 0 | NULL, &request); |
329 | 0 | if (rc != pcmk_ok) { |
330 | 0 | return rc; |
331 | 0 | } |
332 | 0 | pcmk__xe_set(request, PCMK__XA_ACL_TARGET, user_name); |
333 | 0 | pcmk__xe_set(request, PCMK__XA_CIB_CLIENTID, private->id); |
334 | |
|
335 | 0 | if (pcmk__is_set(call_options, cib_transaction)) { |
336 | 0 | rc = cib__extend_transaction(cib, request); |
337 | 0 | goto done; |
338 | 0 | } |
339 | | |
340 | 0 | rc = cib_file_process_request(cib, request, &output); |
341 | |
|
342 | 0 | if ((output_data != NULL) && (output != NULL)) { |
343 | 0 | if (output->doc == private->cib_xml->doc) { |
344 | 0 | *output_data = pcmk__xml_copy(NULL, output); |
345 | 0 | } else { |
346 | 0 | *output_data = output; |
347 | 0 | } |
348 | 0 | } |
349 | |
|
350 | 0 | done: |
351 | 0 | if ((output != NULL) |
352 | 0 | && (output->doc != private->cib_xml->doc) |
353 | 0 | && ((output_data == NULL) || (output != *output_data))) { |
354 | |
|
355 | 0 | pcmk__xml_free(output); |
356 | 0 | } |
357 | 0 | pcmk__xml_free(request); |
358 | 0 | return rc; |
359 | 0 | } |
360 | | |
361 | | /*! |
362 | | * \internal |
363 | | * \brief Read CIB from disk and validate it against XML schema |
364 | | * |
365 | | * \param[in] filename Name of file to read CIB from |
366 | | * \param[out] output Where to store the read CIB XML |
367 | | * |
368 | | * \return pcmk_ok on success, |
369 | | * -ENXIO if file does not exist (or stat() otherwise fails), or |
370 | | * -pcmk_err_schema_validation if XML doesn't parse or validate |
371 | | * \note If filename is the live CIB, this will *not* verify its digest, |
372 | | * though that functionality would be trivial to add here. |
373 | | * Also, this will *not* verify that the file is writable, |
374 | | * because some callers might not need to write. |
375 | | */ |
376 | | static int |
377 | | load_file_cib(const char *filename, xmlNode **output) |
378 | 0 | { |
379 | 0 | struct stat buf; |
380 | 0 | xmlNode *root = NULL; |
381 | | |
382 | | /* Ensure file is readable */ |
383 | 0 | if (strcmp(filename, "-") && (stat(filename, &buf) < 0)) { |
384 | 0 | return -ENXIO; |
385 | 0 | } |
386 | | |
387 | | /* Parse XML from file */ |
388 | 0 | root = pcmk__xml_read(filename); |
389 | 0 | if (root == NULL) { |
390 | 0 | return -pcmk_err_schema_validation; |
391 | 0 | } |
392 | | |
393 | | /* Add a status section if not already present */ |
394 | 0 | if (pcmk__xe_first_child(root, PCMK_XE_STATUS, NULL, NULL) == NULL) { |
395 | 0 | pcmk__xe_create(root, PCMK_XE_STATUS); |
396 | 0 | } |
397 | | |
398 | | /* Validate XML against its specified schema */ |
399 | 0 | if (!pcmk__configured_schema_validates(root)) { |
400 | 0 | pcmk__xml_free(root); |
401 | 0 | return -pcmk_err_schema_validation; |
402 | 0 | } |
403 | | |
404 | | /* Remember the parsed XML for later use */ |
405 | 0 | *output = root; |
406 | 0 | return pcmk_ok; |
407 | 0 | } |
408 | | |
409 | | static int |
410 | | cib_file_signon(cib_t *cib, const char *name, enum cib_conn_type type) |
411 | 0 | { |
412 | 0 | int rc = pcmk_ok; |
413 | 0 | cib_file_opaque_t *private = cib->variant_opaque; |
414 | |
|
415 | 0 | if (private->filename == NULL) { |
416 | 0 | rc = -EINVAL; |
417 | 0 | } else { |
418 | 0 | rc = load_file_cib(private->filename, &private->cib_xml); |
419 | 0 | } |
420 | |
|
421 | 0 | if (rc == pcmk_ok) { |
422 | 0 | pcmk__debug("Opened connection to local file '%s' for %s", |
423 | 0 | private->filename, pcmk__s(name, "client")); |
424 | 0 | cib->state = cib_connected_command; |
425 | 0 | cib->type = cib_command; |
426 | 0 | register_client(cib); |
427 | |
|
428 | 0 | } else { |
429 | 0 | pcmk__info("Connection to local file '%s' for %s (client %s) failed: " |
430 | 0 | "%s", |
431 | 0 | private->filename, pcmk__s(name, "client"), private->id, |
432 | 0 | pcmk_strerror(rc)); |
433 | 0 | } |
434 | 0 | return rc; |
435 | 0 | } |
436 | | |
437 | | /*! |
438 | | * \internal |
439 | | * \brief Write out the in-memory CIB to a live CIB file |
440 | | * |
441 | | * \param[in] cib_root Root of XML tree to write |
442 | | * \param[in,out] path Full path to file to write |
443 | | * |
444 | | * \return Standard Pacemaker return code |
445 | | */ |
446 | | static int |
447 | | cib_file_write_live(xmlNode *cib_root, char *path) |
448 | 0 | { |
449 | 0 | uid_t euid = geteuid(); |
450 | 0 | uid_t daemon_uid = 0; |
451 | 0 | gid_t daemon_gid = 0; |
452 | 0 | char *sep = strrchr(path, '/'); |
453 | 0 | const char *cib_dirname, *cib_filename; |
454 | 0 | int rc = pcmk_rc_ok; |
455 | | |
456 | | /* Get the desired uid/gid */ |
457 | 0 | rc = pcmk__daemon_user(&daemon_uid, &daemon_gid); |
458 | 0 | if (rc != pcmk_rc_ok) { |
459 | 0 | pcmk__err("Could not find user " CRM_DAEMON_USER ": %s", |
460 | 0 | pcmk_rc_str(rc)); |
461 | 0 | return rc; |
462 | 0 | } |
463 | | |
464 | | /* If we're root, we can change the ownership; |
465 | | * if we're daemon, anything we create will be OK; |
466 | | * otherwise, block access so we don't create wrong owner |
467 | | */ |
468 | 0 | if ((euid != 0) && (euid != daemon_uid)) { |
469 | 0 | pcmk__err("Must be root or " CRM_DAEMON_USER " to modify live CIB"); |
470 | | |
471 | | // @TODO Should this return an error instead? |
472 | 0 | return pcmk_rc_ok; |
473 | 0 | } |
474 | | |
475 | | /* fancy footwork to separate dirname from filename |
476 | | * (we know the canonical name maps to the live CIB, |
477 | | * but the given name might be relative, or symlinked) |
478 | | */ |
479 | 0 | if (sep == NULL) { /* no directory component specified */ |
480 | 0 | cib_dirname = "./"; |
481 | 0 | cib_filename = path; |
482 | 0 | } else if (sep == path) { /* given name is in / */ |
483 | 0 | cib_dirname = "/"; |
484 | 0 | cib_filename = path + 1; |
485 | 0 | } else { /* typical case; split given name into parts */ |
486 | 0 | *sep = '\0'; |
487 | 0 | cib_dirname = path; |
488 | 0 | cib_filename = sep + 1; |
489 | 0 | } |
490 | | |
491 | | /* if we're root, we want to update the file ownership */ |
492 | 0 | if (euid == 0) { |
493 | 0 | cib_file_owner = daemon_uid; |
494 | 0 | cib_file_group = daemon_gid; |
495 | 0 | cib_do_chown = TRUE; |
496 | 0 | } |
497 | | |
498 | | /* write the file */ |
499 | 0 | rc = cib_file_write_with_digest(cib_root, cib_dirname, cib_filename); |
500 | 0 | rc = pcmk_legacy2rc(rc); |
501 | | |
502 | | /* turn off file ownership changes, for other callers */ |
503 | 0 | if (euid == 0) { |
504 | 0 | cib_do_chown = FALSE; |
505 | 0 | } |
506 | | |
507 | | /* undo fancy stuff */ |
508 | 0 | if ((sep != NULL) && (*sep == '\0')) { |
509 | 0 | *sep = '/'; |
510 | 0 | } |
511 | |
|
512 | 0 | return rc; |
513 | 0 | } |
514 | | |
515 | | /*! |
516 | | * \internal |
517 | | * \brief Sign-off method for CIB file variants |
518 | | * |
519 | | * This will write the file to disk if needed, and free the in-memory CIB. If |
520 | | * the file is the live CIB, it will compute and write a signature as well. |
521 | | * |
522 | | * \param[in,out] cib CIB object to sign off |
523 | | * |
524 | | * \return pcmk_ok on success, pcmk_err_generic on failure |
525 | | * \todo This method should refuse to write the live CIB if the CIB manager is |
526 | | * running. |
527 | | */ |
528 | | static int |
529 | | cib_file_signoff(cib_t *cib) |
530 | 0 | { |
531 | 0 | int rc = pcmk_ok; |
532 | 0 | cib_file_opaque_t *private = cib->variant_opaque; |
533 | |
|
534 | 0 | pcmk__debug("Disconnecting from the CIB manager"); |
535 | 0 | cib->state = cib_disconnected; |
536 | 0 | cib->type = cib_no_connection; |
537 | 0 | unregister_client(cib); |
538 | 0 | cib->cmds->end_transaction(cib, false, cib_none); |
539 | | |
540 | | /* If the in-memory CIB has been changed, write it to disk */ |
541 | 0 | if (pcmk__is_set(private->flags, cib_file_flag_dirty)) { |
542 | | |
543 | | /* If this is the live CIB, write it out with a digest */ |
544 | 0 | if (pcmk__is_set(private->flags, cib_file_flag_live)) { |
545 | 0 | rc = cib_file_write_live(private->cib_xml, private->filename); |
546 | 0 | rc = pcmk_rc2legacy(rc); |
547 | | |
548 | | /* Otherwise, it's a simple write */ |
549 | 0 | } else { |
550 | 0 | bool compress = g_str_has_suffix(private->filename, ".bz2"); |
551 | |
|
552 | 0 | if (pcmk__xml_write_file(private->cib_xml, private->filename, |
553 | 0 | compress) != pcmk_rc_ok) { |
554 | 0 | rc = pcmk_err_generic; |
555 | 0 | } |
556 | 0 | } |
557 | |
|
558 | 0 | if (rc == pcmk_ok) { |
559 | 0 | pcmk__info("Wrote CIB to %s", private->filename); |
560 | 0 | cib_clear_file_flags(private, cib_file_flag_dirty); |
561 | 0 | } else { |
562 | 0 | pcmk__err("Could not write CIB to %s", private->filename); |
563 | 0 | } |
564 | 0 | } |
565 | | |
566 | | /* Free the in-memory CIB */ |
567 | 0 | pcmk__xml_free(private->cib_xml); |
568 | 0 | private->cib_xml = NULL; |
569 | 0 | return rc; |
570 | 0 | } |
571 | | |
572 | | static int |
573 | | cib_file_free(cib_t *cib) |
574 | 23 | { |
575 | 23 | int rc = pcmk_ok; |
576 | | |
577 | 23 | if (cib->state != cib_disconnected) { |
578 | 0 | rc = cib_file_signoff(cib); |
579 | 0 | } |
580 | | |
581 | 23 | if (rc == pcmk_ok) { |
582 | 23 | cib_file_opaque_t *private = cib->variant_opaque; |
583 | | |
584 | 23 | free(private->id); |
585 | 23 | free(private->filename); |
586 | 23 | free(private); |
587 | 23 | free(cib->cmds); |
588 | 23 | free(cib->user); |
589 | 23 | free(cib); |
590 | | |
591 | 23 | } else { |
592 | 0 | fprintf(stderr, "Couldn't sign off: %d\n", rc); |
593 | 0 | } |
594 | | |
595 | 23 | return rc; |
596 | 23 | } |
597 | | |
598 | | static int |
599 | | cib_file_register_notification(cib_t *cib, const char *callback, int enabled) |
600 | 0 | { |
601 | 0 | return -EPROTONOSUPPORT; |
602 | 0 | } |
603 | | |
604 | | static int |
605 | | cib_file_set_connection_dnotify(cib_t *cib, |
606 | | void (*dnotify) (gpointer user_data)) |
607 | 0 | { |
608 | 0 | return -EPROTONOSUPPORT; |
609 | 0 | } |
610 | | |
611 | | /*! |
612 | | * \internal |
613 | | * \brief Get the given CIB connection's unique client identifier |
614 | | * |
615 | | * \param[in] cib CIB connection |
616 | | * \param[out] async_id If not \p NULL, where to store asynchronous client ID |
617 | | * \param[out] sync_id If not \p NULL, where to store synchronous client ID |
618 | | * |
619 | | * \return Legacy Pacemaker return code |
620 | | * |
621 | | * \note This is the \p cib_file variant implementation of |
622 | | * \p cib_api_operations_t:client_id(). |
623 | | */ |
624 | | static int |
625 | | cib_file_client_id(const cib_t *cib, const char **async_id, |
626 | | const char **sync_id) |
627 | 0 | { |
628 | 0 | cib_file_opaque_t *private = cib->variant_opaque; |
629 | |
|
630 | 0 | if (async_id != NULL) { |
631 | 0 | *async_id = private->id; |
632 | 0 | } |
633 | 0 | if (sync_id != NULL) { |
634 | 0 | *sync_id = private->id; |
635 | 0 | } |
636 | 0 | return pcmk_ok; |
637 | 0 | } |
638 | | |
639 | | cib_t * |
640 | | cib_file_new(const char *cib_location) |
641 | 23 | { |
642 | 23 | cib_t *cib = NULL; |
643 | 23 | cib_file_opaque_t *private = NULL; |
644 | 23 | char *filename = NULL; |
645 | | |
646 | 23 | if (cib_location == NULL) { |
647 | 0 | cib_location = getenv("CIB_file"); |
648 | 0 | if (cib_location == NULL) { |
649 | 0 | return NULL; // Shouldn't be possible if we were called internally |
650 | 0 | } |
651 | 0 | } |
652 | | |
653 | 23 | cib = cib_new_variant(); |
654 | 23 | if (cib == NULL) { |
655 | 0 | return NULL; |
656 | 0 | } |
657 | | |
658 | 23 | filename = strdup(cib_location); |
659 | 23 | if (filename == NULL) { |
660 | 0 | free(cib); |
661 | 0 | return NULL; |
662 | 0 | } |
663 | | |
664 | 23 | private = calloc(1, sizeof(cib_file_opaque_t)); |
665 | 23 | if (private == NULL) { |
666 | 0 | free(cib); |
667 | 0 | free(filename); |
668 | 0 | return NULL; |
669 | 0 | } |
670 | | |
671 | 23 | private->id = pcmk__generate_uuid(); |
672 | 23 | private->filename = filename; |
673 | | |
674 | 23 | cib->variant = cib_file; |
675 | 23 | cib->variant_opaque = private; |
676 | | |
677 | 23 | private->flags = 0; |
678 | 23 | if (cib_file_is_live(cib_location)) { |
679 | 0 | cib_set_file_flags(private, cib_file_flag_live); |
680 | 0 | pcmk__trace("File %s detected as live CIB", cib_location); |
681 | 0 | } |
682 | | |
683 | | /* assign variant specific ops */ |
684 | 23 | cib->delegate_fn = cib_file_perform_op_delegate; |
685 | 23 | cib->cmds->signon = cib_file_signon; |
686 | 23 | cib->cmds->signoff = cib_file_signoff; |
687 | 23 | cib->cmds->free = cib_file_free; |
688 | 23 | cib->cmds->register_notification = cib_file_register_notification; |
689 | 23 | cib->cmds->set_connection_dnotify = cib_file_set_connection_dnotify; |
690 | | |
691 | 23 | cib->cmds->client_id = cib_file_client_id; |
692 | | |
693 | 23 | return cib; |
694 | 23 | } |
695 | | |
696 | | /*! |
697 | | * \internal |
698 | | * \brief Compare the calculated digest of an XML tree against a signature file |
699 | | * |
700 | | * \param[in] root Root of XML tree to compare |
701 | | * \param[in] sigfile Name of signature file containing digest to compare |
702 | | * |
703 | | * \return TRUE if digests match or signature file does not exist, else FALSE |
704 | | */ |
705 | | static gboolean |
706 | | cib_file_verify_digest(xmlNode *root, const char *sigfile) |
707 | 0 | { |
708 | 0 | gboolean passed = FALSE; |
709 | 0 | char *expected; |
710 | 0 | int rc = pcmk__file_contents(sigfile, &expected); |
711 | |
|
712 | 0 | switch (rc) { |
713 | 0 | case pcmk_rc_ok: |
714 | 0 | if (expected == NULL) { |
715 | 0 | pcmk__err("On-disk digest at %s is empty", sigfile); |
716 | 0 | return FALSE; |
717 | 0 | } |
718 | 0 | break; |
719 | 0 | case ENOENT: |
720 | 0 | pcmk__warn("No on-disk digest present at %s", sigfile); |
721 | 0 | return TRUE; |
722 | 0 | default: |
723 | 0 | pcmk__err("Could not read on-disk digest from %s: %s", sigfile, |
724 | 0 | pcmk_rc_str(rc)); |
725 | 0 | return FALSE; |
726 | 0 | } |
727 | 0 | passed = pcmk__verify_digest(root, expected); |
728 | 0 | free(expected); |
729 | 0 | return passed; |
730 | 0 | } |
731 | | |
732 | | /*! |
733 | | * \internal |
734 | | * \brief Read an XML tree from a file and verify its digest |
735 | | * |
736 | | * \param[in] filename Name of XML file to read |
737 | | * \param[in] sigfile Name of signature file containing digest to compare |
738 | | * \param[out] root If non-NULL, will be set to pointer to parsed XML tree |
739 | | * |
740 | | * \return 0 if file was successfully read, parsed and verified, otherwise: |
741 | | * -errno on stat() failure, |
742 | | * -pcmk_err_cib_corrupt if file size is 0 or XML is not parseable, or |
743 | | * -pcmk_err_cib_modified if digests do not match |
744 | | * \note If root is non-NULL, it is the caller's responsibility to free *root on |
745 | | * successful return. |
746 | | */ |
747 | | int |
748 | | cib_file_read_and_verify(const char *filename, const char *sigfile, xmlNode **root) |
749 | 0 | { |
750 | 0 | int s_res; |
751 | 0 | struct stat buf; |
752 | 0 | char *local_sigfile = NULL; |
753 | 0 | xmlNode *local_root = NULL; |
754 | |
|
755 | 0 | pcmk__assert(filename != NULL); |
756 | 0 | if (root) { |
757 | 0 | *root = NULL; |
758 | 0 | } |
759 | | |
760 | | /* Verify that file exists and its size is nonzero */ |
761 | 0 | s_res = stat(filename, &buf); |
762 | 0 | if (s_res < 0) { |
763 | 0 | pcmk__warn("Could not verify cluster configuration file %s: " |
764 | 0 | "stat() failed: %s", |
765 | 0 | filename, strerror(errno)); |
766 | 0 | return -errno; |
767 | 0 | } else if (buf.st_size == 0) { |
768 | 0 | pcmk__warn("Cluster configuration file %s is corrupt (size is zero)", |
769 | 0 | filename); |
770 | 0 | return -pcmk_err_cib_corrupt; |
771 | 0 | } |
772 | | |
773 | | /* Parse XML */ |
774 | 0 | local_root = pcmk__xml_read(filename); |
775 | 0 | if (local_root == NULL) { |
776 | 0 | pcmk__warn("Cluster configuration file %s is corrupt (unparseable as " |
777 | 0 | "XML)", |
778 | 0 | filename); |
779 | 0 | return -pcmk_err_cib_corrupt; |
780 | 0 | } |
781 | | |
782 | | /* If sigfile is not specified, use original file name plus .sig */ |
783 | 0 | if (sigfile == NULL) { |
784 | 0 | sigfile = local_sigfile = pcmk__assert_asprintf("%s.sig", filename); |
785 | 0 | } |
786 | | |
787 | | /* Verify that digests match */ |
788 | 0 | if (cib_file_verify_digest(local_root, sigfile) == FALSE) { |
789 | 0 | free(local_sigfile); |
790 | 0 | pcmk__xml_free(local_root); |
791 | 0 | return -pcmk_err_cib_modified; |
792 | 0 | } |
793 | | |
794 | 0 | free(local_sigfile); |
795 | 0 | if (root) { |
796 | 0 | *root = local_root; |
797 | 0 | } else { |
798 | 0 | pcmk__xml_free(local_root); |
799 | 0 | } |
800 | 0 | return pcmk_ok; |
801 | 0 | } |
802 | | |
803 | | /*! |
804 | | * \internal |
805 | | * \brief Back up a CIB |
806 | | * |
807 | | * \param[in] cib_dirname Directory containing CIB file and backups |
808 | | * \param[in] cib_filename Name (relative to cib_dirname) of CIB file to back up |
809 | | * |
810 | | * \return 0 on success, -1 on error |
811 | | */ |
812 | | static int |
813 | | cib_file_backup(const char *cib_dirname, const char *cib_filename) |
814 | 0 | { |
815 | 0 | int rc = 0; |
816 | 0 | unsigned int seq = 0U; |
817 | 0 | char *cib_path = pcmk__assert_asprintf("%s/%s", cib_dirname, cib_filename); |
818 | 0 | char *cib_digest = pcmk__assert_asprintf("%s.sig", cib_path); |
819 | 0 | char *backup_path; |
820 | 0 | char *backup_digest; |
821 | | |
822 | | // Determine backup and digest file names |
823 | 0 | if (pcmk__read_series_sequence(cib_dirname, CIB_SERIES, |
824 | 0 | &seq) != pcmk_rc_ok) { |
825 | | // @TODO maybe handle errors better ... |
826 | 0 | seq = 0U; |
827 | 0 | } |
828 | 0 | backup_path = pcmk__series_filename(cib_dirname, CIB_SERIES, seq, |
829 | 0 | CIB_SERIES_BZIP); |
830 | 0 | backup_digest = pcmk__assert_asprintf("%s.sig", backup_path); |
831 | | |
832 | | /* Remove the old backups if they exist */ |
833 | 0 | unlink(backup_path); |
834 | 0 | unlink(backup_digest); |
835 | | |
836 | | /* Back up the CIB, by hard-linking it to the backup name */ |
837 | 0 | if ((link(cib_path, backup_path) < 0) && (errno != ENOENT)) { |
838 | 0 | pcmk__err("Could not archive %s by linking to %s: %s", cib_path, |
839 | 0 | backup_path, strerror(errno)); |
840 | 0 | rc = -1; |
841 | | |
842 | | /* Back up the CIB signature similarly */ |
843 | 0 | } else if ((link(cib_digest, backup_digest) < 0) && (errno != ENOENT)) { |
844 | 0 | pcmk__err("Could not archive %s by linking to %s: %s", cib_digest, |
845 | 0 | backup_digest, strerror(errno)); |
846 | 0 | rc = -1; |
847 | | |
848 | | /* Update the last counter and ensure everything is sync'd to media */ |
849 | 0 | } else { |
850 | 0 | pcmk__write_series_sequence(cib_dirname, CIB_SERIES, ++seq, |
851 | 0 | CIB_SERIES_MAX); |
852 | 0 | if (cib_do_chown) { |
853 | 0 | int rc2; |
854 | |
|
855 | 0 | if ((chown(backup_path, cib_file_owner, cib_file_group) < 0) |
856 | 0 | && (errno != ENOENT)) { |
857 | |
|
858 | 0 | pcmk__err("Could not set owner of %s: %s", backup_path, |
859 | 0 | strerror(errno)); |
860 | 0 | rc = -1; |
861 | 0 | } |
862 | 0 | if ((chown(backup_digest, cib_file_owner, cib_file_group) < 0) |
863 | 0 | && (errno != ENOENT)) { |
864 | |
|
865 | 0 | pcmk__err("Could not set owner of %s: %s", backup_digest, |
866 | 0 | strerror(errno)); |
867 | 0 | rc = -1; |
868 | 0 | } |
869 | 0 | rc2 = pcmk__chown_series_sequence(cib_dirname, CIB_SERIES, |
870 | 0 | cib_file_owner, cib_file_group); |
871 | 0 | if (rc2 != pcmk_rc_ok) { |
872 | 0 | pcmk__err("Could not set owner of sequence file in %s: %s", |
873 | 0 | cib_dirname, pcmk_rc_str(rc2)); |
874 | 0 | rc = -1; |
875 | 0 | } |
876 | 0 | } |
877 | 0 | pcmk__sync_directory(cib_dirname); |
878 | 0 | pcmk__info("Archived previous version as %s", backup_path); |
879 | 0 | } |
880 | |
|
881 | 0 | free(cib_path); |
882 | 0 | free(cib_digest); |
883 | 0 | free(backup_path); |
884 | 0 | free(backup_digest); |
885 | 0 | return rc; |
886 | 0 | } |
887 | | |
888 | | /*! |
889 | | * \internal |
890 | | * \brief Prepare CIB XML to be written to disk |
891 | | * |
892 | | * Set \c PCMK_XA_NUM_UPDATES to 0, set \c PCMK_XA_CIB_LAST_WRITTEN to the |
893 | | * current timestamp, and strip out the status section. |
894 | | * |
895 | | * \param[in,out] root Root of CIB XML tree |
896 | | * |
897 | | * \return void |
898 | | */ |
899 | | static void |
900 | | cib_file_prepare_xml(xmlNode *root) |
901 | 0 | { |
902 | 0 | xmlNode *cib_status_root = NULL; |
903 | | |
904 | | /* Always write out with num_updates=0 and current last-written timestamp */ |
905 | 0 | pcmk__xe_set(root, PCMK_XA_NUM_UPDATES, "0"); |
906 | 0 | pcmk__xe_add_last_written(root); |
907 | | |
908 | | /* Delete status section before writing to file, because |
909 | | * we discard it on startup anyway, and users get confused by it */ |
910 | 0 | cib_status_root = pcmk__xe_first_child(root, PCMK_XE_STATUS, NULL, NULL); |
911 | 0 | CRM_CHECK(cib_status_root != NULL, return); |
912 | 0 | pcmk__xml_free(cib_status_root); |
913 | 0 | } |
914 | | |
915 | | /*! |
916 | | * \internal |
917 | | * \brief Write CIB to disk, along with a signature file containing its digest |
918 | | * |
919 | | * \param[in,out] cib_root Root of XML tree to write |
920 | | * \param[in] cib_dirname Directory containing CIB and signature files |
921 | | * \param[in] cib_filename Name (relative to cib_dirname) of file to write |
922 | | * |
923 | | * \return pcmk_ok on success, |
924 | | * pcmk_err_cib_modified if existing cib_filename doesn't match digest, |
925 | | * pcmk_err_cib_backup if existing cib_filename couldn't be backed up, |
926 | | * or pcmk_err_cib_save if new cib_filename couldn't be saved |
927 | | */ |
928 | | int |
929 | | cib_file_write_with_digest(xmlNode *cib_root, const char *cib_dirname, |
930 | | const char *cib_filename) |
931 | 0 | { |
932 | 0 | int exit_rc = pcmk_ok; |
933 | 0 | int rc, fd; |
934 | 0 | char *digest = NULL; |
935 | | |
936 | | /* Detect CIB version for diagnostic purposes */ |
937 | 0 | const char *epoch = pcmk__xe_get(cib_root, PCMK_XA_EPOCH); |
938 | 0 | const char *admin_epoch = pcmk__xe_get(cib_root, PCMK_XA_ADMIN_EPOCH); |
939 | | |
940 | | /* Determine full CIB and signature pathnames */ |
941 | 0 | char *cib_path = pcmk__assert_asprintf("%s/%s", cib_dirname, cib_filename); |
942 | 0 | char *digest_path = pcmk__assert_asprintf("%s.sig", cib_path); |
943 | | |
944 | | /* Create temporary file name patterns for writing out CIB and signature */ |
945 | 0 | char *tmp_cib = pcmk__assert_asprintf("%s/cib.XXXXXX", cib_dirname); |
946 | 0 | char *tmp_digest = pcmk__assert_asprintf("%s/cib.XXXXXX", cib_dirname); |
947 | | |
948 | | /* Ensure the admin didn't modify the existing CIB underneath us */ |
949 | 0 | pcmk__trace("Reading cluster configuration file %s", cib_path); |
950 | 0 | rc = cib_file_read_and_verify(cib_path, NULL, NULL); |
951 | 0 | if ((rc != pcmk_ok) && (rc != -ENOENT)) { |
952 | 0 | pcmk__err("%s was manually modified while the cluster was active!", |
953 | 0 | cib_path); |
954 | 0 | exit_rc = pcmk_err_cib_modified; |
955 | 0 | goto cleanup; |
956 | 0 | } |
957 | | |
958 | | /* Back up the existing CIB */ |
959 | 0 | if (cib_file_backup(cib_dirname, cib_filename) < 0) { |
960 | 0 | exit_rc = pcmk_err_cib_backup; |
961 | 0 | goto cleanup; |
962 | 0 | } |
963 | | |
964 | 0 | pcmk__debug("Writing CIB to disk"); |
965 | 0 | umask(S_IWGRP | S_IWOTH | S_IROTH); |
966 | 0 | cib_file_prepare_xml(cib_root); |
967 | | |
968 | | /* Write the CIB to a temporary file, so we can deploy (near) atomically */ |
969 | 0 | fd = mkstemp(tmp_cib); |
970 | 0 | if (fd < 0) { |
971 | 0 | pcmk__err("Couldn't open temporary file %s for writing CIB: %s", |
972 | 0 | tmp_cib, strerror(errno)); |
973 | 0 | exit_rc = pcmk_err_cib_save; |
974 | 0 | goto cleanup; |
975 | 0 | } |
976 | | |
977 | | /* Protect the temporary file */ |
978 | 0 | if (fchmod(fd, S_IRUSR | S_IWUSR) < 0) { |
979 | 0 | pcmk__err("Couldn't protect temporary file %s for writing CIB: %s", |
980 | 0 | tmp_cib, strerror(errno)); |
981 | 0 | exit_rc = pcmk_err_cib_save; |
982 | 0 | goto cleanup; |
983 | 0 | } |
984 | 0 | if (cib_do_chown && (fchown(fd, cib_file_owner, cib_file_group) < 0)) { |
985 | 0 | pcmk__err("Couldn't protect temporary file %s for writing CIB: %s", |
986 | 0 | tmp_cib, strerror(errno)); |
987 | 0 | exit_rc = pcmk_err_cib_save; |
988 | 0 | goto cleanup; |
989 | 0 | } |
990 | | |
991 | | /* Write out the CIB */ |
992 | 0 | if (pcmk__xml_write_fd(cib_root, tmp_cib, fd) != pcmk_rc_ok) { |
993 | 0 | pcmk__err("Changes couldn't be written to %s", tmp_cib); |
994 | 0 | exit_rc = pcmk_err_cib_save; |
995 | 0 | goto cleanup; |
996 | 0 | } |
997 | | |
998 | | /* Calculate CIB digest */ |
999 | 0 | digest = pcmk__digest_on_disk_cib(cib_root); |
1000 | 0 | pcmk__assert(digest != NULL); |
1001 | 0 | pcmk__info("Wrote version %s.%s.0 of the CIB to disk (digest: %s)", |
1002 | 0 | pcmk__s(admin_epoch, "0"), pcmk__s(epoch, "0"), digest); |
1003 | | |
1004 | | /* Write the CIB digest to a temporary file */ |
1005 | 0 | fd = mkstemp(tmp_digest); |
1006 | 0 | if (fd < 0) { |
1007 | 0 | pcmk__err("Could not create temporary file %s for CIB digest: %s", |
1008 | 0 | tmp_digest, strerror(errno)); |
1009 | 0 | exit_rc = pcmk_err_cib_save; |
1010 | 0 | goto cleanup; |
1011 | 0 | } |
1012 | 0 | if (cib_do_chown && (fchown(fd, cib_file_owner, cib_file_group) < 0)) { |
1013 | 0 | pcmk__err("Couldn't protect temporary file %s for writing CIB: %s", |
1014 | 0 | tmp_cib, strerror(errno)); |
1015 | 0 | exit_rc = pcmk_err_cib_save; |
1016 | 0 | close(fd); |
1017 | 0 | goto cleanup; |
1018 | 0 | } |
1019 | 0 | rc = pcmk__write_sync(fd, digest); |
1020 | 0 | if (rc != pcmk_rc_ok) { |
1021 | 0 | pcmk__err("Could not write digest to %s: %s", tmp_digest, |
1022 | 0 | pcmk_rc_str(rc)); |
1023 | 0 | exit_rc = pcmk_err_cib_save; |
1024 | 0 | close(fd); |
1025 | 0 | goto cleanup; |
1026 | 0 | } |
1027 | 0 | close(fd); |
1028 | 0 | pcmk__debug("Wrote digest %s to disk", digest); |
1029 | | |
1030 | | /* Verify that what we wrote is sane */ |
1031 | 0 | pcmk__info("Reading cluster configuration file %s (digest: %s)", tmp_cib, |
1032 | 0 | tmp_digest); |
1033 | 0 | rc = cib_file_read_and_verify(tmp_cib, tmp_digest, NULL); |
1034 | 0 | pcmk__assert(rc == 0); |
1035 | | |
1036 | | /* Rename temporary files to live, and sync directory changes to media */ |
1037 | 0 | pcmk__debug("Activating %s", tmp_cib); |
1038 | 0 | if (rename(tmp_cib, cib_path) < 0) { |
1039 | 0 | pcmk__err("Couldn't rename %s as %s: %s", tmp_cib, cib_path, |
1040 | 0 | strerror(errno)); |
1041 | 0 | exit_rc = pcmk_err_cib_save; |
1042 | 0 | } |
1043 | 0 | if (rename(tmp_digest, digest_path) < 0) { |
1044 | 0 | pcmk__err("Couldn't rename %s as %s: %s", tmp_digest, digest_path, |
1045 | 0 | strerror(errno)); |
1046 | 0 | exit_rc = pcmk_err_cib_save; |
1047 | 0 | } |
1048 | 0 | pcmk__sync_directory(cib_dirname); |
1049 | |
|
1050 | 0 | cleanup: |
1051 | 0 | free(cib_path); |
1052 | 0 | free(digest_path); |
1053 | 0 | free(digest); |
1054 | 0 | free(tmp_digest); |
1055 | 0 | free(tmp_cib); |
1056 | 0 | return exit_rc; |
1057 | 0 | } |
1058 | | |
1059 | | /*! |
1060 | | * \internal |
1061 | | * \brief Process requests in a CIB transaction |
1062 | | * |
1063 | | * Stop when a request fails or when all requests have been processed. |
1064 | | * |
1065 | | * \param[in,out] cib CIB client |
1066 | | * \param[in,out] transaction CIB transaction |
1067 | | * |
1068 | | * \return Standard Pacemaker return code |
1069 | | */ |
1070 | | static int |
1071 | | cib_file_process_transaction_requests(cib_t *cib, xmlNode *transaction) |
1072 | 0 | { |
1073 | 0 | cib_file_opaque_t *private = cib->variant_opaque; |
1074 | |
|
1075 | 0 | for (xmlNode *request = pcmk__xe_first_child(transaction, |
1076 | 0 | PCMK__XE_CIB_COMMAND, NULL, |
1077 | 0 | NULL); |
1078 | 0 | request != NULL; |
1079 | 0 | request = pcmk__xe_next(request, PCMK__XE_CIB_COMMAND)) { |
1080 | |
|
1081 | 0 | xmlNode *output = NULL; |
1082 | 0 | const char *op = pcmk__xe_get(request, PCMK__XA_CIB_OP); |
1083 | |
|
1084 | 0 | int rc = cib_file_process_request(cib, request, &output); |
1085 | |
|
1086 | 0 | rc = pcmk_legacy2rc(rc); |
1087 | 0 | if (rc != pcmk_rc_ok) { |
1088 | 0 | pcmk__err("Aborting transaction for CIB file client (%s) on file " |
1089 | 0 | "'%s' due to failed %s request: %s", |
1090 | 0 | private->id, private->filename, op, pcmk_rc_str(rc)); |
1091 | 0 | pcmk__log_xml_info(request, "Failed request"); |
1092 | 0 | return rc; |
1093 | 0 | } |
1094 | | |
1095 | 0 | pcmk__trace("Applied %s request to transaction working CIB for CIB " |
1096 | 0 | "file client (%s) on file '%s'", |
1097 | 0 | op, private->id, private->filename); |
1098 | 0 | pcmk__log_xml_trace(request, "Successful request"); |
1099 | 0 | } |
1100 | | |
1101 | 0 | return pcmk_rc_ok; |
1102 | 0 | } |
1103 | | |
1104 | | /*! |
1105 | | * \internal |
1106 | | * \brief Commit a given CIB file client's transaction to a working CIB copy |
1107 | | * |
1108 | | * \param[in,out] cib CIB file client |
1109 | | * \param[in] transaction CIB transaction |
1110 | | * \param[in,out] result_cib Where to store result CIB |
1111 | | * |
1112 | | * \return Standard Pacemaker return code |
1113 | | * |
1114 | | * \note The caller is responsible for replacing the \p cib argument's |
1115 | | * \p private->cib_xml with \p result_cib on success, and for freeing |
1116 | | * \p result_cib using \p pcmk__xml_free() on failure. |
1117 | | */ |
1118 | | static int |
1119 | | cib_file_commit_transaction(cib_t *cib, xmlNode *transaction, |
1120 | | xmlNode **result_cib) |
1121 | 0 | { |
1122 | 0 | int rc = pcmk_rc_ok; |
1123 | 0 | cib_file_opaque_t *private = cib->variant_opaque; |
1124 | 0 | xmlNode *saved_cib = private->cib_xml; |
1125 | |
|
1126 | 0 | CRM_CHECK(pcmk__xe_is(transaction, PCMK__XE_CIB_TRANSACTION), |
1127 | 0 | return pcmk_rc_no_transaction); |
1128 | | |
1129 | | /* *result_cib should be a copy of private->cib_xml (created by |
1130 | | * cib_perform_op()). If not, make a copy now. Change tracking isn't |
1131 | | * strictly required here because: |
1132 | | * * Each request in the transaction will have changes tracked and ACLs |
1133 | | * checked if appropriate. |
1134 | | * * cib_perform_op() will infer changes for the commit request at the end. |
1135 | | */ |
1136 | 0 | CRM_CHECK((*result_cib != NULL) && (*result_cib != private->cib_xml), |
1137 | 0 | *result_cib = pcmk__xml_copy(NULL, private->cib_xml)); |
1138 | |
|
1139 | 0 | pcmk__trace("Committing transaction for CIB file client (%s) on file '%s' " |
1140 | 0 | "to working CIB", |
1141 | 0 | private->id, private->filename); |
1142 | | |
1143 | | // Apply all changes to a working copy of the CIB |
1144 | 0 | private->cib_xml = *result_cib; |
1145 | |
|
1146 | 0 | rc = cib_file_process_transaction_requests(cib, transaction); |
1147 | |
|
1148 | 0 | pcmk__trace("Transaction commit %s for CIB file client (%s) on file '%s'", |
1149 | 0 | ((rc == pcmk_rc_ok)? "succeeded" : "failed"), |
1150 | 0 | private->id, private->filename); |
1151 | | |
1152 | | /* Some request types (for example, erase) may have freed private->cib_xml |
1153 | | * (the working copy) and pointed it at a new XML object. In that case, it |
1154 | | * follows that *result_cib (the working copy) was freed. |
1155 | | * |
1156 | | * Point *result_cib at the updated working copy stored in private->cib_xml. |
1157 | | */ |
1158 | 0 | *result_cib = private->cib_xml; |
1159 | | |
1160 | | // Point private->cib_xml back to the unchanged original copy |
1161 | 0 | private->cib_xml = saved_cib; |
1162 | |
|
1163 | 0 | return rc; |
1164 | 0 | } |
1165 | | |
1166 | | static int |
1167 | | cib_file_process_commit_transaction(const char *op, int options, |
1168 | | const char *section, xmlNode *req, |
1169 | | xmlNode *input, xmlNode *existing_cib, |
1170 | | xmlNode **result_cib, xmlNode **answer) |
1171 | 0 | { |
1172 | 0 | int rc = pcmk_rc_ok; |
1173 | 0 | const char *client_id = pcmk__xe_get(req, PCMK__XA_CIB_CLIENTID); |
1174 | 0 | cib_t *cib = NULL; |
1175 | |
|
1176 | 0 | CRM_CHECK(client_id != NULL, return -EINVAL); |
1177 | | |
1178 | 0 | cib = get_client(client_id); |
1179 | 0 | CRM_CHECK(cib != NULL, return -EINVAL); |
1180 | | |
1181 | 0 | rc = cib_file_commit_transaction(cib, input, result_cib); |
1182 | 0 | if (rc != pcmk_rc_ok) { |
1183 | 0 | cib_file_opaque_t *private = cib->variant_opaque; |
1184 | |
|
1185 | | pcmk__err("Could not commit transaction for CIB file client (%s) on " |
1186 | 0 | "file '%s': %s", |
1187 | 0 | private->id, private->filename, pcmk_rc_str(rc)); |
1188 | 0 | } |
1189 | 0 | return pcmk_rc2legacy(rc); |
1190 | 0 | } |