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):
(8) Event leaked_handle: Handle variable "new_fd" going out of scope leaks the handle.
Also see events: [open_fn][var_assign][off_by_one][remediation]
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