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