1 /*
2 * Copyright 2004-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 <stdio.h>
13 #include <unistd.h>
14 #include <string.h>
15 #include <stdlib.h>
16 #include <errno.h>
17 #include <fcntl.h>
18 #include <dirent.h>
19
20 #include <sys/param.h>
21 #include <sys/types.h>
22 #include <sys/wait.h>
23 #include <sys/stat.h>
24
25 #include <glib.h>
26 #include <libxml/tree.h>
27
28 #include <crm/crm.h>
29
30 #include <crm/cib.h>
31 #include <crm/common/util.h>
32 #include <crm/common/xml.h>
33 #include <crm/cib/internal.h>
34 #include <crm/cluster.h>
35
36 #include <pacemaker-based.h>
37
38 crm_trigger_t *cib_writer = NULL;
39
40 int write_cib_contents(gpointer p);
41
42 static void
43 cib_rename(const char *old)
44 {
45 int new_fd;
46 char *new = crm_strdup_printf("%s/cib.auto.XXXXXX", cib_root);
47
48 umask(S_IWGRP | S_IWOTH | S_IROTH);
|
(1) Event open_fn: |
Returning handle opened by "mkstemp". |
|
(2) Event var_assign: |
Assigning: "new_fd" = handle returned from "mkstemp(new)". |
| Also see events: |
[off_by_one][remediation][leaked_handle] |
49 new_fd = mkstemp(new);
50
|
(3) Event path: |
Condition "new_fd < 0", taking false branch. |
|
(4) Event path: |
Condition "rename(old, new) < 0", taking false branch. |
51 if ((new_fd < 0) || (rename(old, new) < 0)) {
52 crm_err("Couldn't archive unusable file %s (disabling disk writes and continuing)",
53 old);
54 cib_writes_enabled = FALSE;
55 } else {
56 crm_err("Archived unusable file %s as %s", old, new);
57 }
58
|
(5) Event path: |
Condition "new_fd > 0", taking false branch. |
|
(6) Event off_by_one: |
Testing whether handle "new_fd" is strictly greater than zero is suspicious. "new_fd" leaks when it is zero. |
|
(7) Event remediation: |
Did you intend to include equality with zero? |
| Also see events: |
[open_fn][var_assign][leaked_handle] |
59 if (new_fd > 0) {
60 close(new_fd);
61 }
62 free(new);
|
CID (unavailable; MK=0759a52ab30e0689bf71ca81a527dcd5) (#1 of 1): Resource leak (RESOURCE_LEAK): |
63 }
64
65 /*
66 * It is the callers responsibility to free the output of this function
67 */
68
69 static xmlNode *
70 retrieveCib(const char *filename, const char *sigfile)
71 {
72 xmlNode *root = NULL;
73
74 crm_info("Reading cluster configuration file %s (digest: %s)",
75 filename, sigfile);
76 switch (cib_file_read_and_verify(filename, sigfile, &root)) {
77 case -pcmk_err_cib_corrupt:
78 crm_warn("Continuing but %s will NOT be used.", filename);
79 break;
80
81 case -pcmk_err_cib_modified:
82 /* Archive the original files so the contents are not lost */
83 crm_warn("Continuing but %s will NOT be used.", filename);
84 cib_rename(filename);
85 cib_rename(sigfile);
86 break;
87 }
88 return root;
89 }
90
91 /*
92 * for OSs without support for direntry->d_type, like Solaris
93 */
94 #ifndef DT_UNKNOWN
95 # define DT_UNKNOWN 0
96 # define DT_FIFO 1
97 # define DT_CHR 2
98 # define DT_DIR 4
99 # define DT_BLK 6
100 # define DT_REG 8
101 # define DT_LNK 10
102 # define DT_SOCK 12
103 # define DT_WHT 14
104 #endif /*DT_UNKNOWN*/
105
106 static int cib_archive_filter(const struct dirent * a)
107 {
108 int rc = 0;
109 /* Looking for regular files (d_type = 8) starting with 'cib-' and not ending in .sig */
110 struct stat s;
111 char *a_path = crm_strdup_printf("%s/%s", cib_root, a->d_name);
112
113 if(stat(a_path, &s) != 0) {
114 rc = errno;
115 crm_trace("%s - stat failed: %s (%d)", a->d_name, pcmk_rc_str(rc), rc);
116 rc = 0;
117
118 } else if ((s.st_mode & S_IFREG) != S_IFREG) {
119 unsigned char dtype;
120 #ifdef HAVE_STRUCT_DIRENT_D_TYPE
121 dtype = a->d_type;
122 #else
123 switch (s.st_mode & S_IFMT) {
124 case S_IFREG: dtype = DT_REG; break;
125 case S_IFDIR: dtype = DT_DIR; break;
126 case S_IFCHR: dtype = DT_CHR; break;
127 case S_IFBLK: dtype = DT_BLK; break;
128 case S_IFLNK: dtype = DT_LNK; break;
129 case S_IFIFO: dtype = DT_FIFO; break;
130 case S_IFSOCK: dtype = DT_SOCK; break;
131 default: dtype = DT_UNKNOWN; break;
132 }
133 #endif
134 crm_trace("%s - wrong type (%d)", a->d_name, dtype);
135
136 } else if(strstr(a->d_name, "cib-") != a->d_name) {
137 crm_trace("%s - wrong prefix", a->d_name);
138
139 } else if (pcmk__ends_with_ext(a->d_name, ".sig")) {
140 crm_trace("%s - wrong suffix", a->d_name);
141
142 } else {
143 crm_debug("%s - candidate", a->d_name);
144 rc = 1;
145 }
146
147 free(a_path);
148 return rc;
149 }
150
151 static int cib_archive_sort(const struct dirent ** a, const struct dirent **b)
152 {
153 /* Order by creation date - most recently created file first */
154 int rc = 0;
155 struct stat buf;
156
157 time_t a_age = 0;
158 time_t b_age = 0;
159
160 char *a_path = crm_strdup_printf("%s/%s", cib_root, a[0]->d_name);
161 char *b_path = crm_strdup_printf("%s/%s", cib_root, b[0]->d_name);
162
163 if(stat(a_path, &buf) == 0) {
164 a_age = buf.st_ctime;
165 }
166 if(stat(b_path, &buf) == 0) {
167 b_age = buf.st_ctime;
168 }
169
170 free(a_path);
171 free(b_path);
172
173 if(a_age > b_age) {
174 rc = 1;
175 } else if(a_age < b_age) {
176 rc = -1;
177 }
178
179 crm_trace("%s (%lu) vs. %s (%lu) : %d",
180 a[0]->d_name, (unsigned long)a_age,
181 b[0]->d_name, (unsigned long)b_age, rc);
182 return rc;
183 }
184
185 xmlNode *
186 readCibXmlFile(const char *dir, const char *file, gboolean discard_status)
187 {
188 struct dirent **namelist = NULL;
189
190 int lpc = 0;
191 char *sigfile = NULL;
192 char *sigfilepath = NULL;
193 char *filename = NULL;
194 const char *name = NULL;
195 const char *value = NULL;
196 const char *validation = NULL;
197 const char *use_valgrind = pcmk__env_option(PCMK__ENV_VALGRIND_ENABLED);
198
199 xmlNode *root = NULL;
200 xmlNode *status = NULL;
201
202 sigfile = crm_strdup_printf("%s.sig", file);
203 if (pcmk__daemon_can_write(dir, file) == FALSE
204 || pcmk__daemon_can_write(dir, sigfile) == FALSE) {
205 cib_status = -EACCES;
206 return NULL;
207 }
208
209 filename = crm_strdup_printf("%s/%s", dir, file);
210 sigfilepath = crm_strdup_printf("%s/%s", dir, sigfile);
211 free(sigfile);
212
213 cib_status = pcmk_ok;
214 root = retrieveCib(filename, sigfilepath);
215 free(filename);
216 free(sigfilepath);
217
218 if (root == NULL) {
219 crm_warn("Primary configuration corrupt or unusable, trying backups in %s", cib_root);
220 lpc = scandir(cib_root, &namelist, cib_archive_filter, cib_archive_sort);
221 if (lpc < 0) {
222 crm_err("scandir(%s) failed: %s", cib_root, pcmk_rc_str(errno));
223 }
224 }
225
226 while (root == NULL && lpc > 1) {
227 crm_debug("Testing %d candidates", lpc);
228
229 lpc--;
230
231 filename = crm_strdup_printf("%s/%s", cib_root, namelist[lpc]->d_name);
232 sigfile = crm_strdup_printf("%s.sig", filename);
233
234 crm_info("Reading cluster configuration file %s (digest: %s)",
235 filename, sigfile);
236 if (cib_file_read_and_verify(filename, sigfile, &root) < 0) {
237 crm_warn("Continuing but %s will NOT be used.", filename);
238 } else {
239 crm_notice("Continuing with last valid configuration archive: %s", filename);
240 }
241
242 free(namelist[lpc]);
243 free(filename);
244 free(sigfile);
245 }
246 free(namelist);
247
248 if (root == NULL) {
249 root = createEmptyCib(0);
250 crm_warn("Continuing with an empty configuration.");
251 }
252
253 if (cib_writes_enabled && use_valgrind &&
254 (crm_is_true(use_valgrind) || strstr(use_valgrind, "pacemaker-based"))) {
255
256 cib_writes_enabled = FALSE;
257 crm_err("*** Disabling disk writes to avoid confusing Valgrind ***");
258 }
259
260 status = pcmk__xe_first_child(root, PCMK_XE_STATUS, NULL, NULL);
261 if (discard_status && status != NULL) {
262 // Strip out the PCMK_XE_STATUS section if there is one
263 free_xml(status);
264 status = NULL;
265 }
266 if (status == NULL) {
267 pcmk__xe_create(root, PCMK_XE_STATUS);
268 }
269
270 /* Do this before schema validation happens */
271
272 /* fill in some defaults */
273 name = PCMK_XA_ADMIN_EPOCH;
274 value = crm_element_value(root, name);
275 if (value == NULL) {
276 crm_warn("No value for %s was specified in the configuration.", name);
277 crm_warn("The recommended course of action is to shutdown,"
278 " run crm_verify and fix any errors it reports.");
279 crm_warn("We will default to zero and continue but may get"
280 " confused about which configuration to use if"
281 " multiple nodes are powered up at the same time.");
282 crm_xml_add_int(root, name, 0);
283 }
284
285 name = PCMK_XA_EPOCH;
286 value = crm_element_value(root, name);
287 if (value == NULL) {
288 crm_xml_add_int(root, name, 0);
289 }
290
291 name = PCMK_XA_NUM_UPDATES;
292 value = crm_element_value(root, name);
293 if (value == NULL) {
294 crm_xml_add_int(root, name, 0);
295 }
296
297 // Unset (DC should set appropriate value)
298 pcmk__xe_remove_attr(root, PCMK_XA_DC_UUID);
299
300 if (discard_status) {
301 crm_log_xml_trace(root, "[on-disk]");
302 }
303
304 validation = crm_element_value(root, PCMK_XA_VALIDATE_WITH);
305 if (!pcmk__configured_schema_validates(root)) {
306 crm_err("CIB does not validate with %s",
307 pcmk__s(validation, "no schema specified"));
308 cib_status = -pcmk_err_schema_validation;
309
310 // @COMPAT Not specifying validate-with is deprecated since 2.1.8
311 } else if (validation == NULL) {
312 pcmk__update_schema(&root, NULL, false, false);
313 validation = crm_element_value(root, PCMK_XA_VALIDATE_WITH);
314 if (validation != NULL) {
315 crm_notice("Enabling %s validation on"
316 " the existing (sane) configuration", validation);
317 } else {
318 crm_err("CIB does not validate with any known schema");
319 cib_status = -pcmk_err_schema_validation;
320 }
321 }
322
323 return root;
324 }
325
326 gboolean
327 uninitializeCib(void)
328 {
329 xmlNode *tmp_cib = the_cib;
330
331 if (tmp_cib == NULL) {
332 crm_debug("The CIB has already been deallocated.");
333 return FALSE;
334 }
335
336 the_cib = NULL;
337
338 crm_debug("Deallocating the CIB.");
339
340 free_xml(tmp_cib);
341
342 crm_debug("The CIB has been deallocated.");
343
344 return TRUE;
345 }
346
347 /*
348 * This method will free the old CIB pointer on success and the new one
349 * on failure.
350 */
351 int
352 activateCibXml(xmlNode * new_cib, gboolean to_disk, const char *op)
353 {
354 if (new_cib) {
355 xmlNode *saved_cib = the_cib;
356
357 pcmk__assert(new_cib != saved_cib);
358 the_cib = new_cib;
359 free_xml(saved_cib);
360 if (cib_writes_enabled && cib_status == pcmk_ok && to_disk) {
361 crm_debug("Triggering CIB write for %s op", op);
362 mainloop_set_trigger(cib_writer);
363 }
364 return pcmk_ok;
365 }
366
367 crm_err("Ignoring invalid CIB");
368 if (the_cib) {
369 crm_warn("Reverting to last known CIB");
370 } else {
371 crm_crit("Could not write out new CIB and no saved version to revert to");
372 }
373 return -ENODATA;
374 }
375
376 static void
377 cib_diskwrite_complete(mainloop_child_t * p, pid_t pid, int core, int signo, int exitcode)
378 {
379 const char *errmsg = "Could not write CIB to disk";
380
381 if ((exitcode != 0) && cib_writes_enabled) {
382 cib_writes_enabled = FALSE;
383 errmsg = "Disabling CIB disk writes after failure";
384 }
385
386 if ((signo == 0) && (exitcode == 0)) {
387 crm_trace("Disk write [%d] succeeded", (int) pid);
388
389 } else if (signo == 0) {
390 crm_err("%s: process %d exited %d", errmsg, (int) pid, exitcode);
391
392 } else {
393 crm_err("%s: process %d terminated with signal %d (%s)%s",
394 errmsg, (int) pid, signo, strsignal(signo),
395 (core? " and dumped core" : ""));
396 }
397
398 mainloop_trigger_complete(cib_writer);
399 }
400
401 int
402 write_cib_contents(gpointer p)
403 {
404 int exit_rc = pcmk_ok;
405 xmlNode *cib_local = NULL;
406
407 /* Make a copy of the CIB to write (possibly in a forked child) */
408 if (p) {
409 /* Synchronous write out */
410 cib_local = pcmk__xml_copy(NULL, p);
411
412 } else {
413 int pid = 0;
414 int bb_state = qb_log_ctl(QB_LOG_BLACKBOX, QB_LOG_CONF_STATE_GET, 0);
415
416 /* Turn it off before the fork() to avoid:
417 * - 2 processes writing to the same shared mem
418 * - the child needing to disable it
419 * (which would close it from underneath the parent)
420 * This way, the shared mem files are already closed
421 */
422 qb_log_ctl(QB_LOG_BLACKBOX, QB_LOG_CONF_ENABLED, QB_FALSE);
423
424 pid = fork();
425 if (pid < 0) {
426 crm_err("Disabling disk writes after fork failure: %s", pcmk_rc_str(errno));
427 cib_writes_enabled = FALSE;
428 return FALSE;
429 }
430
431 if (pid) {
432 /* Parent */
433 mainloop_child_add(pid, 0, "disk-writer", NULL, cib_diskwrite_complete);
434 if (bb_state == QB_LOG_STATE_ENABLED) {
435 /* Re-enable now that it it safe */
436 qb_log_ctl(QB_LOG_BLACKBOX, QB_LOG_CONF_ENABLED, QB_TRUE);
437 }
438
439 return -1; /* -1 means 'still work to do' */
440 }
441
442 /* Asynchronous write-out after a fork() */
443
444 /* In theory, we can scribble on the_cib here and not affect the parent,
445 * but let's be safe anyway.
446 */
447 cib_local = pcmk__xml_copy(NULL, the_cib);
448 }
449
450 /* Write the CIB */
451 exit_rc = cib_file_write_with_digest(cib_local, cib_root, "cib.xml");
452
453 /* A nonzero exit code will cause further writes to be disabled */
454 free_xml(cib_local);
455 if (p == NULL) {
456 crm_exit_t exit_code = CRM_EX_OK;
457
458 switch (exit_rc) {
459 case pcmk_ok:
460 exit_code = CRM_EX_OK;
461 break;
462 case pcmk_err_cib_modified:
463 exit_code = CRM_EX_DIGEST; // Existing CIB doesn't match digest
464 break;
465 case pcmk_err_cib_backup: // Existing CIB couldn't be backed up
466 case pcmk_err_cib_save: // New CIB couldn't be saved
467 exit_code = CRM_EX_CANTCREAT;
468 break;
469 default:
470 exit_code = CRM_EX_ERROR;
471 break;
472 }
473
474 /* Use _exit() because exit() could affect the parent adversely */
475 pcmk_common_cleanup();
476 _exit(exit_code);
477 }
478 return exit_rc;
479 }
480