root/daemons/execd/remoted_schemas.c

/* [previous][next][first][last][top][bottom][index][help] */

DEFINITIONS

This source file includes following definitions.
  1. rm_files
  2. clean_up_extra_schema_files
  3. write_extra_schema_file
  4. get_schema_files
  5. get_schema_files_complete
  6. remoted_request_cib_schema_files

   1 /*
   2  * Copyright 2023-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 Lesser General Public License
   7  * version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY.
   8  */
   9 
  10 #include <crm_internal.h>
  11 
  12 #include <ftw.h>
  13 #include <unistd.h>
  14 #include <sys/stat.h>
  15 
  16 #include <crm/cib.h>
  17 #include <crm/cib/cib_types.h>
  18 #include <crm/cib/internal.h>
  19 #include <crm/crm.h>
  20 #include <crm/common/mainloop.h>
  21 #include <crm/common/xml.h>
  22 
  23 #include "pacemaker-execd.h"
  24 
  25 static pid_t schema_fetch_pid = 0;
  26 
  27 static int
  28 rm_files(const char *pathname, const struct stat *sbuf, int type, struct FTW *ftwb)
     /* [previous][next][first][last][top][bottom][index][help] */
  29 {
  30     /* Don't delete PCMK__REMOTE_SCHEMA_DIR . */
  31     if (ftwb->level == 0) {
  32         return 0;
  33     }
  34 
  35     if (remove(pathname) != 0) {
  36         int rc = errno;
  37         crm_err("Could not remove %s: %s", pathname, pcmk_rc_str(rc));
  38         return -1;
  39     }
  40 
  41     return 0;
  42 }
  43 
  44 static void
  45 clean_up_extra_schema_files(void)
     /* [previous][next][first][last][top][bottom][index][help] */
  46 {
  47     const char *remote_schema_dir = pcmk__remote_schema_dir();
  48     struct stat sb;
  49     int rc;
  50 
  51     rc = stat(remote_schema_dir, &sb);
  52 
  53     if (rc == -1) {
  54         if (errno == ENOENT) {
  55             /* If the directory doesn't exist, try to make it first. */
  56             if (mkdir(remote_schema_dir, 0755) != 0) {
  57                 rc = errno;
  58                 crm_err("Could not create directory for schemas: %s",
  59                         pcmk_rc_str(rc));
  60             }
  61 
  62         } else {
  63             rc = errno;
  64             crm_err("Could not create directory for schemas: %s",
  65                     pcmk_rc_str(rc));
  66         }
  67 
  68     } else if (!S_ISDIR(sb.st_mode)) {
  69         /* If something exists with the same name that's not a directory, that's
  70          * an error.
  71          */
  72         crm_err("%s already exists but is not a directory", remote_schema_dir);
  73 
  74     } else {
  75         /* It's a directory - clear it out so we can download potentially new
  76          * schema files.
  77          */
  78         rc = nftw(remote_schema_dir, rm_files, 10, FTW_DEPTH|FTW_MOUNT|FTW_PHYS);
  79 
  80         if (rc != 0) {
  81             crm_err("Could not remove %s: %s", remote_schema_dir, pcmk_rc_str(rc));
  82         }
  83     }
  84 }
  85 
  86 static void
  87 write_extra_schema_file(xmlNode *xml, void *user_data)
     /* [previous][next][first][last][top][bottom][index][help] */
  88 {
  89     const char *remote_schema_dir = pcmk__remote_schema_dir();
  90     const char *file = NULL;
  91     char *path = NULL;
  92     int rc;
  93 
  94     file = crm_element_value(xml, PCMK_XA_PATH);
  95     if (file == NULL) {
  96         crm_warn("No destination path given in schema request");
  97         return;
  98     }
  99 
 100     path = crm_strdup_printf("%s/%s", remote_schema_dir, file);
 101 
 102     /* The schema is a CDATA node, which is a child of the <file> node.  Traverse
 103      * all children and look for the first CDATA child.  There can't be more than
 104      * one because we only have one file attribute on the parent.
 105      */
 106     for (xmlNode *child = xml->children; child != NULL; child = child->next) {
 107         FILE *stream = NULL;
 108 
 109         if (child->type != XML_CDATA_SECTION_NODE) {
 110             continue;
 111         }
 112 
 113         stream = fopen(path, "w+");
 114         if (stream == NULL) {
 115             crm_warn("Could not write schema file %s: %s", path, strerror(errno));
 116         } else {
 117             rc = fprintf(stream, "%s", child->content);
 118 
 119             if (rc < 0) {
 120                 crm_warn("Could not write schema file %s: %s", path, strerror(errno));
 121             }
 122 
 123             fclose(stream);
 124         }
 125 
 126         break;
 127     }
 128 
 129     free(path);
 130 }
 131 
 132 static void
 133 get_schema_files(void)
     /* [previous][next][first][last][top][bottom][index][help] */
 134 {
 135     int rc = pcmk_rc_ok;
 136     cib_t *cib = NULL;
 137     xmlNode *reply;
 138 
 139     cib = cib_new();
 140     if (cib == NULL) {
 141         _exit(ENOTCONN);
 142     }
 143 
 144     rc = cib->cmds->signon(cib, crm_system_name, cib_query);
 145     if (rc != pcmk_ok) {
 146         crm_err("Could not connect to the CIB manager: %s", pcmk_strerror(rc));
 147         _exit(pcmk_rc2exitc(rc));
 148     }
 149 
 150     rc = cib->cmds->fetch_schemas(cib, &reply, pcmk__highest_schema_name(),
 151                                   cib_sync_call);
 152     if (rc != pcmk_ok) {
 153         crm_err("Could not get schema files: %s", pcmk_strerror(rc));
 154         rc = pcmk_legacy2rc(rc);
 155 
 156     } else if (reply->children != NULL) {
 157         /* The returned document looks something like this:
 158          * <cib_command>
 159          *   <cib_calldata>
 160          *     <schemas>
 161          *       <schema version="pacemaker-1.1">
 162          *         <file path="foo-1.1">
 163          *         </file>
 164          *         <file path="bar-1.1">
 165          *         </file>
 166          *         ...
 167          *       </schema>
 168          *       <schema version="pacemaker-1.2">
 169          *       </schema>
 170          *       ...
 171          *     </schemas>
 172          *   </cib_calldata>
 173          * </cib_command>
 174          *
 175          * All the <schemas> and <schema> tags are really just there for organizing
 176          * the XML a little better.  What we really care about are the <file> nodes,
 177          * and specifically the path attributes and the CDATA children (not shown)
 178          * of each.  We can use an xpath query to reach down and get all the <file>
 179          * nodes at once.
 180          *
 181          * If we already have the latest schema version, or we asked for one later
 182          * than what the cluster supports, we'll get back an empty <schemas> node,
 183          * so all this will continue to work.  It just won't do anything.
 184          */
 185         crm_foreach_xpath_result(reply, "//" PCMK_XA_FILE,
 186                                  write_extra_schema_file, NULL);
 187     }
 188 
 189     free_xml(reply);
 190     cib__clean_up_connection(&cib);
 191     _exit(pcmk_rc2exitc(rc));
 192 }
 193 
 194 /* Load any additional schema files when the child is finished fetching and
 195  * saving them to disk.
 196  */
 197 static void
 198 get_schema_files_complete(mainloop_child_t *p, pid_t pid, int core, int signo, int exitcode)
     /* [previous][next][first][last][top][bottom][index][help] */
 199 {
 200     const char *errmsg = "Could not load additional schema files";
 201 
 202     if ((signo == 0) && (exitcode == 0)) {
 203         const char *remote_schema_dir = pcmk__remote_schema_dir();
 204 
 205         /* Don't just crm_schema_init here because that will load the base
 206          * schemas again too.  Instead just load the things we fetched.
 207          */
 208         pcmk__load_schemas_from_dir(remote_schema_dir);
 209         pcmk__sort_schemas();
 210         crm_info("Fetching extra schema files completed successfully");
 211 
 212     } else {
 213         if (signo == 0) {
 214             crm_err("%s: process %d exited %d", errmsg, (int) pid, exitcode);
 215 
 216         } else {
 217             crm_err("%s: process %d terminated with signal %d (%s)%s",
 218                     errmsg, (int) pid, signo, strsignal(signo),
 219                     (core? " and dumped core" : ""));
 220         }
 221 
 222         /* Clean up any incomplete schema data we might have been downloading when
 223          * the process timed out or crashed.  We don't need to do any extra cleanup
 224          * because we never loaded the extra schemas, and we don't need to call
 225          * crm_schema_init because that was called in remoted_request_cib_schema_files
 226          * before this function.
 227          */
 228         clean_up_extra_schema_files();
 229     }
 230 }
 231 
 232 void
 233 remoted_request_cib_schema_files(void)
     /* [previous][next][first][last][top][bottom][index][help] */
 234 {
 235     pid_t pid;
 236     int rc;
 237 
 238     /* If a previous schema-fetch process is still running when we're called
 239      * again, it's hung.  Attempt to kill it before cleaning up the extra
 240      * directory.
 241      */
 242     if (schema_fetch_pid != 0) {
 243         if (mainloop_child_kill(schema_fetch_pid) == FALSE) {
 244             crm_warn("Unable to kill pre-existing schema-fetch process");
 245             return;
 246         }
 247 
 248         schema_fetch_pid = 0;
 249     }
 250 
 251     /* Clean up any extra schema files we downloaded from a previous cluster
 252      * connection.  After the files are gone, we need to wipe them from
 253      * known_schemas, but there's no opposite operation for add_schema().
 254      *
 255      * Instead, unload all the schemas.  This means we'll also forget about all
 256      * installed schemas as well, which means that pcmk__highest_schema_name()
 257      * would fail. So we need to load the base schemas right now.
 258      */
 259     clean_up_extra_schema_files();
 260     crm_schema_cleanup();
 261     crm_schema_init();
 262 
 263     crm_info("Fetching extra schema files from cluster");
 264     pid = fork();
 265 
 266     switch (pid) {
 267         case -1: {
 268             rc = errno;
 269             crm_warn("Could not spawn process to get schema files: %s", pcmk_rc_str(rc));
 270             break;
 271         }
 272 
 273         case 0:
 274             /* child */
 275             get_schema_files();
 276             break;
 277 
 278         default:
 279             /* parent */
 280             schema_fetch_pid = pid;
 281             mainloop_child_add_with_flags(pid, 5 * 60 * 1000, "schema-fetch", NULL,
 282                                           mainloop_leave_pid_group,
 283                                           get_schema_files_complete);
 284             break;
 285     }
 286 }

/* [previous][next][first][last][top][bottom][index][help] */