diff --git a/lib/cib/cib_file.c b/lib/cib/cib_file.c index 6ef93d470e..7d05965620 100644 --- a/lib/cib/cib_file.c +++ b/lib/cib/cib_file.c @@ -1,927 +1,919 @@ /* * Original copyright 2004 International Business Machines * Later changes copyright 2008-2023 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include +#define CIB_SERIES "cib" +#define CIB_SERIES_MAX 100 +#define CIB_SERIES_BZIP FALSE /* Must be false because archived copies are + created with hard links + */ + +#define CIB_LIVE_NAME CIB_SERIES ".xml" + enum cib_file_flags { cib_file_flag_dirty = (1 << 0), cib_file_flag_live = (1 << 1), }; typedef struct cib_file_opaque_s { uint32_t flags; // Group of enum cib_file_flags char *filename; } cib_file_opaque_t; +struct cib_func_entry { + const char *op; + gboolean read_only; + cib_op_t fn; +}; + +static struct cib_func_entry cib_file_ops[] = { + { PCMK__CIB_REQUEST_QUERY, TRUE, cib_process_query }, + { PCMK__CIB_REQUEST_MODIFY, FALSE, cib_process_modify }, + { PCMK__CIB_REQUEST_APPLY_PATCH, FALSE, cib_process_diff }, + { PCMK__CIB_REQUEST_BUMP, FALSE, cib_process_bump }, + { PCMK__CIB_REQUEST_REPLACE, FALSE, cib_process_replace }, + { PCMK__CIB_REQUEST_CREATE, FALSE, cib_process_create }, + { PCMK__CIB_REQUEST_DELETE, FALSE, cib_process_delete }, + { PCMK__CIB_REQUEST_ERASE, FALSE, cib_process_erase }, + { PCMK__CIB_REQUEST_UPGRADE, FALSE, cib_process_upgrade }, +}; + +static xmlNode *in_mem_cib = NULL; + +/* cib_file_backup() and cib_file_write_with_digest() need to chown the + * written files only in limited circumstances, so these variables allow + * that to be indicated without affecting external callers + */ +static uid_t cib_file_owner = 0; +static uid_t cib_file_group = 0; +static gboolean cib_do_chown = FALSE; + #define cib_set_file_flags(cibfile, flags_to_set) do { \ (cibfile)->flags = pcmk__set_flags_as(__func__, __LINE__, \ LOG_TRACE, "CIB file", \ cibfile->filename, \ (cibfile)->flags, \ (flags_to_set), \ #flags_to_set); \ } while (0) #define cib_clear_file_flags(cibfile, flags_to_clear) do { \ (cibfile)->flags = pcmk__clear_flags_as(__func__, __LINE__, \ LOG_TRACE, "CIB file", \ cibfile->filename, \ (cibfile)->flags, \ (flags_to_clear), \ #flags_to_clear); \ } while (0) -int cib_file_perform_op_delegate(cib_t * cib, const char *op, const char *host, const char *section, - xmlNode * data, xmlNode ** output_data, int call_options, - const char *user_name); - -int cib_file_signon(cib_t * cib, const char *name, enum cib_conn_type type); -int cib_file_signoff(cib_t * cib); -int cib_file_free(cib_t * cib); - -static int -cib_file_inputfd(cib_t * cib) -{ - return -EPROTONOSUPPORT; -} - -static int -cib_file_set_connection_dnotify(cib_t * cib, void (*dnotify) (gpointer user_data)) -{ - return -EPROTONOSUPPORT; -} - -static int -cib_file_register_notification(cib_t * cib, const char *callback, int enabled) -{ - return -EPROTONOSUPPORT; -} - -/*! - * \internal - * \brief Get the given CIB connection's unique client identifier - * - * \param[in] cib CIB connection - * \param[out] async_id If not \p NULL, where to store asynchronous client ID - * \param[out] sync_id If not \p NULL, where to store synchronous client ID - * - * \return Legacy Pacemaker return code (specifically, \p -EPROTONOSUPPORT) - * - * \note This is the \p cib_file variant implementation of - * \p cib_api_operations_t:client_id(). - * \note A \p cib_file object doesn't connect to the CIB and is never assigned a - * client ID. - */ -static int -cib_file_client_id(const cib_t *cib, const char **async_id, - const char **sync_id) -{ - if (async_id != NULL) { - *async_id = NULL; - } - if (sync_id != NULL) { - *sync_id = NULL; - } - return -EPROTONOSUPPORT; -} - -/*! - * \internal - * \brief Compare the calculated digest of an XML tree against a signature file - * - * \param[in] root Root of XML tree to compare - * \param[in] sigfile Name of signature file containing digest to compare - * - * \return TRUE if digests match or signature file does not exist, else FALSE - */ -static gboolean -cib_file_verify_digest(xmlNode *root, const char *sigfile) -{ - gboolean passed = FALSE; - char *expected; - int rc = pcmk__file_contents(sigfile, &expected); - - switch (rc) { - case pcmk_rc_ok: - if (expected == NULL) { - crm_err("On-disk digest at %s is empty", sigfile); - return FALSE; - } - break; - case ENOENT: - crm_warn("No on-disk digest present at %s", sigfile); - return TRUE; - default: - crm_err("Could not read on-disk digest from %s: %s", - sigfile, pcmk_rc_str(rc)); - return FALSE; - } - passed = pcmk__verify_digest(root, expected); - free(expected); - return passed; -} - -/*! - * \internal - * \brief Read an XML tree from a file and verify its digest - * - * \param[in] filename Name of XML file to read - * \param[in] sigfile Name of signature file containing digest to compare - * \param[out] root If non-NULL, will be set to pointer to parsed XML tree - * - * \return 0 if file was successfully read, parsed and verified, otherwise: - * -errno on stat() failure, - * -pcmk_err_cib_corrupt if file size is 0 or XML is not parseable, or - * -pcmk_err_cib_modified if digests do not match - * \note If root is non-NULL, it is the caller's responsibility to free *root on - * successful return. - */ -int -cib_file_read_and_verify(const char *filename, const char *sigfile, xmlNode **root) -{ - int s_res; - struct stat buf; - char *local_sigfile = NULL; - xmlNode *local_root = NULL; - - CRM_ASSERT(filename != NULL); - if (root) { - *root = NULL; - } - - /* Verify that file exists and its size is nonzero */ - s_res = stat(filename, &buf); - if (s_res < 0) { - crm_perror(LOG_WARNING, "Could not verify cluster configuration file %s", filename); - return -errno; - } else if (buf.st_size == 0) { - crm_warn("Cluster configuration file %s is corrupt (size is zero)", filename); - return -pcmk_err_cib_corrupt; - } - - /* Parse XML */ - local_root = filename2xml(filename); - if (local_root == NULL) { - crm_warn("Cluster configuration file %s is corrupt (unparseable as XML)", filename); - return -pcmk_err_cib_corrupt; - } - - /* If sigfile is not specified, use original file name plus .sig */ - if (sigfile == NULL) { - sigfile = local_sigfile = crm_strdup_printf("%s.sig", filename); - } - - /* Verify that digests match */ - if (cib_file_verify_digest(local_root, sigfile) == FALSE) { - free(local_sigfile); - free_xml(local_root); - return -pcmk_err_cib_modified; - } - - free(local_sigfile); - if (root) { - *root = local_root; - } else { - free_xml(local_root); - } - return pcmk_ok; -} - -#define CIB_SERIES "cib" -#define CIB_SERIES_MAX 100 -#define CIB_SERIES_BZIP FALSE /* Must be false because archived copies are - created with hard links - */ - -#define CIB_LIVE_NAME CIB_SERIES ".xml" - /*! * \internal * \brief Check whether a file is the live CIB * * \param[in] filename Name of file to check * * \return TRUE if file exists and its real path is same as live CIB's */ static gboolean cib_file_is_live(const char *filename) { gboolean same = FALSE; if (filename != NULL) { // Canonicalize file names for true comparison char *real_filename = NULL; if (pcmk__real_path(filename, &real_filename) == pcmk_rc_ok) { char *real_livename = NULL; if (pcmk__real_path(CRM_CONFIG_DIR "/" CIB_LIVE_NAME, &real_livename) == pcmk_rc_ok) { same = !strcmp(real_filename, real_livename); free(real_livename); } free(real_filename); } } return same; } -/* cib_file_backup() and cib_file_write_with_digest() need to chown the - * written files only in limited circumstances, so these variables allow - * that to be indicated without affecting external callers - */ -static uid_t cib_file_owner = 0; -static uid_t cib_file_group = 0; -static gboolean cib_do_chown = FALSE; - -/*! - * \internal - * \brief Back up a CIB - * - * \param[in] cib_dirname Directory containing CIB file and backups - * \param[in] cib_filename Name (relative to cib_dirname) of CIB file to back up - * - * \return 0 on success, -1 on error - */ static int -cib_file_backup(const char *cib_dirname, const char *cib_filename) +cib_file_perform_op_delegate(cib_t *cib, const char *op, const char *host, + const char *section, xmlNode *data, + xmlNode **output_data, int call_options, + const char *user_name) { - int rc = 0; - unsigned int seq; - char *cib_path = crm_strdup_printf("%s/%s", cib_dirname, cib_filename); - char *cib_digest = crm_strdup_printf("%s.sig", cib_path); - char *backup_path; - char *backup_digest; + int rc = pcmk_ok; + char *effective_user = NULL; + gboolean query = FALSE; + gboolean changed = FALSE; + xmlNode *request = NULL; + xmlNode *output = NULL; + xmlNode *cib_diff = NULL; + xmlNode *result_cib = NULL; + cib_op_t *fn = NULL; + int lpc = 0; + static int max_msg_types = PCMK__NELEM(cib_file_ops); + cib_file_opaque_t *private = cib->variant_opaque; - // Determine backup and digest file names - if (pcmk__read_series_sequence(cib_dirname, CIB_SERIES, - &seq) != pcmk_rc_ok) { - // @TODO maybe handle errors better ... - seq = 0; - } - backup_path = pcmk__series_filename(cib_dirname, CIB_SERIES, seq, - CIB_SERIES_BZIP); - backup_digest = crm_strdup_printf("%s.sig", backup_path); + crm_info("Handling %s operation for %s as %s", + (op? op : "invalid"), (section? section : "entire CIB"), + (user_name? user_name : "default user")); - /* Remove the old backups if they exist */ - unlink(backup_path); - unlink(backup_digest); + cib__set_call_options(call_options, "file operation", + cib_no_mtime|cib_inhibit_bcast|cib_scope_local); - /* Back up the CIB, by hard-linking it to the backup name */ - if ((link(cib_path, backup_path) < 0) && (errno != ENOENT)) { - crm_perror(LOG_ERR, "Could not archive %s by linking to %s", - cib_path, backup_path); - rc = -1; + if (cib->state == cib_disconnected) { + return -ENOTCONN; + } - /* Back up the CIB signature similarly */ - } else if ((link(cib_digest, backup_digest) < 0) && (errno != ENOENT)) { - crm_perror(LOG_ERR, "Could not archive %s by linking to %s", - cib_digest, backup_digest); - rc = -1; + if (output_data != NULL) { + *output_data = NULL; + } - /* Update the last counter and ensure everything is sync'd to media */ - } else { - pcmk__write_series_sequence(cib_dirname, CIB_SERIES, ++seq, - CIB_SERIES_MAX); - if (cib_do_chown) { - int rc2; + if (op == NULL) { + return -EINVAL; + } - if ((chown(backup_path, cib_file_owner, cib_file_group) < 0) - && (errno != ENOENT)) { - crm_perror(LOG_ERR, "Could not set owner of %s", backup_path); - rc = -1; - } - if ((chown(backup_digest, cib_file_owner, cib_file_group) < 0) - && (errno != ENOENT)) { - crm_perror(LOG_ERR, "Could not set owner of %s", backup_digest); - rc = -1; - } - rc2 = pcmk__chown_series_sequence(cib_dirname, CIB_SERIES, - cib_file_owner, cib_file_group); - if (rc2 != pcmk_rc_ok) { - crm_err("Could not set owner of sequence file in %s: %s", - cib_dirname, pcmk_rc_str(rc2)); - rc = -1; - } + for (lpc = 0; lpc < max_msg_types; lpc++) { + if (pcmk__str_eq(op, cib_file_ops[lpc].op, pcmk__str_casei)) { + fn = &(cib_file_ops[lpc].fn); + query = cib_file_ops[lpc].read_only; + break; } - pcmk__sync_directory(cib_dirname); - crm_info("Archived previous version as %s", backup_path); } - free(cib_path); - free(cib_digest); - free(backup_path); - free(backup_digest); + if (fn == NULL) { + return -EPROTONOSUPPORT; + } + + cib->call_id++; + request = cib_create_op(cib->call_id, op, host, section, data, call_options, + user_name); + if(user_name) { + crm_xml_add(request, XML_ACL_TAG_USER, user_name); + } + + /* Mirror the logic in cib_prepare_common() */ + if (section != NULL && data != NULL && pcmk__str_eq(crm_element_name(data), XML_TAG_CIB, pcmk__str_none)) { + data = pcmk_find_cib_element(data, section); + } + + rc = cib_perform_op(op, call_options, fn, query, + section, request, data, TRUE, &changed, in_mem_cib, &result_cib, &cib_diff, + &output); + + free_xml(request); + if (rc == -pcmk_err_schema_validation) { + validate_xml_verbose(result_cib); + } + + if (rc != pcmk_ok) { + free_xml(result_cib); + + } else if (query == FALSE) { + pcmk__output_t *out = NULL; + + rc = pcmk_rc2legacy(pcmk__log_output_new(&out)); + CRM_CHECK(rc == pcmk_ok, goto done); + + pcmk__output_set_log_level(out, LOG_DEBUG); + rc = out->message(out, "xml-patchset", cib_diff); + out->finish(out, pcmk_rc2exitc(rc), true, NULL); + pcmk__output_free(out); + rc = pcmk_ok; + + free_xml(in_mem_cib); + in_mem_cib = result_cib; + cib_set_file_flags(private, cib_file_flag_dirty); + } + + if (cib->op_callback != NULL) { + cib->op_callback(NULL, cib->call_id, rc, output); + } + + if ((output_data != NULL) && (output != NULL)) { + *output_data = (output == in_mem_cib)? copy_xml(output) : output; + } + +done: + free_xml(cib_diff); + + if ((output_data == NULL) && (output != in_mem_cib)) { + /* Don't free output if we're still using it. (output_data != NULL) + * means we may have assigned *output_data = output above. + */ + free_xml(output); + } + free(effective_user); return rc; } /*! * \internal - * \brief Prepare CIB XML to be written to disk + * \brief Read CIB from disk and validate it against XML schema * - * Set num_updates to 0, set cib-last-written to the current timestamp, - * and strip out the status section. + * \param[in] filename Name of file to read CIB from * - * \param[in,out] root Root of CIB XML tree + * \return pcmk_ok on success, + * -ENXIO if file does not exist (or stat() otherwise fails), or + * -pcmk_err_schema_validation if XML doesn't parse or validate + * \note If filename is the live CIB, this will *not* verify its digest, + * though that functionality would be trivial to add here. + * Also, this will *not* verify that the file is writable, + * because some callers might not need to write. + */ +static int +load_file_cib(const char *filename) +{ + struct stat buf; + xmlNode *root = NULL; + + /* Ensure file is readable */ + if (strcmp(filename, "-") && (stat(filename, &buf) < 0)) { + return -ENXIO; + } + + /* Parse XML from file */ + root = filename2xml(filename); + if (root == NULL) { + return -pcmk_err_schema_validation; + } + + /* Add a status section if not already present */ + if (find_xml_node(root, XML_CIB_TAG_STATUS, FALSE) == NULL) { + create_xml_node(root, XML_CIB_TAG_STATUS); + } + + /* Validate XML against its specified schema */ + if (validate_xml(root, NULL, TRUE) == FALSE) { + const char *schema = crm_element_value(root, XML_ATTR_VALIDATION); + + crm_err("CIB does not validate against %s", schema); + free_xml(root); + return -pcmk_err_schema_validation; + } + + /* Remember the parsed XML for later use */ + in_mem_cib = root; + return pcmk_ok; +} + +static int +cib_file_signon(cib_t *cib, const char *name, enum cib_conn_type type) +{ + int rc = pcmk_ok; + cib_file_opaque_t *private = cib->variant_opaque; + + if (private->filename == NULL) { + rc = -EINVAL; + } else { + rc = load_file_cib(private->filename); + } + + if (rc == pcmk_ok) { + crm_debug("Opened connection to local file '%s' for %s", + private->filename, name); + cib->state = cib_connected_command; + cib->type = cib_command; + + } else { + crm_info("Connection to local file '%s' for %s failed: %s\n", + private->filename, name, pcmk_strerror(rc)); + } + return rc; +} + +/*! + * \internal + * \brief Write out the in-memory CIB to a live CIB file * - * \return void + * param[in,out] path Full path to file to write + * + * \return 0 on success, -1 on failure */ -static void -cib_file_prepare_xml(xmlNode *root) +static int +cib_file_write_live(char *path) { - xmlNode *cib_status_root = NULL; + uid_t uid = geteuid(); + struct passwd *daemon_pwent; + char *sep = strrchr(path, '/'); + const char *cib_dirname, *cib_filename; + int rc = 0; - /* Always write out with num_updates=0 and current last-written timestamp */ - crm_xml_add(root, XML_ATTR_NUMUPDATES, "0"); - pcmk__xe_add_last_written(root); + /* Get the desired uid/gid */ + errno = 0; + daemon_pwent = getpwnam(CRM_DAEMON_USER); + if (daemon_pwent == NULL) { + crm_perror(LOG_ERR, "Could not find %s user", CRM_DAEMON_USER); + return -1; + } - /* Delete status section before writing to file, because - * we discard it on startup anyway, and users get confused by it */ - cib_status_root = find_xml_node(root, XML_CIB_TAG_STATUS, TRUE); - CRM_LOG_ASSERT(cib_status_root != NULL); - if (cib_status_root != NULL) { - free_xml(cib_status_root); + /* If we're root, we can change the ownership; + * if we're daemon, anything we create will be OK; + * otherwise, block access so we don't create wrong owner + */ + if ((uid != 0) && (uid != daemon_pwent->pw_uid)) { + crm_perror(LOG_ERR, "Must be root or %s to modify live CIB", + CRM_DAEMON_USER); + return 0; } + + /* fancy footwork to separate dirname from filename + * (we know the canonical name maps to the live CIB, + * but the given name might be relative, or symlinked) + */ + if (sep == NULL) { /* no directory component specified */ + cib_dirname = "./"; + cib_filename = path; + } else if (sep == path) { /* given name is in / */ + cib_dirname = "/"; + cib_filename = path + 1; + } else { /* typical case; split given name into parts */ + *sep = '\0'; + cib_dirname = path; + cib_filename = sep + 1; + } + + /* if we're root, we want to update the file ownership */ + if (uid == 0) { + cib_file_owner = daemon_pwent->pw_uid; + cib_file_group = daemon_pwent->pw_gid; + cib_do_chown = TRUE; + } + + /* write the file */ + if (cib_file_write_with_digest(in_mem_cib, cib_dirname, + cib_filename) != pcmk_ok) { + rc = -1; + } + + /* turn off file ownership changes, for other callers */ + if (uid == 0) { + cib_do_chown = FALSE; + } + + /* undo fancy stuff */ + if ((sep != NULL) && (*sep == '\0')) { + *sep = '/'; + } + + return rc; } /*! * \internal - * \brief Write CIB to disk, along with a signature file containing its digest + * \brief Sign-off method for CIB file variants * - * \param[in,out] cib_root Root of XML tree to write - * \param[in] cib_dirname Directory containing CIB and signature files - * \param[in] cib_filename Name (relative to cib_dirname) of file to write + * This will write the file to disk if needed, and free the in-memory CIB. If + * the file is the live CIB, it will compute and write a signature as well. + * + * \param[in,out] cib CIB object to sign off * - * \return pcmk_ok on success, - * pcmk_err_cib_modified if existing cib_filename doesn't match digest, - * pcmk_err_cib_backup if existing cib_filename couldn't be backed up, - * or pcmk_err_cib_save if new cib_filename couldn't be saved + * \return pcmk_ok on success, pcmk_err_generic on failure + * \todo This method should refuse to write the live CIB if the CIB manager is + * running. */ -int -cib_file_write_with_digest(xmlNode *cib_root, const char *cib_dirname, - const char *cib_filename) +static int +cib_file_signoff(cib_t *cib) { - int exit_rc = pcmk_ok; - int rc, fd; - char *digest = NULL; + int rc = pcmk_ok; + cib_file_opaque_t *private = cib->variant_opaque; - /* Detect CIB version for diagnostic purposes */ - const char *epoch = crm_element_value(cib_root, XML_ATTR_GENERATION); - const char *admin_epoch = crm_element_value(cib_root, - XML_ATTR_GENERATION_ADMIN); + crm_debug("Disconnecting from the CIB manager"); + cib->state = cib_disconnected; + cib->type = cib_no_connection; - /* Determine full CIB and signature pathnames */ - char *cib_path = crm_strdup_printf("%s/%s", cib_dirname, cib_filename); - char *digest_path = crm_strdup_printf("%s.sig", cib_path); + /* If the in-memory CIB has been changed, write it to disk */ + if (pcmk_is_set(private->flags, cib_file_flag_dirty)) { - /* Create temporary file name patterns for writing out CIB and signature */ - char *tmp_cib = crm_strdup_printf("%s/cib.XXXXXX", cib_dirname); - char *tmp_digest = crm_strdup_printf("%s/cib.XXXXXX", cib_dirname); + /* If this is the live CIB, write it out with a digest */ + if (pcmk_is_set(private->flags, cib_file_flag_live)) { + if (cib_file_write_live(private->filename) < 0) { + rc = pcmk_err_generic; + } - CRM_ASSERT((cib_path != NULL) && (digest_path != NULL) - && (tmp_cib != NULL) && (tmp_digest != NULL)); + /* Otherwise, it's a simple write */ + } else { + gboolean do_bzip = pcmk__ends_with_ext(private->filename, ".bz2"); - /* Ensure the admin didn't modify the existing CIB underneath us */ - crm_trace("Reading cluster configuration file %s", cib_path); - rc = cib_file_read_and_verify(cib_path, NULL, NULL); - if ((rc != pcmk_ok) && (rc != -ENOENT)) { - crm_err("%s was manually modified while the cluster was active!", - cib_path); - exit_rc = pcmk_err_cib_modified; - goto cleanup; - } + if (write_xml_file(in_mem_cib, private->filename, do_bzip) <= 0) { + rc = pcmk_err_generic; + } + } - /* Back up the existing CIB */ - if (cib_file_backup(cib_dirname, cib_filename) < 0) { - exit_rc = pcmk_err_cib_backup; - goto cleanup; + if (rc == pcmk_ok) { + crm_info("Wrote CIB to %s", private->filename); + cib_clear_file_flags(private, cib_file_flag_dirty); + } else { + crm_err("Could not write CIB to %s", private->filename); + } } - crm_debug("Writing CIB to disk"); - umask(S_IWGRP | S_IWOTH | S_IROTH); - cib_file_prepare_xml(cib_root); + /* Free the in-memory CIB */ + free_xml(in_mem_cib); + in_mem_cib = NULL; + return rc; +} - /* Write the CIB to a temporary file, so we can deploy (near) atomically */ - fd = mkstemp(tmp_cib); - if (fd < 0) { - crm_perror(LOG_ERR, "Couldn't open temporary file %s for writing CIB", - tmp_cib); - exit_rc = pcmk_err_cib_save; - goto cleanup; - } +static int +cib_file_free(cib_t *cib) +{ + int rc = pcmk_ok; - /* Protect the temporary file */ - if (fchmod(fd, S_IRUSR | S_IWUSR) < 0) { - crm_perror(LOG_ERR, "Couldn't protect temporary file %s for writing CIB", - tmp_cib); - exit_rc = pcmk_err_cib_save; - goto cleanup; - } - if (cib_do_chown && (fchown(fd, cib_file_owner, cib_file_group) < 0)) { - crm_perror(LOG_ERR, "Couldn't protect temporary file %s for writing CIB", - tmp_cib); - exit_rc = pcmk_err_cib_save; - goto cleanup; + if (cib->state != cib_disconnected) { + rc = cib_file_signoff(cib); } - /* Write out the CIB */ - if (write_xml_fd(cib_root, tmp_cib, fd, FALSE) <= 0) { - crm_err("Changes couldn't be written to %s", tmp_cib); - exit_rc = pcmk_err_cib_save; - goto cleanup; - } + if (rc == pcmk_ok) { + cib_file_opaque_t *private = cib->variant_opaque; - /* Calculate CIB digest */ - digest = calculate_on_disk_digest(cib_root); - CRM_ASSERT(digest != NULL); - crm_info("Wrote version %s.%s.0 of the CIB to disk (digest: %s)", - (admin_epoch ? admin_epoch : "0"), (epoch ? epoch : "0"), digest); + free(private->filename); + free(cib->cmds); + free(private); + free(cib); - /* Write the CIB digest to a temporary file */ - fd = mkstemp(tmp_digest); - if (fd < 0) { - crm_perror(LOG_ERR, "Could not create temporary file for CIB digest"); - exit_rc = pcmk_err_cib_save; - goto cleanup; - } - if (cib_do_chown && (fchown(fd, cib_file_owner, cib_file_group) < 0)) { - crm_perror(LOG_ERR, "Couldn't protect temporary file %s for writing CIB", - tmp_cib); - exit_rc = pcmk_err_cib_save; - close(fd); - goto cleanup; - } - rc = pcmk__write_sync(fd, digest); - if (rc != pcmk_rc_ok) { - crm_err("Could not write digest to %s: %s", - tmp_digest, pcmk_rc_str(rc)); - exit_rc = pcmk_err_cib_save; - close(fd); - goto cleanup; + } else { + fprintf(stderr, "Couldn't sign off: %d\n", rc); } - close(fd); - crm_debug("Wrote digest %s to disk", digest); - /* Verify that what we wrote is sane */ - crm_info("Reading cluster configuration file %s (digest: %s)", - tmp_cib, tmp_digest); - rc = cib_file_read_and_verify(tmp_cib, tmp_digest, NULL); - CRM_ASSERT(rc == 0); + return rc; +} - /* Rename temporary files to live, and sync directory changes to media */ - crm_debug("Activating %s", tmp_cib); - if (rename(tmp_cib, cib_path) < 0) { - crm_perror(LOG_ERR, "Couldn't rename %s as %s", tmp_cib, cib_path); - exit_rc = pcmk_err_cib_save; +static int +cib_file_inputfd(cib_t *cib) +{ + return -EPROTONOSUPPORT; +} + +static int +cib_file_register_notification(cib_t *cib, const char *callback, int enabled) +{ + return -EPROTONOSUPPORT; +} + +static int +cib_file_set_connection_dnotify(cib_t *cib, + void (*dnotify) (gpointer user_data)) +{ + return -EPROTONOSUPPORT; +} + +/*! + * \internal + * \brief Get the given CIB connection's unique client identifier + * + * \param[in] cib CIB connection + * \param[out] async_id If not \p NULL, where to store asynchronous client ID + * \param[out] sync_id If not \p NULL, where to store synchronous client ID + * + * \return Legacy Pacemaker return code (specifically, \p -EPROTONOSUPPORT) + * + * \note This is the \p cib_file variant implementation of + * \p cib_api_operations_t:client_id(). + * \note A \p cib_file object doesn't connect to the CIB and is never assigned a + * client ID. + */ +static int +cib_file_client_id(const cib_t *cib, const char **async_id, + const char **sync_id) +{ + if (async_id != NULL) { + *async_id = NULL; } - if (rename(tmp_digest, digest_path) < 0) { - crm_perror(LOG_ERR, "Couldn't rename %s as %s", tmp_digest, - digest_path); - exit_rc = pcmk_err_cib_save; + if (sync_id != NULL) { + *sync_id = NULL; } - pcmk__sync_directory(cib_dirname); - - cleanup: - free(cib_path); - free(digest_path); - free(digest); - free(tmp_digest); - free(tmp_cib); - return exit_rc; + return -EPROTONOSUPPORT; } cib_t * cib_file_new(const char *cib_location) { cib_file_opaque_t *private = NULL; cib_t *cib = cib_new_variant(); if (cib == NULL) { return NULL; } private = calloc(1, sizeof(cib_file_opaque_t)); if (private == NULL) { free(cib); return NULL; } cib->variant = cib_file; cib->variant_opaque = private; if (cib_location == NULL) { cib_location = getenv("CIB_file"); CRM_CHECK(cib_location != NULL, return NULL); // Shouldn't be possible } private->flags = 0; if (cib_file_is_live(cib_location)) { cib_set_file_flags(private, cib_file_flag_live); crm_trace("File %s detected as live CIB", cib_location); } private->filename = strdup(cib_location); /* assign variant specific ops */ cib->delegate_fn = cib_file_perform_op_delegate; cib->cmds->signon = cib_file_signon; cib->cmds->signoff = cib_file_signoff; cib->cmds->free = cib_file_free; cib->cmds->inputfd = cib_file_inputfd; cib->cmds->register_notification = cib_file_register_notification; cib->cmds->set_connection_dnotify = cib_file_set_connection_dnotify; - cib->cmds->client_id = cib_file_client_id; - - return cib; -} - -static xmlNode *in_mem_cib = NULL; - -/*! - * \internal - * \brief Read CIB from disk and validate it against XML schema - * - * \param[in] filename Name of file to read CIB from - * - * \return pcmk_ok on success, - * -ENXIO if file does not exist (or stat() otherwise fails), or - * -pcmk_err_schema_validation if XML doesn't parse or validate - * \note If filename is the live CIB, this will *not* verify its digest, - * though that functionality would be trivial to add here. - * Also, this will *not* verify that the file is writable, - * because some callers might not need to write. - */ -static int -load_file_cib(const char *filename) -{ - struct stat buf; - xmlNode *root = NULL; - - /* Ensure file is readable */ - if (strcmp(filename, "-") && (stat(filename, &buf) < 0)) { - return -ENXIO; - } - - /* Parse XML from file */ - root = filename2xml(filename); - if (root == NULL) { - return -pcmk_err_schema_validation; - } - - /* Add a status section if not already present */ - if (find_xml_node(root, XML_CIB_TAG_STATUS, FALSE) == NULL) { - create_xml_node(root, XML_CIB_TAG_STATUS); - } - - /* Validate XML against its specified schema */ - if (validate_xml(root, NULL, TRUE) == FALSE) { - const char *schema = crm_element_value(root, XML_ATTR_VALIDATION); - - crm_err("CIB does not validate against %s", schema); - free_xml(root); - return -pcmk_err_schema_validation; - } - - /* Remember the parsed XML for later use */ - in_mem_cib = root; - return pcmk_ok; + cib->cmds->client_id = cib_file_client_id; + + return cib; } -int -cib_file_signon(cib_t * cib, const char *name, enum cib_conn_type type) +/*! + * \internal + * \brief Compare the calculated digest of an XML tree against a signature file + * + * \param[in] root Root of XML tree to compare + * \param[in] sigfile Name of signature file containing digest to compare + * + * \return TRUE if digests match or signature file does not exist, else FALSE + */ +static gboolean +cib_file_verify_digest(xmlNode *root, const char *sigfile) { - int rc = pcmk_ok; - cib_file_opaque_t *private = cib->variant_opaque; - - if (private->filename == NULL) { - rc = -EINVAL; - } else { - rc = load_file_cib(private->filename); - } - - if (rc == pcmk_ok) { - crm_debug("Opened connection to local file '%s' for %s", - private->filename, name); - cib->state = cib_connected_command; - cib->type = cib_command; + gboolean passed = FALSE; + char *expected; + int rc = pcmk__file_contents(sigfile, &expected); - } else { - crm_info("Connection to local file '%s' for %s failed: %s\n", - private->filename, name, pcmk_strerror(rc)); + switch (rc) { + case pcmk_rc_ok: + if (expected == NULL) { + crm_err("On-disk digest at %s is empty", sigfile); + return FALSE; + } + break; + case ENOENT: + crm_warn("No on-disk digest present at %s", sigfile); + return TRUE; + default: + crm_err("Could not read on-disk digest from %s: %s", + sigfile, pcmk_rc_str(rc)); + return FALSE; } - return rc; + passed = pcmk__verify_digest(root, expected); + free(expected); + return passed; } /*! * \internal - * \brief Write out the in-memory CIB to a live CIB file + * \brief Read an XML tree from a file and verify its digest * - * param[in,out] path Full path to file to write + * \param[in] filename Name of XML file to read + * \param[in] sigfile Name of signature file containing digest to compare + * \param[out] root If non-NULL, will be set to pointer to parsed XML tree * - * \return 0 on success, -1 on failure + * \return 0 if file was successfully read, parsed and verified, otherwise: + * -errno on stat() failure, + * -pcmk_err_cib_corrupt if file size is 0 or XML is not parseable, or + * -pcmk_err_cib_modified if digests do not match + * \note If root is non-NULL, it is the caller's responsibility to free *root on + * successful return. */ -static int -cib_file_write_live(char *path) +int +cib_file_read_and_verify(const char *filename, const char *sigfile, xmlNode **root) { - uid_t uid = geteuid(); - struct passwd *daemon_pwent; - char *sep = strrchr(path, '/'); - const char *cib_dirname, *cib_filename; - int rc = 0; - - /* Get the desired uid/gid */ - errno = 0; - daemon_pwent = getpwnam(CRM_DAEMON_USER); - if (daemon_pwent == NULL) { - crm_perror(LOG_ERR, "Could not find %s user", CRM_DAEMON_USER); - return -1; - } + int s_res; + struct stat buf; + char *local_sigfile = NULL; + xmlNode *local_root = NULL; - /* If we're root, we can change the ownership; - * if we're daemon, anything we create will be OK; - * otherwise, block access so we don't create wrong owner - */ - if ((uid != 0) && (uid != daemon_pwent->pw_uid)) { - crm_perror(LOG_ERR, "Must be root or %s to modify live CIB", - CRM_DAEMON_USER); - return 0; + CRM_ASSERT(filename != NULL); + if (root) { + *root = NULL; } - /* fancy footwork to separate dirname from filename - * (we know the canonical name maps to the live CIB, - * but the given name might be relative, or symlinked) - */ - if (sep == NULL) { /* no directory component specified */ - cib_dirname = "./"; - cib_filename = path; - } else if (sep == path) { /* given name is in / */ - cib_dirname = "/"; - cib_filename = path + 1; - } else { /* typical case; split given name into parts */ - *sep = '\0'; - cib_dirname = path; - cib_filename = sep + 1; + /* Verify that file exists and its size is nonzero */ + s_res = stat(filename, &buf); + if (s_res < 0) { + crm_perror(LOG_WARNING, "Could not verify cluster configuration file %s", filename); + return -errno; + } else if (buf.st_size == 0) { + crm_warn("Cluster configuration file %s is corrupt (size is zero)", filename); + return -pcmk_err_cib_corrupt; } - /* if we're root, we want to update the file ownership */ - if (uid == 0) { - cib_file_owner = daemon_pwent->pw_uid; - cib_file_group = daemon_pwent->pw_gid; - cib_do_chown = TRUE; + /* Parse XML */ + local_root = filename2xml(filename); + if (local_root == NULL) { + crm_warn("Cluster configuration file %s is corrupt (unparseable as XML)", filename); + return -pcmk_err_cib_corrupt; } - /* write the file */ - if (cib_file_write_with_digest(in_mem_cib, cib_dirname, - cib_filename) != pcmk_ok) { - rc = -1; + /* If sigfile is not specified, use original file name plus .sig */ + if (sigfile == NULL) { + sigfile = local_sigfile = crm_strdup_printf("%s.sig", filename); } - /* turn off file ownership changes, for other callers */ - if (uid == 0) { - cib_do_chown = FALSE; + /* Verify that digests match */ + if (cib_file_verify_digest(local_root, sigfile) == FALSE) { + free(local_sigfile); + free_xml(local_root); + return -pcmk_err_cib_modified; } - /* undo fancy stuff */ - if ((sep != NULL) && (*sep == '\0')) { - *sep = '/'; + free(local_sigfile); + if (root) { + *root = local_root; + } else { + free_xml(local_root); } - - return rc; + return pcmk_ok; } /*! * \internal - * \brief Sign-off method for CIB file variants - * - * This will write the file to disk if needed, and free the in-memory CIB. If - * the file is the live CIB, it will compute and write a signature as well. + * \brief Back up a CIB * - * \param[in,out] cib CIB object to sign off + * \param[in] cib_dirname Directory containing CIB file and backups + * \param[in] cib_filename Name (relative to cib_dirname) of CIB file to back up * - * \return pcmk_ok on success, pcmk_err_generic on failure - * \todo This method should refuse to write the live CIB if the CIB manager is - * running. + * \return 0 on success, -1 on error */ -int -cib_file_signoff(cib_t * cib) +static int +cib_file_backup(const char *cib_dirname, const char *cib_filename) { - int rc = pcmk_ok; - cib_file_opaque_t *private = cib->variant_opaque; + int rc = 0; + unsigned int seq; + char *cib_path = crm_strdup_printf("%s/%s", cib_dirname, cib_filename); + char *cib_digest = crm_strdup_printf("%s.sig", cib_path); + char *backup_path; + char *backup_digest; - crm_debug("Disconnecting from the CIB manager"); - cib->state = cib_disconnected; - cib->type = cib_no_connection; + // Determine backup and digest file names + if (pcmk__read_series_sequence(cib_dirname, CIB_SERIES, + &seq) != pcmk_rc_ok) { + // @TODO maybe handle errors better ... + seq = 0; + } + backup_path = pcmk__series_filename(cib_dirname, CIB_SERIES, seq, + CIB_SERIES_BZIP); + backup_digest = crm_strdup_printf("%s.sig", backup_path); - /* If the in-memory CIB has been changed, write it to disk */ - if (pcmk_is_set(private->flags, cib_file_flag_dirty)) { + /* Remove the old backups if they exist */ + unlink(backup_path); + unlink(backup_digest); - /* If this is the live CIB, write it out with a digest */ - if (pcmk_is_set(private->flags, cib_file_flag_live)) { - if (cib_file_write_live(private->filename) < 0) { - rc = pcmk_err_generic; - } + /* Back up the CIB, by hard-linking it to the backup name */ + if ((link(cib_path, backup_path) < 0) && (errno != ENOENT)) { + crm_perror(LOG_ERR, "Could not archive %s by linking to %s", + cib_path, backup_path); + rc = -1; - /* Otherwise, it's a simple write */ - } else { - gboolean do_bzip = pcmk__ends_with_ext(private->filename, ".bz2"); + /* Back up the CIB signature similarly */ + } else if ((link(cib_digest, backup_digest) < 0) && (errno != ENOENT)) { + crm_perror(LOG_ERR, "Could not archive %s by linking to %s", + cib_digest, backup_digest); + rc = -1; - if (write_xml_file(in_mem_cib, private->filename, do_bzip) <= 0) { - rc = pcmk_err_generic; - } - } + /* Update the last counter and ensure everything is sync'd to media */ + } else { + pcmk__write_series_sequence(cib_dirname, CIB_SERIES, ++seq, + CIB_SERIES_MAX); + if (cib_do_chown) { + int rc2; - if (rc == pcmk_ok) { - crm_info("Wrote CIB to %s", private->filename); - cib_clear_file_flags(private, cib_file_flag_dirty); - } else { - crm_err("Could not write CIB to %s", private->filename); + if ((chown(backup_path, cib_file_owner, cib_file_group) < 0) + && (errno != ENOENT)) { + crm_perror(LOG_ERR, "Could not set owner of %s", backup_path); + rc = -1; + } + if ((chown(backup_digest, cib_file_owner, cib_file_group) < 0) + && (errno != ENOENT)) { + crm_perror(LOG_ERR, "Could not set owner of %s", backup_digest); + rc = -1; + } + rc2 = pcmk__chown_series_sequence(cib_dirname, CIB_SERIES, + cib_file_owner, cib_file_group); + if (rc2 != pcmk_rc_ok) { + crm_err("Could not set owner of sequence file in %s: %s", + cib_dirname, pcmk_rc_str(rc2)); + rc = -1; + } } - } - - /* Free the in-memory CIB */ - free_xml(in_mem_cib); - in_mem_cib = NULL; + pcmk__sync_directory(cib_dirname); + crm_info("Archived previous version as %s", backup_path); + } + + free(cib_path); + free(cib_digest); + free(backup_path); + free(backup_digest); return rc; } -int -cib_file_free(cib_t * cib) +/*! + * \internal + * \brief Prepare CIB XML to be written to disk + * + * Set num_updates to 0, set cib-last-written to the current timestamp, + * and strip out the status section. + * + * \param[in,out] root Root of CIB XML tree + * + * \return void + */ +static void +cib_file_prepare_xml(xmlNode *root) { - int rc = pcmk_ok; - - if (cib->state != cib_disconnected) { - rc = cib_file_signoff(cib); - } - - if (rc == pcmk_ok) { - cib_file_opaque_t *private = cib->variant_opaque; + xmlNode *cib_status_root = NULL; - free(private->filename); - free(cib->cmds); - free(private); - free(cib); + /* Always write out with num_updates=0 and current last-written timestamp */ + crm_xml_add(root, XML_ATTR_NUMUPDATES, "0"); + pcmk__xe_add_last_written(root); - } else { - fprintf(stderr, "Couldn't sign off: %d\n", rc); + /* Delete status section before writing to file, because + * we discard it on startup anyway, and users get confused by it */ + cib_status_root = find_xml_node(root, XML_CIB_TAG_STATUS, TRUE); + CRM_LOG_ASSERT(cib_status_root != NULL); + if (cib_status_root != NULL) { + free_xml(cib_status_root); } - - return rc; } -struct cib_func_entry { - const char *op; - gboolean read_only; - cib_op_t fn; -}; - -/* *INDENT-OFF* */ -static struct cib_func_entry cib_file_ops[] = { - { PCMK__CIB_REQUEST_QUERY, TRUE, cib_process_query}, - { PCMK__CIB_REQUEST_MODIFY, FALSE, cib_process_modify}, - { PCMK__CIB_REQUEST_APPLY_PATCH,FALSE, cib_process_diff}, - { PCMK__CIB_REQUEST_BUMP, FALSE, cib_process_bump }, - { PCMK__CIB_REQUEST_REPLACE, FALSE, cib_process_replace}, - { PCMK__CIB_REQUEST_CREATE, FALSE, cib_process_create }, - { PCMK__CIB_REQUEST_DELETE, FALSE, cib_process_delete}, - { PCMK__CIB_REQUEST_ERASE, FALSE, cib_process_erase}, - { PCMK__CIB_REQUEST_UPGRADE, FALSE, cib_process_upgrade}, -}; -/* *INDENT-ON* */ - +/*! + * \internal + * \brief Write CIB to disk, along with a signature file containing its digest + * + * \param[in,out] cib_root Root of XML tree to write + * \param[in] cib_dirname Directory containing CIB and signature files + * \param[in] cib_filename Name (relative to cib_dirname) of file to write + * + * \return pcmk_ok on success, + * pcmk_err_cib_modified if existing cib_filename doesn't match digest, + * pcmk_err_cib_backup if existing cib_filename couldn't be backed up, + * or pcmk_err_cib_save if new cib_filename couldn't be saved + */ int -cib_file_perform_op_delegate(cib_t * cib, const char *op, const char *host, const char *section, - xmlNode * data, xmlNode ** output_data, int call_options, - const char *user_name) +cib_file_write_with_digest(xmlNode *cib_root, const char *cib_dirname, + const char *cib_filename) { - int rc = pcmk_ok; - char *effective_user = NULL; - gboolean query = FALSE; - gboolean changed = FALSE; - xmlNode *request = NULL; - xmlNode *output = NULL; - xmlNode *cib_diff = NULL; - xmlNode *result_cib = NULL; - cib_op_t *fn = NULL; - int lpc = 0; - static int max_msg_types = PCMK__NELEM(cib_file_ops); - cib_file_opaque_t *private = cib->variant_opaque; + int exit_rc = pcmk_ok; + int rc, fd; + char *digest = NULL; - crm_info("Handling %s operation for %s as %s", - (op? op : "invalid"), (section? section : "entire CIB"), - (user_name? user_name : "default user")); + /* Detect CIB version for diagnostic purposes */ + const char *epoch = crm_element_value(cib_root, XML_ATTR_GENERATION); + const char *admin_epoch = crm_element_value(cib_root, + XML_ATTR_GENERATION_ADMIN); - cib__set_call_options(call_options, "file operation", - cib_no_mtime|cib_inhibit_bcast|cib_scope_local); + /* Determine full CIB and signature pathnames */ + char *cib_path = crm_strdup_printf("%s/%s", cib_dirname, cib_filename); + char *digest_path = crm_strdup_printf("%s.sig", cib_path); - if (cib->state == cib_disconnected) { - return -ENOTCONN; - } + /* Create temporary file name patterns for writing out CIB and signature */ + char *tmp_cib = crm_strdup_printf("%s/cib.XXXXXX", cib_dirname); + char *tmp_digest = crm_strdup_printf("%s/cib.XXXXXX", cib_dirname); - if (output_data != NULL) { - *output_data = NULL; - } + CRM_ASSERT((cib_path != NULL) && (digest_path != NULL) + && (tmp_cib != NULL) && (tmp_digest != NULL)); - if (op == NULL) { - return -EINVAL; + /* Ensure the admin didn't modify the existing CIB underneath us */ + crm_trace("Reading cluster configuration file %s", cib_path); + rc = cib_file_read_and_verify(cib_path, NULL, NULL); + if ((rc != pcmk_ok) && (rc != -ENOENT)) { + crm_err("%s was manually modified while the cluster was active!", + cib_path); + exit_rc = pcmk_err_cib_modified; + goto cleanup; } - for (lpc = 0; lpc < max_msg_types; lpc++) { - if (pcmk__str_eq(op, cib_file_ops[lpc].op, pcmk__str_casei)) { - fn = &(cib_file_ops[lpc].fn); - query = cib_file_ops[lpc].read_only; - break; - } + /* Back up the existing CIB */ + if (cib_file_backup(cib_dirname, cib_filename) < 0) { + exit_rc = pcmk_err_cib_backup; + goto cleanup; } - if (fn == NULL) { - return -EPROTONOSUPPORT; - } + crm_debug("Writing CIB to disk"); + umask(S_IWGRP | S_IWOTH | S_IROTH); + cib_file_prepare_xml(cib_root); - cib->call_id++; - request = cib_create_op(cib->call_id, op, host, section, data, call_options, - user_name); - if(user_name) { - crm_xml_add(request, XML_ACL_TAG_USER, user_name); + /* Write the CIB to a temporary file, so we can deploy (near) atomically */ + fd = mkstemp(tmp_cib); + if (fd < 0) { + crm_perror(LOG_ERR, "Couldn't open temporary file %s for writing CIB", + tmp_cib); + exit_rc = pcmk_err_cib_save; + goto cleanup; } - /* Mirror the logic in cib_prepare_common() */ - if (section != NULL && data != NULL && pcmk__str_eq(crm_element_name(data), XML_TAG_CIB, pcmk__str_none)) { - data = pcmk_find_cib_element(data, section); + /* Protect the temporary file */ + if (fchmod(fd, S_IRUSR | S_IWUSR) < 0) { + crm_perror(LOG_ERR, "Couldn't protect temporary file %s for writing CIB", + tmp_cib); + exit_rc = pcmk_err_cib_save; + goto cleanup; } - - rc = cib_perform_op(op, call_options, fn, query, - section, request, data, TRUE, &changed, in_mem_cib, &result_cib, &cib_diff, - &output); - - free_xml(request); - if (rc == -pcmk_err_schema_validation) { - validate_xml_verbose(result_cib); + if (cib_do_chown && (fchown(fd, cib_file_owner, cib_file_group) < 0)) { + crm_perror(LOG_ERR, "Couldn't protect temporary file %s for writing CIB", + tmp_cib); + exit_rc = pcmk_err_cib_save; + goto cleanup; } - if (rc != pcmk_ok) { - free_xml(result_cib); - - } else if (query == FALSE) { - pcmk__output_t *out = NULL; - - rc = pcmk_rc2legacy(pcmk__log_output_new(&out)); - CRM_CHECK(rc == pcmk_ok, goto done); + /* Write out the CIB */ + if (write_xml_fd(cib_root, tmp_cib, fd, FALSE) <= 0) { + crm_err("Changes couldn't be written to %s", tmp_cib); + exit_rc = pcmk_err_cib_save; + goto cleanup; + } - pcmk__output_set_log_level(out, LOG_DEBUG); - rc = out->message(out, "xml-patchset", cib_diff); - out->finish(out, pcmk_rc2exitc(rc), true, NULL); - pcmk__output_free(out); - rc = pcmk_ok; + /* Calculate CIB digest */ + digest = calculate_on_disk_digest(cib_root); + CRM_ASSERT(digest != NULL); + crm_info("Wrote version %s.%s.0 of the CIB to disk (digest: %s)", + (admin_epoch ? admin_epoch : "0"), (epoch ? epoch : "0"), digest); - free_xml(in_mem_cib); - in_mem_cib = result_cib; - cib_set_file_flags(private, cib_file_flag_dirty); + /* Write the CIB digest to a temporary file */ + fd = mkstemp(tmp_digest); + if (fd < 0) { + crm_perror(LOG_ERR, "Could not create temporary file for CIB digest"); + exit_rc = pcmk_err_cib_save; + goto cleanup; } - - if (cib->op_callback != NULL) { - cib->op_callback(NULL, cib->call_id, rc, output); + if (cib_do_chown && (fchown(fd, cib_file_owner, cib_file_group) < 0)) { + crm_perror(LOG_ERR, "Couldn't protect temporary file %s for writing CIB", + tmp_cib); + exit_rc = pcmk_err_cib_save; + close(fd); + goto cleanup; } - - if ((output_data != NULL) && (output != NULL)) { - *output_data = (output == in_mem_cib)? copy_xml(output) : output; + rc = pcmk__write_sync(fd, digest); + if (rc != pcmk_rc_ok) { + crm_err("Could not write digest to %s: %s", + tmp_digest, pcmk_rc_str(rc)); + exit_rc = pcmk_err_cib_save; + close(fd); + goto cleanup; } + close(fd); + crm_debug("Wrote digest %s to disk", digest); -done: - free_xml(cib_diff); + /* Verify that what we wrote is sane */ + crm_info("Reading cluster configuration file %s (digest: %s)", + tmp_cib, tmp_digest); + rc = cib_file_read_and_verify(tmp_cib, tmp_digest, NULL); + CRM_ASSERT(rc == 0); - if ((output_data == NULL) && (output != in_mem_cib)) { - /* Don't free output if we're still using it. (output_data != NULL) - * means we may have assigned *output_data = output above. - */ - free_xml(output); + /* Rename temporary files to live, and sync directory changes to media */ + crm_debug("Activating %s", tmp_cib); + if (rename(tmp_cib, cib_path) < 0) { + crm_perror(LOG_ERR, "Couldn't rename %s as %s", tmp_cib, cib_path); + exit_rc = pcmk_err_cib_save; } - free(effective_user); - return rc; + if (rename(tmp_digest, digest_path) < 0) { + crm_perror(LOG_ERR, "Couldn't rename %s as %s", tmp_digest, + digest_path); + exit_rc = pcmk_err_cib_save; + } + pcmk__sync_directory(cib_dirname); + + cleanup: + free(cib_path); + free(digest_path); + free(digest); + free(tmp_digest); + free(tmp_cib); + return exit_rc; } diff --git a/lib/cib/cib_native.c b/lib/cib/cib_native.c index ee41eb1cbc..4a87f56ab6 100644 --- a/lib/cib/cib_native.c +++ b/lib/cib/cib_native.c @@ -1,509 +1,502 @@ /* * Copyright 2004 International Business Machines - * Later changes copyright 2004-2022 the Pacemaker project contributors + * Later changes copyright 2004-2023 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 #ifndef _GNU_SOURCE # define _GNU_SOURCE #endif #include #include #include #include #include #include #include #include #include #include #include #include typedef struct cib_native_opaque_s { char *token; crm_ipc_t *ipc; void (*dnotify_fn) (gpointer user_data); mainloop_io_t *source; } cib_native_opaque_t; -int cib_native_perform_op_delegate(cib_t * cib, const char *op, const char *host, - const char *section, xmlNode * data, xmlNode ** output_data, - int call_options, const char *user_name); - -int cib_native_free(cib_t * cib); -int cib_native_signoff(cib_t * cib); -int cib_native_signon(cib_t * cib, const char *name, enum cib_conn_type type); -int cib_native_signon_raw(cib_t * cib, const char *name, enum cib_conn_type type, int *event_fd); - -int cib_native_set_connection_dnotify(cib_t * cib, void (*dnotify) (gpointer user_data)); - static int -cib_native_register_notification(cib_t *cib, const char *callback, int enabled) +cib_native_perform_op_delegate(cib_t *cib, const char *op, const char *host, + const char *section, xmlNode *data, + xmlNode **output_data, int call_options, + const char *user_name) { int rc = pcmk_ok; - xmlNode *notify_msg = create_xml_node(NULL, "cib-callback"); + int reply_id = 0; + enum crm_ipc_flags ipc_flags = crm_ipc_flags_none; + + xmlNode *op_msg = NULL; + xmlNode *op_reply = NULL; + cib_native_opaque_t *native = cib->variant_opaque; - if (cib->state != cib_disconnected) { - crm_xml_add(notify_msg, F_CIB_OPERATION, T_CIB_NOTIFY); - crm_xml_add(notify_msg, F_CIB_NOTIFY_TYPE, callback); - crm_xml_add_int(notify_msg, F_CIB_NOTIFY_ACTIVATE, enabled); - rc = crm_ipc_send(native->ipc, notify_msg, crm_ipc_client_response, - 1000 * cib->call_timeout, NULL); - if (rc <= 0) { - crm_trace("Notification not registered: %d", rc); - rc = -ECOMM; - } + if (cib->state == cib_disconnected) { + return -ENOTCONN; } - free_xml(notify_msg); - return rc; -} + if (output_data != NULL) { + *output_data = NULL; + } -/*! - * \internal - * \brief Get the given CIB connection's unique client identifier - * - * These can be used to check whether this client requested the action that - * triggered a CIB notification. - * - * \param[in] cib CIB connection - * \param[out] async_id If not \p NULL, where to store asynchronous client ID - * \param[out] sync_id If not \p NULL, where to store synchronous client ID - * - * \return Legacy Pacemaker return code (specifically, \p pcmk_ok) - * - * \note This is the \p cib_native variant implementation of - * \p cib_api_operations_t:client_id(). - * \note For \p cib_native objects, \p async_id and \p sync_id are the same. - * \note The client ID is assigned during CIB sign-on. - */ -static int -cib_native_client_id(const cib_t *cib, const char **async_id, - const char **sync_id) -{ - cib_native_opaque_t *native = cib->variant_opaque; + if (op == NULL) { + crm_err("No operation specified"); + return -EINVAL; + } - if (async_id != NULL) { - *async_id = native->token; + if (call_options & cib_sync_call) { + pcmk__set_ipc_flags(ipc_flags, "client", crm_ipc_client_response); } - if (sync_id != NULL) { - *sync_id = native->token; + + cib->call_id++; + if (cib->call_id < 1) { + cib->call_id = 1; } - return pcmk_ok; -} -cib_t * -cib_native_new(void) -{ - cib_native_opaque_t *native = NULL; - cib_t *cib = cib_new_variant(); + op_msg = cib_create_op(cib->call_id, op, host, section, data, call_options, + user_name); + if (op_msg == NULL) { + return -EPROTO; + } - if (cib == NULL) { - return NULL; + crm_trace("Sending %s message to the CIB manager (timeout=%ds)", op, cib->call_timeout); + rc = crm_ipc_send(native->ipc, op_msg, ipc_flags, cib->call_timeout * 1000, &op_reply); + free_xml(op_msg); + + if (rc < 0) { + crm_err("Couldn't perform %s operation (timeout=%ds): %s (%d)", op, + cib->call_timeout, pcmk_strerror(rc), rc); + rc = -ECOMM; + goto done; } - native = calloc(1, sizeof(cib_native_opaque_t)); + crm_log_xml_trace(op_reply, "Reply"); - if (native == NULL) { - free(cib); - return NULL; + if (!(call_options & cib_sync_call)) { + crm_trace("Async call, returning %d", cib->call_id); + CRM_CHECK(cib->call_id != 0, return -ENOMSG); + free_xml(op_reply); + return cib->call_id; } - cib->variant = cib_native; - cib->variant_opaque = native; + rc = pcmk_ok; + crm_element_value_int(op_reply, F_CIB_CALLID, &reply_id); + if (reply_id == cib->call_id) { + xmlNode *tmp = get_message_xml(op_reply, F_CIB_CALLDATA); - native->ipc = NULL; - native->source = NULL; - native->dnotify_fn = NULL; + crm_trace("Synchronous reply %d received", reply_id); + if (crm_element_value_int(op_reply, F_CIB_RC, &rc) != 0) { + rc = -EPROTO; + } - /* assign variant specific ops */ - cib->delegate_fn = cib_native_perform_op_delegate; - cib->cmds->signon = cib_native_signon; - cib->cmds->signon_raw = cib_native_signon_raw; - cib->cmds->signoff = cib_native_signoff; - cib->cmds->free = cib_native_free; + if (output_data == NULL || (call_options & cib_discard_reply)) { + crm_trace("Discarding reply"); - cib->cmds->register_notification = cib_native_register_notification; - cib->cmds->set_connection_dnotify = cib_native_set_connection_dnotify; + } else if (tmp != NULL) { + *output_data = copy_xml(tmp); + } - cib->cmds->client_id = cib_native_client_id; + } else if (reply_id <= 0) { + crm_err("Received bad reply: No id set"); + crm_log_xml_err(op_reply, "Bad reply"); + rc = -ENOMSG; + goto done; - return cib; -} + } else { + crm_err("Received bad reply: %d (wanted %d)", reply_id, cib->call_id); + crm_log_xml_err(op_reply, "Old reply"); + rc = -ENOMSG; + goto done; + } -int -cib_native_signon(cib_t * cib, const char *name, enum cib_conn_type type) -{ - return cib_native_signon_raw(cib, name, type, NULL); + if (op_reply == NULL && cib->state == cib_disconnected) { + rc = -ENOTCONN; + + } else if (rc == pcmk_ok && op_reply == NULL) { + rc = -ETIME; + } + + switch (rc) { + case pcmk_ok: + case -EPERM: + break; + + /* This is an internal value that clients do not and should not care about */ + case -pcmk_err_diff_resync: + rc = pcmk_ok; + break; + + /* These indicate internal problems */ + case -EPROTO: + case -ENOMSG: + crm_err("Call failed: %s", pcmk_strerror(rc)); + if (op_reply) { + crm_log_xml_err(op_reply, "Invalid reply"); + } + break; + + default: + if (!pcmk__str_eq(op, PCMK__CIB_REQUEST_QUERY, pcmk__str_none)) { + crm_warn("Call failed: %s", pcmk_strerror(rc)); + } + } + + done: + if (!crm_ipc_connected(native->ipc)) { + crm_err("The CIB manager disconnected"); + cib->state = cib_disconnected; + } + + free_xml(op_reply); + return rc; } static int -cib_native_dispatch_internal(const char *buffer, ssize_t length, gpointer userdata) +cib_native_dispatch_internal(const char *buffer, ssize_t length, + gpointer userdata) { const char *type = NULL; xmlNode *msg = NULL; cib_t *cib = userdata; crm_trace("dispatching %p", userdata); if (cib == NULL) { crm_err("No CIB!"); return 0; } msg = string2xml(buffer); if (msg == NULL) { crm_warn("Received a NULL message from the CIB manager"); return 0; } /* do callbacks */ type = crm_element_value(msg, F_TYPE); crm_trace("Activating %s callbacks...", type); crm_log_xml_explicit(msg, "cib-reply"); if (pcmk__str_eq(type, T_CIB, pcmk__str_casei)) { cib_native_callback(cib, msg, 0, 0); } else if (pcmk__str_eq(type, T_CIB_NOTIFY, pcmk__str_casei)) { g_list_foreach(cib->notify_list, cib_native_notify, msg); } else { crm_err("Unknown message type: %s", type); } free_xml(msg); return 0; } static void cib_native_destroy(void *userdata) { cib_t *cib = userdata; cib_native_opaque_t *native = cib->variant_opaque; crm_trace("destroying %p", userdata); cib->state = cib_disconnected; native->source = NULL; native->ipc = NULL; if (native->dnotify_fn) { native->dnotify_fn(userdata); } } -int -cib_native_signon_raw(cib_t * cib, const char *name, enum cib_conn_type type, int *async_fd) +static int +cib_native_signoff(cib_t *cib) +{ + cib_native_opaque_t *native = cib->variant_opaque; + + crm_debug("Disconnecting from the CIB manager"); + + cib_free_notify(cib); + remove_cib_op_callback(0, TRUE); + + if (native->source != NULL) { + /* Attached to mainloop */ + mainloop_del_ipc_client(native->source); + native->source = NULL; + native->ipc = NULL; + + } else if (native->ipc) { + /* Not attached to mainloop */ + crm_ipc_t *ipc = native->ipc; + + native->ipc = NULL; + crm_ipc_close(ipc); + crm_ipc_destroy(ipc); + } + + cib->state = cib_disconnected; + cib->type = cib_no_connection; + + return pcmk_ok; +} + +static int +cib_native_signon_raw(cib_t *cib, const char *name, enum cib_conn_type type, + int *async_fd) { int rc = pcmk_ok; const char *channel = NULL; cib_native_opaque_t *native = cib->variant_opaque; struct ipc_client_callbacks cib_callbacks = { .dispatch = cib_native_dispatch_internal, .destroy = cib_native_destroy }; cib->call_timeout = PCMK__IPC_TIMEOUT; if (type == cib_command) { cib->state = cib_connected_command; channel = PCMK__SERVER_BASED_RW; } else if (type == cib_command_nonblocking) { cib->state = cib_connected_command; channel = PCMK__SERVER_BASED_SHM; } else if (type == cib_query) { cib->state = cib_connected_query; channel = PCMK__SERVER_BASED_RO; } else { return -ENOTCONN; } crm_trace("Connecting %s channel", channel); if (async_fd != NULL) { native->ipc = crm_ipc_new(channel, 0); if (native->ipc && crm_ipc_connect(native->ipc)) { *async_fd = crm_ipc_get_fd(native->ipc); } else if (native->ipc) { rc = -ENOTCONN; } } else { native->source = mainloop_add_ipc_client(channel, G_PRIORITY_HIGH, 512 * 1024 /* 512k */ , cib, &cib_callbacks); native->ipc = mainloop_get_ipc_client(native->source); } if (rc != pcmk_ok || native->ipc == NULL || !crm_ipc_connected(native->ipc)) { crm_info("Could not connect to CIB manager for %s", name); rc = -ENOTCONN; } if (rc == pcmk_ok) { xmlNode *reply = NULL; xmlNode *hello = create_xml_node(NULL, "cib_command"); crm_xml_add(hello, F_TYPE, T_CIB); crm_xml_add(hello, F_CIB_OPERATION, CRM_OP_REGISTER); crm_xml_add(hello, F_CIB_CLIENTNAME, name); crm_xml_add_int(hello, F_CIB_CALLOPTS, cib_sync_call); if (crm_ipc_send(native->ipc, hello, crm_ipc_client_response, -1, &reply) > 0) { const char *msg_type = crm_element_value(reply, F_CIB_OPERATION); rc = pcmk_ok; crm_log_xml_trace(reply, "reg-reply"); if (!pcmk__str_eq(msg_type, CRM_OP_REGISTER, pcmk__str_casei)) { crm_info("Reply to CIB registration message has " "unknown type '%s'", msg_type); rc = -EPROTO; } else { native->token = crm_element_value_copy(reply, F_CIB_CLIENTID); if (native->token == NULL) { rc = -EPROTO; } } free_xml(reply); } else { rc = -ECOMM; } free_xml(hello); } if (rc == pcmk_ok) { crm_info("Successfully connected to CIB manager for %s", name); return pcmk_ok; } crm_info("Connection to CIB manager for %s failed: %s", name, pcmk_strerror(rc)); cib_native_signoff(cib); return rc; } -int -cib_native_signoff(cib_t * cib) +static int +cib_native_signon(cib_t *cib, const char *name, enum cib_conn_type type) { - cib_native_opaque_t *native = cib->variant_opaque; - - crm_debug("Disconnecting from the CIB manager"); - - cib_free_notify(cib); - remove_cib_op_callback(0, TRUE); - - if (native->source != NULL) { - /* Attached to mainloop */ - mainloop_del_ipc_client(native->source); - native->source = NULL; - native->ipc = NULL; - - } else if (native->ipc) { - /* Not attached to mainloop */ - crm_ipc_t *ipc = native->ipc; - - native->ipc = NULL; - crm_ipc_close(ipc); - crm_ipc_destroy(ipc); - } - - cib->state = cib_disconnected; - cib->type = cib_no_connection; - - return pcmk_ok; + return cib_native_signon_raw(cib, name, type, NULL); } -int -cib_native_free(cib_t * cib) +static int +cib_native_free(cib_t *cib) { int rc = pcmk_ok; if (cib->state != cib_disconnected) { rc = cib_native_signoff(cib); } if (cib->state == cib_disconnected) { cib_native_opaque_t *native = cib->variant_opaque; free(native->token); free(cib->variant_opaque); free(cib->cmds); free(cib); } return rc; } -int -cib_native_perform_op_delegate(cib_t * cib, const char *op, const char *host, const char *section, - xmlNode * data, xmlNode ** output_data, int call_options, - const char *user_name) +static int +cib_native_register_notification(cib_t *cib, const char *callback, int enabled) { int rc = pcmk_ok; - int reply_id = 0; - enum crm_ipc_flags ipc_flags = crm_ipc_flags_none; - - xmlNode *op_msg = NULL; - xmlNode *op_reply = NULL; - + xmlNode *notify_msg = create_xml_node(NULL, "cib-callback"); cib_native_opaque_t *native = cib->variant_opaque; - if (cib->state == cib_disconnected) { - return -ENOTCONN; - } - - if (output_data != NULL) { - *output_data = NULL; - } - - if (op == NULL) { - crm_err("No operation specified"); - return -EINVAL; + if (cib->state != cib_disconnected) { + crm_xml_add(notify_msg, F_CIB_OPERATION, T_CIB_NOTIFY); + crm_xml_add(notify_msg, F_CIB_NOTIFY_TYPE, callback); + crm_xml_add_int(notify_msg, F_CIB_NOTIFY_ACTIVATE, enabled); + rc = crm_ipc_send(native->ipc, notify_msg, crm_ipc_client_response, + 1000 * cib->call_timeout, NULL); + if (rc <= 0) { + crm_trace("Notification not registered: %d", rc); + rc = -ECOMM; + } } - if (call_options & cib_sync_call) { - pcmk__set_ipc_flags(ipc_flags, "client", crm_ipc_client_response); - } + free_xml(notify_msg); + return rc; +} - cib->call_id++; - if (cib->call_id < 1) { - cib->call_id = 1; - } +static int +cib_native_set_connection_dnotify(cib_t *cib, + void (*dnotify) (gpointer user_data)) +{ + cib_native_opaque_t *native = NULL; - op_msg = cib_create_op(cib->call_id, op, host, section, data, call_options, - user_name); - if (op_msg == NULL) { - return -EPROTO; + if (cib == NULL) { + crm_err("No CIB!"); + return FALSE; } - crm_trace("Sending %s message to the CIB manager (timeout=%ds)", op, cib->call_timeout); - rc = crm_ipc_send(native->ipc, op_msg, ipc_flags, cib->call_timeout * 1000, &op_reply); - free_xml(op_msg); + native = cib->variant_opaque; + native->dnotify_fn = dnotify; - if (rc < 0) { - crm_err("Couldn't perform %s operation (timeout=%ds): %s (%d)", op, - cib->call_timeout, pcmk_strerror(rc), rc); - rc = -ECOMM; - goto done; - } + return pcmk_ok; +} - crm_log_xml_trace(op_reply, "Reply"); +/*! + * \internal + * \brief Get the given CIB connection's unique client identifier + * + * These can be used to check whether this client requested the action that + * triggered a CIB notification. + * + * \param[in] cib CIB connection + * \param[out] async_id If not \p NULL, where to store asynchronous client ID + * \param[out] sync_id If not \p NULL, where to store synchronous client ID + * + * \return Legacy Pacemaker return code (specifically, \p pcmk_ok) + * + * \note This is the \p cib_native variant implementation of + * \p cib_api_operations_t:client_id(). + * \note For \p cib_native objects, \p async_id and \p sync_id are the same. + * \note The client ID is assigned during CIB sign-on. + */ +static int +cib_native_client_id(const cib_t *cib, const char **async_id, + const char **sync_id) +{ + cib_native_opaque_t *native = cib->variant_opaque; - if (!(call_options & cib_sync_call)) { - crm_trace("Async call, returning %d", cib->call_id); - CRM_CHECK(cib->call_id != 0, return -ENOMSG); - free_xml(op_reply); - return cib->call_id; + if (async_id != NULL) { + *async_id = native->token; } - - rc = pcmk_ok; - crm_element_value_int(op_reply, F_CIB_CALLID, &reply_id); - if (reply_id == cib->call_id) { - xmlNode *tmp = get_message_xml(op_reply, F_CIB_CALLDATA); - - crm_trace("Synchronous reply %d received", reply_id); - if (crm_element_value_int(op_reply, F_CIB_RC, &rc) != 0) { - rc = -EPROTO; - } - - if (output_data == NULL || (call_options & cib_discard_reply)) { - crm_trace("Discarding reply"); - - } else if (tmp != NULL) { - *output_data = copy_xml(tmp); - } - - } else if (reply_id <= 0) { - crm_err("Received bad reply: No id set"); - crm_log_xml_err(op_reply, "Bad reply"); - rc = -ENOMSG; - goto done; - - } else { - crm_err("Received bad reply: %d (wanted %d)", reply_id, cib->call_id); - crm_log_xml_err(op_reply, "Old reply"); - rc = -ENOMSG; - goto done; + if (sync_id != NULL) { + *sync_id = native->token; } + return pcmk_ok; +} - if (op_reply == NULL && cib->state == cib_disconnected) { - rc = -ENOTCONN; +cib_t * +cib_native_new(void) +{ + cib_native_opaque_t *native = NULL; + cib_t *cib = cib_new_variant(); - } else if (rc == pcmk_ok && op_reply == NULL) { - rc = -ETIME; + if (cib == NULL) { + return NULL; } - switch (rc) { - case pcmk_ok: - case -EPERM: - break; - - /* This is an internal value that clients do not and should not care about */ - case -pcmk_err_diff_resync: - rc = pcmk_ok; - break; - - /* These indicate internal problems */ - case -EPROTO: - case -ENOMSG: - crm_err("Call failed: %s", pcmk_strerror(rc)); - if (op_reply) { - crm_log_xml_err(op_reply, "Invalid reply"); - } - break; + native = calloc(1, sizeof(cib_native_opaque_t)); - default: - if (!pcmk__str_eq(op, PCMK__CIB_REQUEST_QUERY, pcmk__str_none)) { - crm_warn("Call failed: %s", pcmk_strerror(rc)); - } + if (native == NULL) { + free(cib); + return NULL; } - done: - if (!crm_ipc_connected(native->ipc)) { - crm_err("The CIB manager disconnected"); - cib->state = cib_disconnected; - } + cib->variant = cib_native; + cib->variant_opaque = native; - free_xml(op_reply); - return rc; -} + native->ipc = NULL; + native->source = NULL; + native->dnotify_fn = NULL; -int -cib_native_set_connection_dnotify(cib_t * cib, void (*dnotify) (gpointer user_data)) -{ - cib_native_opaque_t *native = NULL; + /* assign variant specific ops */ + cib->delegate_fn = cib_native_perform_op_delegate; + cib->cmds->signon = cib_native_signon; + cib->cmds->signon_raw = cib_native_signon_raw; + cib->cmds->signoff = cib_native_signoff; + cib->cmds->free = cib_native_free; - if (cib == NULL) { - crm_err("No CIB!"); - return FALSE; - } + cib->cmds->register_notification = cib_native_register_notification; + cib->cmds->set_connection_dnotify = cib_native_set_connection_dnotify; - native = cib->variant_opaque; - native->dnotify_fn = dnotify; + cib->cmds->client_id = cib_native_client_id; - return pcmk_ok; + return cib; } diff --git a/lib/cib/cib_remote.c b/lib/cib/cib_remote.c index 42e16ad7ee..28095b33dc 100644 --- a/lib/cib/cib_remote.c +++ b/lib/cib/cib_remote.c @@ -1,649 +1,638 @@ /* * Copyright 2008-2023 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef HAVE_GNUTLS_GNUTLS_H # include # define TLS_HANDSHAKE_TIMEOUT_MS 5000 static gnutls_anon_client_credentials_t anon_cred_c; static gboolean remote_gnutls_credentials_init = FALSE; #endif // HAVE_GNUTLS_GNUTLS_H #include typedef struct cib_remote_opaque_s { int port; char *server; char *user; char *passwd; gboolean encrypted; pcmk__remote_t command; pcmk__remote_t callback; pcmk__output_t *out; } cib_remote_opaque_t; -void cib_remote_connection_destroy(gpointer user_data); -int cib_remote_callback_dispatch(gpointer user_data); -int cib_remote_command_dispatch(gpointer user_data); -int cib_remote_signon(cib_t * cib, const char *name, enum cib_conn_type type); -int cib_remote_signoff(cib_t * cib); -int cib_remote_free(cib_t * cib); - -int cib_remote_perform_op(cib_t * cib, const char *op, const char *host, const char *section, - xmlNode * data, xmlNode ** output_data, int call_options, - const char *name); - static int -cib_remote_inputfd(cib_t * cib) +cib_remote_perform_op(cib_t *cib, const char *op, const char *host, + const char *section, xmlNode *data, + xmlNode **output_data, int call_options, const char *name) { + int rc; + int remaining_time = 0; + time_t start_time; + + xmlNode *op_msg = NULL; + xmlNode *op_reply = NULL; + cib_remote_opaque_t *private = cib->variant_opaque; - return private->callback.tcp_socket; -} + if (cib->state == cib_disconnected) { + return -ENOTCONN; + } -static int -cib_remote_set_connection_dnotify(cib_t * cib, void (*dnotify) (gpointer user_data)) -{ - return -EPROTONOSUPPORT; -} + if (output_data != NULL) { + *output_data = NULL; + } -static int -cib_remote_register_notification(cib_t * cib, const char *callback, int enabled) -{ - xmlNode *notify_msg = create_xml_node(NULL, "cib_command"); - cib_remote_opaque_t *private = cib->variant_opaque; + if (op == NULL) { + crm_err("No operation specified"); + return -EINVAL; + } - crm_xml_add(notify_msg, F_CIB_OPERATION, T_CIB_NOTIFY); - crm_xml_add(notify_msg, F_CIB_NOTIFY_TYPE, callback); - crm_xml_add_int(notify_msg, F_CIB_NOTIFY_ACTIVATE, enabled); - pcmk__remote_send_xml(&private->callback, notify_msg); - free_xml(notify_msg); - return pcmk_ok; -} + cib->call_id++; + if (cib->call_id < 1) { + cib->call_id = 1; + } -/*! - * \internal - * \brief Get the given CIB connection's unique client identifiers - * - * These can be used to check whether this client requested the action that - * triggered a CIB notification. - * - * \param[in] cib CIB connection - * \param[out] async_id If not \p NULL, where to store asynchronous client ID - * \param[out] sync_id If not \p NULL, where to store synchronous client ID - * - * \return Legacy Pacemaker return code (specifically, \p pcmk_ok) - * - * \note This is the \p cib_remote variant implementation of - * \p cib_api_operations_t:client_id(). - * \note The client IDs are assigned during CIB sign-on. - */ -static int -cib_remote_client_id(const cib_t *cib, const char **async_id, - const char **sync_id) -{ - cib_remote_opaque_t *private = cib->variant_opaque; + op_msg = cib_create_op(cib->call_id, op, host, section, data, call_options, + NULL); + if (op_msg == NULL) { + return -EPROTO; + } - if (async_id != NULL) { - // private->callback is the channel for async requests - *async_id = private->callback.token; + crm_trace("Sending %s message to the CIB manager", op); + if (!(call_options & cib_sync_call)) { + pcmk__remote_send_xml(&private->callback, op_msg); + } else { + pcmk__remote_send_xml(&private->command, op_msg); } - if (sync_id != NULL) { - // private->command is the channel for sync requests - *sync_id = private->command.token; + free_xml(op_msg); + + if ((call_options & cib_discard_reply)) { + crm_trace("Discarding reply"); + return pcmk_ok; + + } else if (!(call_options & cib_sync_call)) { + return cib->call_id; } - return pcmk_ok; -} -cib_t * -cib_remote_new(const char *server, const char *user, const char *passwd, int port, - gboolean encrypted) -{ - cib_remote_opaque_t *private = NULL; - cib_t *cib = cib_new_variant(); + crm_trace("Waiting for a synchronous reply"); - if (cib == NULL) { - return NULL; + start_time = time(NULL); + remaining_time = cib->call_timeout ? cib->call_timeout : 60; + + rc = pcmk_rc_ok; + while (remaining_time > 0 && (rc != ENOTCONN)) { + int reply_id = -1; + int msg_id = cib->call_id; + + rc = pcmk__read_remote_message(&private->command, + remaining_time * 1000); + op_reply = pcmk__remote_message_xml(&private->command); + + if (!op_reply) { + break; + } + + crm_element_value_int(op_reply, F_CIB_CALLID, &reply_id); + + if (reply_id == msg_id) { + break; + + } else if (reply_id < msg_id) { + crm_debug("Received old reply: %d (wanted %d)", reply_id, msg_id); + crm_log_xml_trace(op_reply, "Old reply"); + + } else if ((reply_id - 10000) > msg_id) { + /* wrap-around case */ + crm_debug("Received old reply: %d (wanted %d)", reply_id, msg_id); + crm_log_xml_trace(op_reply, "Old reply"); + } else { + crm_err("Received a __future__ reply:" " %d (wanted %d)", reply_id, msg_id); + } + + free_xml(op_reply); + op_reply = NULL; + + /* wasn't the right reply, try and read some more */ + remaining_time = time(NULL) - start_time; } - private = calloc(1, sizeof(cib_remote_opaque_t)); + /* if(IPC_ISRCONN(native->command_channel) == FALSE) { */ + /* crm_err("The CIB manager disconnected: %d", */ + /* native->command_channel->ch_status); */ + /* cib->state = cib_disconnected; */ + /* } */ - if (private == NULL) { - free(cib); - return NULL; + if (rc == ENOTCONN) { + crm_err("Disconnected while waiting for reply."); + return -ENOTCONN; + } else if (op_reply == NULL) { + crm_err("No reply message - empty"); + return -ENOMSG; } - cib->variant = cib_remote; - cib->variant_opaque = private; + crm_trace("Synchronous reply received"); - pcmk__str_update(&private->server, server); - pcmk__str_update(&private->user, user); - pcmk__str_update(&private->passwd, passwd); + /* Start processing the reply... */ + if (crm_element_value_int(op_reply, F_CIB_RC, &rc) != 0) { + rc = -EPROTO; + } - private->port = port; - private->encrypted = encrypted; + if (rc == -pcmk_err_diff_resync) { + /* This is an internal value that clients do not and should not care about */ + rc = pcmk_ok; + } - /* assign variant specific ops */ - cib->delegate_fn = cib_remote_perform_op; - cib->cmds->signon = cib_remote_signon; - cib->cmds->signoff = cib_remote_signoff; - cib->cmds->free = cib_remote_free; - cib->cmds->inputfd = cib_remote_inputfd; + if (rc == pcmk_ok || rc == -EPERM) { + crm_log_xml_debug(op_reply, "passed"); - cib->cmds->register_notification = cib_remote_register_notification; - cib->cmds->set_connection_dnotify = cib_remote_set_connection_dnotify; + } else { +/* } else if(rc == -ETIME) { */ + crm_err("Call failed: %s", pcmk_strerror(rc)); + crm_log_xml_warn(op_reply, "failed"); + } - cib->cmds->client_id = cib_remote_client_id; + if (output_data == NULL) { + /* do nothing more */ - return cib; + } else if (!(call_options & cib_discard_reply)) { + xmlNode *tmp = get_message_xml(op_reply, F_CIB_CALLDATA); + + if (tmp == NULL) { + crm_trace("No output in reply to \"%s\" command %d", op, cib->call_id - 1); + } else { + *output_data = copy_xml(tmp); + } + } + + free_xml(op_reply); + + return rc; +} + +static int +cib_remote_callback_dispatch(gpointer user_data) +{ + int rc; + cib_t *cib = user_data; + cib_remote_opaque_t *private = cib->variant_opaque; + + xmlNode *msg = NULL; + + crm_info("Message on callback channel"); + + rc = pcmk__read_remote_message(&private->callback, -1); + + msg = pcmk__remote_message_xml(&private->callback); + while (msg) { + const char *type = crm_element_value(msg, F_TYPE); + + crm_trace("Activating %s callbacks...", type); + + if (pcmk__str_eq(type, T_CIB, pcmk__str_casei)) { + cib_native_callback(cib, msg, 0, 0); + + } else if (pcmk__str_eq(type, T_CIB_NOTIFY, pcmk__str_casei)) { + g_list_foreach(cib->notify_list, cib_native_notify, msg); + + } else { + crm_err("Unknown message type: %s", type); + } + + free_xml(msg); + msg = pcmk__remote_message_xml(&private->callback); + } + + if (rc == ENOTCONN) { + return -1; + } + + return 0; +} + +static int +cib_remote_command_dispatch(gpointer user_data) +{ + int rc; + cib_t *cib = user_data; + cib_remote_opaque_t *private = cib->variant_opaque; + + rc = pcmk__read_remote_message(&private->command, -1); + + free(private->command.buffer); + private->command.buffer = NULL; + crm_err("received late reply for remote cib connection, discarding"); + + if (rc == ENOTCONN) { + return -1; + } + return 0; } static int -cib_tls_close(cib_t * cib) +cib_tls_close(cib_t *cib) { cib_remote_opaque_t *private = cib->variant_opaque; #ifdef HAVE_GNUTLS_GNUTLS_H if (private->encrypted) { if (private->command.tls_session) { gnutls_bye(*(private->command.tls_session), GNUTLS_SHUT_RDWR); gnutls_deinit(*(private->command.tls_session)); gnutls_free(private->command.tls_session); } if (private->callback.tls_session) { gnutls_bye(*(private->callback.tls_session), GNUTLS_SHUT_RDWR); gnutls_deinit(*(private->callback.tls_session)); gnutls_free(private->callback.tls_session); } private->command.tls_session = NULL; private->callback.tls_session = NULL; if (remote_gnutls_credentials_init) { gnutls_anon_free_client_credentials(anon_cred_c); gnutls_global_deinit(); remote_gnutls_credentials_init = FALSE; } } #endif if (private->command.tcp_socket) { shutdown(private->command.tcp_socket, SHUT_RDWR); /* no more receptions */ close(private->command.tcp_socket); } if (private->callback.tcp_socket) { shutdown(private->callback.tcp_socket, SHUT_RDWR); /* no more receptions */ close(private->callback.tcp_socket); } private->command.tcp_socket = 0; private->callback.tcp_socket = 0; free(private->command.buffer); free(private->callback.buffer); private->command.buffer = NULL; private->callback.buffer = NULL; return 0; } +static void +cib_remote_connection_destroy(gpointer user_data) +{ + crm_err("Connection destroyed"); +#ifdef HAVE_GNUTLS_GNUTLS_H + cib_tls_close(user_data); +#endif +} + static int cib_tls_signon(cib_t *cib, pcmk__remote_t *connection, gboolean event_channel) { cib_remote_opaque_t *private = cib->variant_opaque; int rc; xmlNode *answer = NULL; xmlNode *login = NULL; static struct mainloop_fd_callbacks cib_fd_callbacks = { 0, }; cib_fd_callbacks.dispatch = event_channel ? cib_remote_callback_dispatch : cib_remote_command_dispatch; cib_fd_callbacks.destroy = cib_remote_connection_destroy; connection->tcp_socket = -1; #ifdef HAVE_GNUTLS_GNUTLS_H connection->tls_session = NULL; #endif rc = pcmk__connect_remote(private->server, private->port, 0, NULL, &(connection->tcp_socket), NULL, NULL); if (rc != pcmk_rc_ok) { crm_info("Remote connection to %s:%d failed: %s " CRM_XS " rc=%d", private->server, private->port, pcmk_rc_str(rc), rc); return -ENOTCONN; } if (private->encrypted) { /* initialize GnuTls lib */ #ifdef HAVE_GNUTLS_GNUTLS_H if (remote_gnutls_credentials_init == FALSE) { crm_gnutls_global_init(); gnutls_anon_allocate_client_credentials(&anon_cred_c); remote_gnutls_credentials_init = TRUE; } /* bind the socket to GnuTls lib */ connection->tls_session = pcmk__new_tls_session(connection->tcp_socket, GNUTLS_CLIENT, GNUTLS_CRD_ANON, anon_cred_c); if (connection->tls_session == NULL) { cib_tls_close(cib); return -1; } if (pcmk__tls_client_handshake(connection, TLS_HANDSHAKE_TIMEOUT_MS) != pcmk_rc_ok) { crm_err("Session creation for %s:%d failed", private->server, private->port); gnutls_deinit(*connection->tls_session); gnutls_free(connection->tls_session); connection->tls_session = NULL; cib_tls_close(cib); return -1; } #else return -EPROTONOSUPPORT; #endif } - /* login to server */ - login = create_xml_node(NULL, "cib_command"); - crm_xml_add(login, "op", "authenticate"); - crm_xml_add(login, "user", private->user); - crm_xml_add(login, "password", private->passwd); - crm_xml_add(login, "hidden", "password"); - - pcmk__remote_send_xml(connection, login); - free_xml(login); - - rc = pcmk_ok; - if (pcmk__read_remote_message(connection, -1) == ENOTCONN) { - rc = -ENOTCONN; - } - - answer = pcmk__remote_message_xml(connection); - - crm_log_xml_trace(answer, "Reply"); - if (answer == NULL) { - rc = -EPROTO; - - } else { - /* grab the token */ - const char *msg_type = crm_element_value(answer, F_CIB_OPERATION); - const char *tmp_ticket = crm_element_value(answer, F_CIB_CLIENTID); - - if (!pcmk__str_eq(msg_type, CRM_OP_REGISTER, pcmk__str_casei)) { - crm_err("Invalid registration message: %s", msg_type); - rc = -EPROTO; - - } else if (tmp_ticket == NULL) { - rc = -EPROTO; - - } else { - connection->token = strdup(tmp_ticket); - } - } - free_xml(answer); - answer = NULL; - - if (rc != 0) { - cib_tls_close(cib); - return rc; - } - - crm_trace("remote client connection established"); - connection->source = mainloop_add_fd("cib-remote", G_PRIORITY_HIGH, - connection->tcp_socket, cib, - &cib_fd_callbacks); - return rc; -} - -void -cib_remote_connection_destroy(gpointer user_data) -{ - crm_err("Connection destroyed"); -#ifdef HAVE_GNUTLS_GNUTLS_H - cib_tls_close(user_data); -#endif - return; -} - -int -cib_remote_command_dispatch(gpointer user_data) -{ - int rc; - cib_t *cib = user_data; - cib_remote_opaque_t *private = cib->variant_opaque; - - rc = pcmk__read_remote_message(&private->command, -1); - - free(private->command.buffer); - private->command.buffer = NULL; - crm_err("received late reply for remote cib connection, discarding"); - - if (rc == ENOTCONN) { - return -1; - } - return 0; -} - -int -cib_remote_callback_dispatch(gpointer user_data) -{ - int rc; - cib_t *cib = user_data; - cib_remote_opaque_t *private = cib->variant_opaque; + /* login to server */ + login = create_xml_node(NULL, "cib_command"); + crm_xml_add(login, "op", "authenticate"); + crm_xml_add(login, "user", private->user); + crm_xml_add(login, "password", private->passwd); + crm_xml_add(login, "hidden", "password"); - xmlNode *msg = NULL; + pcmk__remote_send_xml(connection, login); + free_xml(login); - crm_info("Message on callback channel"); + rc = pcmk_ok; + if (pcmk__read_remote_message(connection, -1) == ENOTCONN) { + rc = -ENOTCONN; + } - rc = pcmk__read_remote_message(&private->callback, -1); + answer = pcmk__remote_message_xml(connection); - msg = pcmk__remote_message_xml(&private->callback); - while (msg) { - const char *type = crm_element_value(msg, F_TYPE); + crm_log_xml_trace(answer, "Reply"); + if (answer == NULL) { + rc = -EPROTO; - crm_trace("Activating %s callbacks...", type); + } else { + /* grab the token */ + const char *msg_type = crm_element_value(answer, F_CIB_OPERATION); + const char *tmp_ticket = crm_element_value(answer, F_CIB_CLIENTID); - if (pcmk__str_eq(type, T_CIB, pcmk__str_casei)) { - cib_native_callback(cib, msg, 0, 0); + if (!pcmk__str_eq(msg_type, CRM_OP_REGISTER, pcmk__str_casei)) { + crm_err("Invalid registration message: %s", msg_type); + rc = -EPROTO; - } else if (pcmk__str_eq(type, T_CIB_NOTIFY, pcmk__str_casei)) { - g_list_foreach(cib->notify_list, cib_native_notify, msg); + } else if (tmp_ticket == NULL) { + rc = -EPROTO; } else { - crm_err("Unknown message type: %s", type); + connection->token = strdup(tmp_ticket); } - - free_xml(msg); - msg = pcmk__remote_message_xml(&private->callback); } + free_xml(answer); + answer = NULL; - if (rc == ENOTCONN) { - return -1; + if (rc != 0) { + cib_tls_close(cib); + return rc; } - return 0; + crm_trace("remote client connection established"); + connection->source = mainloop_add_fd("cib-remote", G_PRIORITY_HIGH, + connection->tcp_socket, cib, + &cib_fd_callbacks); + return rc; } -int -cib_remote_signon(cib_t * cib, const char *name, enum cib_conn_type type) +static int +cib_remote_signon(cib_t *cib, const char *name, enum cib_conn_type type) { int rc = pcmk_ok; cib_remote_opaque_t *private = cib->variant_opaque; if (private->passwd == NULL) { if (private->out == NULL) { /* If no pcmk__output_t is set, just assume that a text prompt * is good enough. */ pcmk__text_prompt("Password", false, &(private->passwd)); } else { private->out->prompt("Password", false, &(private->passwd)); } } if (private->server == NULL || private->user == NULL) { rc = -EINVAL; } if (rc == pcmk_ok) { rc = cib_tls_signon(cib, &(private->command), FALSE); } if (rc == pcmk_ok) { rc = cib_tls_signon(cib, &(private->callback), TRUE); } if (rc == pcmk_ok) { xmlNode *hello = cib_create_op(0, CRM_OP_REGISTER, NULL, NULL, NULL, 0, NULL); crm_xml_add(hello, F_CIB_CLIENTNAME, name); pcmk__remote_send_xml(&private->command, hello); free_xml(hello); } if (rc == pcmk_ok) { crm_info("Opened connection to %s:%d for %s", private->server, private->port, name); cib->state = cib_connected_command; cib->type = cib_command; } else { crm_info("Connection to %s:%d for %s failed: %s\n", private->server, private->port, name, pcmk_strerror(rc)); } return rc; } -int -cib_remote_signoff(cib_t * cib) +static int +cib_remote_signoff(cib_t *cib) { int rc = pcmk_ok; crm_debug("Disconnecting from the CIB manager"); #ifdef HAVE_GNUTLS_GNUTLS_H cib_tls_close(cib); #endif cib->state = cib_disconnected; cib->type = cib_no_connection; return rc; } -int -cib_remote_free(cib_t * cib) +static int +cib_remote_free(cib_t *cib) { int rc = pcmk_ok; crm_warn("Freeing CIB"); if (cib->state != cib_disconnected) { rc = cib_remote_signoff(cib); if (rc == pcmk_ok) { cib_remote_opaque_t *private = cib->variant_opaque; free(private->server); free(private->user); free(private->passwd); free(cib->cmds); free(private); free(cib); } } return rc; } -int -cib_remote_perform_op(cib_t * cib, const char *op, const char *host, const char *section, - xmlNode * data, xmlNode ** output_data, int call_options, const char *name) +static int +cib_remote_inputfd(cib_t * cib) { - int rc; - int remaining_time = 0; - time_t start_time; - - xmlNode *op_msg = NULL; - xmlNode *op_reply = NULL; - cib_remote_opaque_t *private = cib->variant_opaque; - if (cib->state == cib_disconnected) { - return -ENOTCONN; - } - - if (output_data != NULL) { - *output_data = NULL; - } - - if (op == NULL) { - crm_err("No operation specified"); - return -EINVAL; - } + return private->callback.tcp_socket; +} - cib->call_id++; - if (cib->call_id < 1) { - cib->call_id = 1; - } +static int +cib_remote_register_notification(cib_t * cib, const char *callback, int enabled) +{ + xmlNode *notify_msg = create_xml_node(NULL, "cib_command"); + cib_remote_opaque_t *private = cib->variant_opaque; - op_msg = cib_create_op(cib->call_id, op, host, section, data, call_options, - NULL); - if (op_msg == NULL) { - return -EPROTO; - } + crm_xml_add(notify_msg, F_CIB_OPERATION, T_CIB_NOTIFY); + crm_xml_add(notify_msg, F_CIB_NOTIFY_TYPE, callback); + crm_xml_add_int(notify_msg, F_CIB_NOTIFY_ACTIVATE, enabled); + pcmk__remote_send_xml(&private->callback, notify_msg); + free_xml(notify_msg); + return pcmk_ok; +} - crm_trace("Sending %s message to the CIB manager", op); - if (!(call_options & cib_sync_call)) { - pcmk__remote_send_xml(&private->callback, op_msg); - } else { - pcmk__remote_send_xml(&private->command, op_msg); - } - free_xml(op_msg); +static int +cib_remote_set_connection_dnotify(cib_t * cib, void (*dnotify) (gpointer user_data)) +{ + return -EPROTONOSUPPORT; +} - if ((call_options & cib_discard_reply)) { - crm_trace("Discarding reply"); - return pcmk_ok; +/*! + * \internal + * \brief Get the given CIB connection's unique client identifiers + * + * These can be used to check whether this client requested the action that + * triggered a CIB notification. + * + * \param[in] cib CIB connection + * \param[out] async_id If not \p NULL, where to store asynchronous client ID + * \param[out] sync_id If not \p NULL, where to store synchronous client ID + * + * \return Legacy Pacemaker return code (specifically, \p pcmk_ok) + * + * \note This is the \p cib_remote variant implementation of + * \p cib_api_operations_t:client_id(). + * \note The client IDs are assigned during CIB sign-on. + */ +static int +cib_remote_client_id(const cib_t *cib, const char **async_id, + const char **sync_id) +{ + cib_remote_opaque_t *private = cib->variant_opaque; - } else if (!(call_options & cib_sync_call)) { - return cib->call_id; + if (async_id != NULL) { + // private->callback is the channel for async requests + *async_id = private->callback.token; } - - crm_trace("Waiting for a synchronous reply"); - - start_time = time(NULL); - remaining_time = cib->call_timeout ? cib->call_timeout : 60; - - rc = pcmk_rc_ok; - while (remaining_time > 0 && (rc != ENOTCONN)) { - int reply_id = -1; - int msg_id = cib->call_id; - - rc = pcmk__read_remote_message(&private->command, - remaining_time * 1000); - op_reply = pcmk__remote_message_xml(&private->command); - - if (!op_reply) { - break; - } - - crm_element_value_int(op_reply, F_CIB_CALLID, &reply_id); - - if (reply_id == msg_id) { - break; - - } else if (reply_id < msg_id) { - crm_debug("Received old reply: %d (wanted %d)", reply_id, msg_id); - crm_log_xml_trace(op_reply, "Old reply"); - - } else if ((reply_id - 10000) > msg_id) { - /* wrap-around case */ - crm_debug("Received old reply: %d (wanted %d)", reply_id, msg_id); - crm_log_xml_trace(op_reply, "Old reply"); - } else { - crm_err("Received a __future__ reply:" " %d (wanted %d)", reply_id, msg_id); - } - - free_xml(op_reply); - op_reply = NULL; - - /* wasn't the right reply, try and read some more */ - remaining_time = time(NULL) - start_time; + if (sync_id != NULL) { + // private->command is the channel for sync requests + *sync_id = private->command.token; } + return pcmk_ok; +} - /* if(IPC_ISRCONN(native->command_channel) == FALSE) { */ - /* crm_err("The CIB manager disconnected: %d", */ - /* native->command_channel->ch_status); */ - /* cib->state = cib_disconnected; */ - /* } */ +cib_t * +cib_remote_new(const char *server, const char *user, const char *passwd, int port, + gboolean encrypted) +{ + cib_remote_opaque_t *private = NULL; + cib_t *cib = cib_new_variant(); - if (rc == ENOTCONN) { - crm_err("Disconnected while waiting for reply."); - return -ENOTCONN; - } else if (op_reply == NULL) { - crm_err("No reply message - empty"); - return -ENOMSG; + if (cib == NULL) { + return NULL; } - crm_trace("Synchronous reply received"); - - /* Start processing the reply... */ - if (crm_element_value_int(op_reply, F_CIB_RC, &rc) != 0) { - rc = -EPROTO; - } + private = calloc(1, sizeof(cib_remote_opaque_t)); - if (rc == -pcmk_err_diff_resync) { - /* This is an internal value that clients do not and should not care about */ - rc = pcmk_ok; + if (private == NULL) { + free(cib); + return NULL; } - if (rc == pcmk_ok || rc == -EPERM) { - crm_log_xml_debug(op_reply, "passed"); + cib->variant = cib_remote; + cib->variant_opaque = private; - } else { -/* } else if(rc == -ETIME) { */ - crm_err("Call failed: %s", pcmk_strerror(rc)); - crm_log_xml_warn(op_reply, "failed"); - } + pcmk__str_update(&private->server, server); + pcmk__str_update(&private->user, user); + pcmk__str_update(&private->passwd, passwd); - if (output_data == NULL) { - /* do nothing more */ + private->port = port; + private->encrypted = encrypted; - } else if (!(call_options & cib_discard_reply)) { - xmlNode *tmp = get_message_xml(op_reply, F_CIB_CALLDATA); + /* assign variant specific ops */ + cib->delegate_fn = cib_remote_perform_op; + cib->cmds->signon = cib_remote_signon; + cib->cmds->signoff = cib_remote_signoff; + cib->cmds->free = cib_remote_free; + cib->cmds->inputfd = cib_remote_inputfd; - if (tmp == NULL) { - crm_trace("No output in reply to \"%s\" command %d", op, cib->call_id - 1); - } else { - *output_data = copy_xml(tmp); - } - } + cib->cmds->register_notification = cib_remote_register_notification; + cib->cmds->set_connection_dnotify = cib_remote_set_connection_dnotify; - free_xml(op_reply); + cib->cmds->client_id = cib_remote_client_id; - return rc; + return cib; } void cib__set_output(cib_t *cib, pcmk__output_t *out) { cib_remote_opaque_t *private; if (cib->variant != cib_remote) { return; } private = cib->variant_opaque; private->out = out; }