diff options
Diffstat (limited to 'daemons/execd/remoted_schemas.c')
-rw-r--r-- | daemons/execd/remoted_schemas.c | 286 |
1 files changed, 286 insertions, 0 deletions
diff --git a/daemons/execd/remoted_schemas.c b/daemons/execd/remoted_schemas.c new file mode 100644 index 0000000..f0ec068 --- /dev/null +++ b/daemons/execd/remoted_schemas.c @@ -0,0 +1,286 @@ +/* + * Copyright 2023-2024 the Pacemaker project contributors + * + * The version control history for this file may have further details. + * + * This source code is licensed under the GNU Lesser General Public License + * version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY. + */ + +#include <crm_internal.h> + +#include <ftw.h> +#include <unistd.h> +#include <sys/stat.h> + +#include <crm/cib.h> +#include <crm/cib/cib_types.h> +#include <crm/cib/internal.h> +#include <crm/crm.h> +#include <crm/common/mainloop.h> +#include <crm/common/xml.h> + +#include "pacemaker-execd.h" + +static pid_t schema_fetch_pid = 0; + +static int +rm_files(const char *pathname, const struct stat *sbuf, int type, struct FTW *ftwb) +{ + /* Don't delete PCMK__REMOTE_SCHEMA_DIR . */ + if (ftwb->level == 0) { + return 0; + } + + if (remove(pathname) != 0) { + int rc = errno; + crm_err("Could not remove %s: %s", pathname, pcmk_rc_str(rc)); + return -1; + } + + return 0; +} + +static void +clean_up_extra_schema_files(void) +{ + const char *remote_schema_dir = pcmk__remote_schema_dir(); + struct stat sb; + int rc; + + rc = stat(remote_schema_dir, &sb); + + if (rc == -1) { + if (errno == ENOENT) { + /* If the directory doesn't exist, try to make it first. */ + if (mkdir(remote_schema_dir, 0755) != 0) { + rc = errno; + crm_err("Could not create directory for schemas: %s", + pcmk_rc_str(rc)); + } + + } else { + rc = errno; + crm_err("Could not create directory for schemas: %s", + pcmk_rc_str(rc)); + } + + } else if (!S_ISDIR(sb.st_mode)) { + /* If something exists with the same name that's not a directory, that's + * an error. + */ + crm_err("%s already exists but is not a directory", remote_schema_dir); + + } else { + /* It's a directory - clear it out so we can download potentially new + * schema files. + */ + rc = nftw(remote_schema_dir, rm_files, 10, FTW_DEPTH|FTW_MOUNT|FTW_PHYS); + + if (rc != 0) { + crm_err("Could not remove %s: %s", remote_schema_dir, pcmk_rc_str(rc)); + } + } +} + +static void +write_extra_schema_file(xmlNode *xml, void *user_data) +{ + const char *remote_schema_dir = pcmk__remote_schema_dir(); + const char *file = NULL; + char *path = NULL; + int rc; + + file = crm_element_value(xml, PCMK_XA_PATH); + if (file == NULL) { + crm_warn("No destination path given in schema request"); + return; + } + + path = crm_strdup_printf("%s/%s", remote_schema_dir, file); + + /* The schema is a CDATA node, which is a child of the <file> node. Traverse + * all children and look for the first CDATA child. There can't be more than + * one because we only have one file attribute on the parent. + */ + for (xmlNode *child = xml->children; child != NULL; child = child->next) { + FILE *stream = NULL; + + if (child->type != XML_CDATA_SECTION_NODE) { + continue; + } + + stream = fopen(path, "w+"); + if (stream == NULL) { + crm_warn("Could not write schema file %s: %s", path, strerror(errno)); + } else { + rc = fprintf(stream, "%s", child->content); + + if (rc < 0) { + crm_warn("Could not write schema file %s: %s", path, strerror(errno)); + } + + fclose(stream); + } + + break; + } + + free(path); +} + +static void +get_schema_files(void) +{ + int rc = pcmk_rc_ok; + cib_t *cib = NULL; + xmlNode *reply; + + cib = cib_new(); + if (cib == NULL) { + _exit(ENOTCONN); + } + + rc = cib->cmds->signon(cib, crm_system_name, cib_query); + if (rc != pcmk_ok) { + crm_err("Could not connect to the CIB manager: %s", pcmk_strerror(rc)); + _exit(pcmk_rc2exitc(rc)); + } + + rc = cib->cmds->fetch_schemas(cib, &reply, pcmk__highest_schema_name(), + cib_sync_call); + if (rc != pcmk_ok) { + crm_err("Could not get schema files: %s", pcmk_strerror(rc)); + rc = pcmk_legacy2rc(rc); + + } else if (reply->children != NULL) { + /* The returned document looks something like this: + * <cib_command> + * <cib_calldata> + * <schemas> + * <schema version="pacemaker-1.1"> + * <file path="foo-1.1"> + * </file> + * <file path="bar-1.1"> + * </file> + * ... + * </schema> + * <schema version="pacemaker-1.2"> + * </schema> + * ... + * </schemas> + * </cib_calldata> + * </cib_command> + * + * All the <schemas> and <schema> tags are really just there for organizing + * the XML a little better. What we really care about are the <file> nodes, + * and specifically the path attributes and the CDATA children (not shown) + * of each. We can use an xpath query to reach down and get all the <file> + * nodes at once. + * + * If we already have the latest schema version, or we asked for one later + * than what the cluster supports, we'll get back an empty <schemas> node, + * so all this will continue to work. It just won't do anything. + */ + crm_foreach_xpath_result(reply, "//" PCMK_XA_FILE, + write_extra_schema_file, NULL); + } + + free_xml(reply); + cib__clean_up_connection(&cib); + _exit(pcmk_rc2exitc(rc)); +} + +/* Load any additional schema files when the child is finished fetching and + * saving them to disk. + */ +static void +get_schema_files_complete(mainloop_child_t *p, pid_t pid, int core, int signo, int exitcode) +{ + const char *errmsg = "Could not load additional schema files"; + + if ((signo == 0) && (exitcode == 0)) { + const char *remote_schema_dir = pcmk__remote_schema_dir(); + + /* Don't just crm_schema_init here because that will load the base + * schemas again too. Instead just load the things we fetched. + */ + pcmk__load_schemas_from_dir(remote_schema_dir); + pcmk__sort_schemas(); + crm_info("Fetching extra schema files completed successfully"); + + } else { + if (signo == 0) { + crm_err("%s: process %d exited %d", errmsg, (int) pid, exitcode); + + } else { + crm_err("%s: process %d terminated with signal %d (%s)%s", + errmsg, (int) pid, signo, strsignal(signo), + (core? " and dumped core" : "")); + } + + /* Clean up any incomplete schema data we might have been downloading when + * the process timed out or crashed. We don't need to do any extra cleanup + * because we never loaded the extra schemas, and we don't need to call + * crm_schema_init because that was called in remoted_request_cib_schema_files + * before this function. + */ + clean_up_extra_schema_files(); + } +} + +void +remoted_request_cib_schema_files(void) +{ + pid_t pid; + int rc; + + /* If a previous schema-fetch process is still running when we're called + * again, it's hung. Attempt to kill it before cleaning up the extra + * directory. + */ + if (schema_fetch_pid != 0) { + if (mainloop_child_kill(schema_fetch_pid) == FALSE) { + crm_warn("Unable to kill pre-existing schema-fetch process"); + return; + } + + schema_fetch_pid = 0; + } + + /* Clean up any extra schema files we downloaded from a previous cluster + * connection. After the files are gone, we need to wipe them from + * known_schemas, but there's no opposite operation for add_schema(). + * + * Instead, unload all the schemas. This means we'll also forget about all + * installed schemas as well, which means that pcmk__highest_schema_name() + * would fail. So we need to load the base schemas right now. + */ + clean_up_extra_schema_files(); + crm_schema_cleanup(); + crm_schema_init(); + + crm_info("Fetching extra schema files from cluster"); + pid = fork(); + + switch (pid) { + case -1: { + rc = errno; + crm_warn("Could not spawn process to get schema files: %s", pcmk_rc_str(rc)); + break; + } + + case 0: + /* child */ + get_schema_files(); + break; + + default: + /* parent */ + schema_fetch_pid = pid; + mainloop_child_add_with_flags(pid, 5 * 60 * 1000, "schema-fetch", NULL, + mainloop_leave_pid_group, + get_schema_files_complete); + break; + } +} |