diff --git a/daemons/based/pacemaker-based.c b/daemons/based/pacemaker-based.c index 68c533baa4..d3f3cee411 100644 --- a/daemons/based/pacemaker-based.c +++ b/daemons/based/pacemaker-based.c @@ -1,375 +1,372 @@ /* - * Copyright 2004-2020 the Pacemaker project contributors + * Copyright 2004-2021 the Pacemaker project contributors * * The version control history for this file may have further details. * * This source code is licensed under the GNU General Public License version 2 * or later (GPLv2+) WITHOUT ANY WARRANTY. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include extern int init_remote_listener(int port, gboolean encrypted); gboolean cib_shutdown_flag = FALSE; int cib_status = pcmk_ok; crm_cluster_t crm_cluster; GMainLoop *mainloop = NULL; const char *cib_root = NULL; char *cib_our_uname = NULL; static gboolean preserve_status = FALSE; gboolean cib_writes_enabled = TRUE; int remote_fd = 0; int remote_tls_fd = 0; GHashTable *config_hash = NULL; GHashTable *local_notify_queue = NULL; static void cib_init(void); void cib_shutdown(int nsig); static bool startCib(const char *filename); extern int write_cib_contents(gpointer p); void cib_cleanup(void); static void cib_enable_writes(int nsig) { crm_info("(Re)enabling disk writes"); cib_writes_enabled = TRUE; } static pcmk__cli_option_t long_options[] = { // long option, argument type, storage, short option, description, flags { "help", no_argument, 0, '?', "\tThis text", pcmk__option_default }, { "verbose", no_argument, NULL, 'V', "\tIncrease debug output", pcmk__option_default }, { "stand-alone", no_argument, NULL, 's', "\tAdvanced use only", pcmk__option_default }, { "disk-writes", no_argument, NULL, 'w', "\tAdvanced use only", pcmk__option_default }, { "cib-root", required_argument, NULL, 'r', "\tAdvanced use only", pcmk__option_default }, { 0, 0, 0, 0 } }; int main(int argc, char **argv) { int flag; int rc = 0; int index = 0; int argerr = 0; struct passwd *pwentry = NULL; crm_ipc_t *old_instance = NULL; crm_log_preinit(NULL, argc, argv); pcmk__set_cli_options(NULL, "[options]", long_options, "daemon for managing the configuration " "of a Pacemaker cluster"); mainloop_add_signal(SIGTERM, cib_shutdown); mainloop_add_signal(SIGPIPE, cib_enable_writes); cib_writer = mainloop_add_trigger(G_PRIORITY_LOW, write_cib_contents, NULL); while (1) { flag = pcmk__next_cli_option(argc, argv, &index, NULL); if (flag == -1) break; switch (flag) { case 'V': crm_bump_log_level(argc, argv); break; case 's': stand_alone = TRUE; preserve_status = TRUE; cib_writes_enabled = FALSE; pwentry = getpwnam(CRM_DAEMON_USER); CRM_CHECK(pwentry != NULL, crm_perror(LOG_ERR, "Invalid uid (%s) specified", CRM_DAEMON_USER); return CRM_EX_FATAL); rc = setgid(pwentry->pw_gid); if (rc < 0) { crm_perror(LOG_ERR, "Could not set group to %d", pwentry->pw_gid); return CRM_EX_FATAL; } rc = initgroups(CRM_DAEMON_USER, pwentry->pw_gid); if (rc < 0) { crm_perror(LOG_ERR, "Could not setup groups for user %d", pwentry->pw_uid); return CRM_EX_FATAL; } rc = setuid(pwentry->pw_uid); if (rc < 0) { crm_perror(LOG_ERR, "Could not set user to %d", pwentry->pw_uid); return CRM_EX_FATAL; } break; case '?': /* Help message */ pcmk__cli_help(flag, CRM_EX_OK); break; case 'w': cib_writes_enabled = TRUE; break; case 'r': cib_root = optarg; break; case 'm': cib_metadata(); return CRM_EX_OK; default: ++argerr; break; } } if (argc - optind == 1 && pcmk__str_eq("metadata", argv[optind], pcmk__str_casei)) { cib_metadata(); return CRM_EX_OK; } if (optind > argc) { ++argerr; } if (argerr) { pcmk__cli_help('?', CRM_EX_USAGE); } crm_log_init(NULL, LOG_INFO, TRUE, FALSE, argc, argv, FALSE); crm_notice("Starting Pacemaker CIB manager"); old_instance = crm_ipc_new(PCMK__SERVER_BASED_RO, 0); if (crm_ipc_connect(old_instance)) { /* IPC end-point already up */ crm_ipc_close(old_instance); crm_ipc_destroy(old_instance); crm_err("pacemaker-based is already active, aborting startup"); crm_exit(CRM_EX_OK); } else { /* not up or not authentic, we'll proceed either way */ crm_ipc_destroy(old_instance); old_instance = NULL; } if (cib_root == NULL) { cib_root = CRM_CONFIG_DIR; } else { crm_notice("Using custom config location: %s", cib_root); } if (pcmk__daemon_can_write(cib_root, NULL) == FALSE) { crm_err("Terminating due to bad permissions on %s", cib_root); fprintf(stderr, "ERROR: Bad permissions on %s (see logs for details)\n", cib_root); fflush(stderr); return CRM_EX_FATAL; } crm_peer_init(); // Read initial CIB, connect to cluster, and start IPC servers cib_init(); // Run the main loop mainloop = g_main_loop_new(NULL, FALSE); crm_notice("Pacemaker CIB manager successfully started and accepting connections"); g_main_loop_run(mainloop); /* If main loop returned, clean up and exit. We disconnect in case * terminate_cib() was called with fast=-1. */ crm_cluster_disconnect(&crm_cluster); pcmk__stop_based_ipc(ipcs_ro, ipcs_rw, ipcs_shm); crm_exit(CRM_EX_OK); } void cib_cleanup(void) { crm_peer_destroy(); if (local_notify_queue) { g_hash_table_destroy(local_notify_queue); } pcmk__client_cleanup(); g_hash_table_destroy(config_hash); free(cib_our_uname); } #if SUPPORT_COROSYNC static void cib_cs_dispatch(cpg_handle_t handle, const struct cpg_name *groupName, uint32_t nodeid, uint32_t pid, void *msg, size_t msg_len) { uint32_t kind = 0; xmlNode *xml = NULL; const char *from = NULL; char *data = pcmk_message_common_cs(handle, nodeid, pid, msg, &kind, &from); if(data == NULL) { return; } if (kind == crm_class_cluster) { xml = string2xml(data); if (xml == NULL) { crm_err("Invalid XML: '%.120s'", data); free(data); return; } crm_xml_add(xml, F_ORIG, from); /* crm_xml_add_int(xml, F_SEQ, wrapper->id); */ cib_peer_callback(xml, NULL); } free_xml(xml); free(data); } static void cib_cs_destroy(gpointer user_data) { if (cib_shutdown_flag) { crm_info("Corosync disconnection complete"); } else { crm_crit("Lost connection to cluster layer, shutting down"); terminate_cib(__func__, CRM_EX_DISCONNECT); } } #endif static void cib_peer_update_callback(enum crm_status_type type, crm_node_t * node, const void *data) { switch (type) { case crm_status_processes: if (cib_legacy_mode() && !pcmk_is_set(node->processes, crm_get_cluster_proc())) { uint32_t old = data? *(const uint32_t *)data : 0; if ((node->processes ^ old) & crm_proc_cpg) { crm_info("Attempting to disable legacy mode after %s left the cluster", node->uname); legacy_mode = FALSE; } } break; case crm_status_uname: case crm_status_nstate: if (cib_shutdown_flag && (crm_active_peers() < 2) && (pcmk__ipc_client_count() == 0)) { crm_info("No more peers"); terminate_cib(__func__, -1); } break; } } static void cib_init(void) { if (is_corosync_cluster()) { #if SUPPORT_COROSYNC crm_cluster.destroy = cib_cs_destroy; crm_cluster.cpg.cpg_deliver_fn = cib_cs_dispatch; crm_cluster.cpg.cpg_confchg_fn = pcmk_cpg_membership; #endif } config_hash = pcmk__strkey_table(free, free); if (startCib("cib.xml") == FALSE) { crm_crit("Cannot start CIB... terminating"); crm_exit(CRM_EX_NOINPUT); } if (stand_alone == FALSE) { if (is_corosync_cluster()) { crm_set_status_callback(&cib_peer_update_callback); } if (crm_cluster_connect(&crm_cluster) == FALSE) { crm_crit("Cannot sign in to the cluster... terminating"); crm_exit(CRM_EX_FATAL); } cib_our_uname = crm_cluster.uname; } else { cib_our_uname = strdup("localhost"); } pcmk__serve_based_ipc(&ipcs_ro, &ipcs_rw, &ipcs_shm, &ipc_ro_callbacks, &ipc_rw_callbacks); if (stand_alone) { cib_is_master = TRUE; } } static bool startCib(const char *filename) { gboolean active = FALSE; xmlNode *cib = readCibXmlFile(cib_root, filename, !preserve_status); if (activateCibXml(cib, TRUE, "start") == 0) { int port = 0; - const char *port_s = NULL; active = TRUE; cib_read_config(config_hash, cib); - port_s = crm_element_value(cib, "remote-tls-port"); - if (port_s) { - port = crm_parse_int(port_s, "0"); + pcmk__scan_port(crm_element_value(cib, "remote-tls-port"), &port); + if (port >= 0) { remote_tls_fd = init_remote_listener(port, TRUE); } - port_s = crm_element_value(cib, "remote-clear-port"); - if (port_s) { - port = crm_parse_int(port_s, "0"); + pcmk__scan_port(crm_element_value(cib, "remote-clear-port"), &port); + if (port >= 0) { remote_fd = init_remote_listener(port, FALSE); } } return active; } diff --git a/include/crm/common/strings_internal.h b/include/crm/common/strings_internal.h index d71d54e7cc..39dbd7acf1 100644 --- a/include/crm/common/strings_internal.h +++ b/include/crm/common/strings_internal.h @@ -1,174 +1,175 @@ /* * Copyright 2015-2021 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. */ #ifndef PCMK__STRINGS_INTERNAL__H #define PCMK__STRINGS_INTERNAL__H #include // bool #include // guint, GList, GHashTable /* internal constants for generic string functions (from strings.c) */ #define PCMK__PARSE_INT_DEFAULT -1 #define PCMK__PARSE_DBL_DEFAULT -1.0 /* internal generic string functions (from strings.c) */ enum pcmk__str_flags { pcmk__str_none = 0, pcmk__str_casei = 1 << 0, pcmk__str_null_matches = 1 << 1, pcmk__str_regex = 1 << 2 }; int pcmk__scan_double(const char *text, double *result, const char *default_text, char **end_text); int pcmk__guint_from_hash(GHashTable *table, const char *key, guint default_val, guint *result); bool pcmk__starts_with(const char *str, const char *prefix); bool pcmk__ends_with(const char *s, const char *match); bool pcmk__ends_with_ext(const char *s, const char *match); void pcmk__add_separated_word(char **list, size_t *len, const char *word, const char *separator); int pcmk__compress(const char *data, unsigned int length, unsigned int max, char **result, unsigned int *result_len); int pcmk__scan_ll(const char *text, long long *result, long long default_value); +int pcmk__scan_port(const char *text, int *port); int pcmk__parse_ll_range(const char *srcstring, long long *start, long long *end); GHashTable *pcmk__strkey_table(GDestroyNotify key_destroy_func, GDestroyNotify value_destroy_func); GHashTable *pcmk__strikey_table(GDestroyNotify key_destroy_func, GDestroyNotify value_destroy_func); GHashTable *pcmk__str_table_dup(GHashTable *old_table); /*! * \internal * \brief Create a hash table with integer keys * * \param[in] value_destroy_func Function to free a value * * \return Newly allocated hash table * \note It is the caller's responsibility to free the result, using * g_hash_table_destroy(). */ static inline GHashTable * pcmk__intkey_table(GDestroyNotify value_destroy_func) { return g_hash_table_new_full(g_direct_hash, g_direct_equal, NULL, value_destroy_func); } /*! * \internal * \brief Insert a value into a hash table with integer keys * * \param[in,out] hash_table Table to insert into * \param[in] key Integer key to insert * \param[in] value Value to insert * * \return Whether the key/value was already in the table * \note This has the same semantics as g_hash_table_insert(). If the key * already exists in the table, the old value is freed and replaced. */ static inline gboolean pcmk__intkey_table_insert(GHashTable *hash_table, int key, gpointer value) { return g_hash_table_insert(hash_table, GINT_TO_POINTER(key), value); } /*! * \internal * \brief Look up a value in a hash table with integer keys * * \param[in] hash_table Table to check * \param[in] key Integer key to look for * * \return Value in table for \key (or NULL if not found) */ static inline gpointer pcmk__intkey_table_lookup(GHashTable *hash_table, int key) { return g_hash_table_lookup(hash_table, GINT_TO_POINTER(key)); } /*! * \internal * \brief Remove a key/value from a hash table with integer keys * * \param[in] hash_table Table to modify * \param[in] key Integer key of entry to remove * * \return Whether \p key was found and removed from \p hash_table */ static inline gboolean pcmk__intkey_table_remove(GHashTable *hash_table, int key) { return g_hash_table_remove(hash_table, GINT_TO_POINTER(key)); } gboolean pcmk__str_in_list(GList *lst, const gchar *s); bool pcmk__strcase_any_of(const char *s, ...) G_GNUC_NULL_TERMINATED; bool pcmk__str_any_of(const char *s, ...) G_GNUC_NULL_TERMINATED; bool pcmk__char_in_any_str(int ch, ...) G_GNUC_NULL_TERMINATED; int pcmk__strcmp(const char *s1, const char *s2, uint32_t flags); static inline bool pcmk__str_eq(const char *s1, const char *s2, uint32_t flags) { return pcmk__strcmp(s1, s2, flags) == 0; } // Like pcmk__add_separated_word() but using a space as separator static inline void pcmk__add_word(char **list, size_t *len, const char *word) { return pcmk__add_separated_word(list, len, word, " "); } /* Correctly displaying singular or plural is complicated; consider "1 node has" * vs. "2 nodes have". A flexible solution is to pluralize entire strings, e.g. * * if (a == 1) { * crm_info("singular message"): * } else { * crm_info("plural message"); * } * * though even that's not sufficient for all languages besides English (if we * ever desire to do translations of output and log messages). But the following * convenience macros are "good enough" and more concise for many cases. */ /* Example: * crm_info("Found %d %s", nentries, * pcmk__plural_alt(nentries, "entry", "entries")); */ #define pcmk__plural_alt(i, s1, s2) (((i) == 1)? (s1) : (s2)) // Example: crm_info("Found %d node%s", nnodes, pcmk__plural_s(nnodes)); #define pcmk__plural_s(i) pcmk__plural_alt(i, "", "s") static inline int pcmk__str_empty(const char *s) { return (s == NULL) || (s[0] == '\0'); } // note this returns const not allocated static inline const char * pcmk__btoa(bool condition) { return condition? "true" : "false"; } #endif /* PCMK__STRINGS_INTERNAL__H */ diff --git a/lib/cib/cib_client.c b/lib/cib/cib_client.c index 402dbfce39..077e3da1f3 100644 --- a/lib/cib/cib_client.c +++ b/lib/cib/cib_client.c @@ -1,685 +1,692 @@ /* * Copyright 2004-2021 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 GHashTable *cib_op_callback_table = NULL; int cib_client_set_op_callback(cib_t * cib, void (*callback) (const xmlNode * msg, int call_id, int rc, xmlNode * output)); int cib_client_add_notify_callback(cib_t * cib, const char *event, void (*callback) (const char *event, xmlNode * msg)); int cib_client_del_notify_callback(cib_t * cib, const char *event, void (*callback) (const char *event, xmlNode * msg)); gint ciblib_GCompareFunc(gconstpointer a, gconstpointer b); #define op_common(cib) do { \ if(cib == NULL) { \ return -EINVAL; \ } else if(cib->delegate_fn == NULL) { \ return -EPROTONOSUPPORT; \ } \ } while(0) static int cib_client_noop(cib_t * cib, int call_options) { op_common(cib); return cib_internal_op(cib, CRM_OP_NOOP, NULL, NULL, NULL, NULL, call_options, NULL); } static int cib_client_ping(cib_t * cib, xmlNode ** output_data, int call_options) { op_common(cib); return cib_internal_op(cib, CRM_OP_PING, NULL, NULL, NULL, output_data, call_options, NULL); } static int cib_client_query(cib_t * cib, const char *section, xmlNode ** output_data, int call_options) { return cib->cmds->query_from(cib, NULL, section, output_data, call_options); } static int cib_client_query_from(cib_t * cib, const char *host, const char *section, xmlNode ** output_data, int call_options) { op_common(cib); return cib_internal_op(cib, CIB_OP_QUERY, host, section, NULL, output_data, call_options, NULL); } static int cib_client_is_master(cib_t * cib) { op_common(cib); return cib_internal_op(cib, CIB_OP_ISMASTER, NULL, NULL, NULL, NULL, cib_scope_local | cib_sync_call, NULL); } static int cib_client_set_slave(cib_t * cib, int call_options) { op_common(cib); return cib_internal_op(cib, CIB_OP_SLAVE, NULL, NULL, NULL, NULL, call_options, NULL); } static int cib_client_set_slave_all(cib_t * cib, int call_options) { return -EPROTONOSUPPORT; } static int cib_client_set_master(cib_t * cib, int call_options) { op_common(cib); crm_trace("Adding cib_scope_local to options"); return cib_internal_op(cib, CIB_OP_MASTER, NULL, NULL, NULL, NULL, call_options | cib_scope_local, NULL); } static int cib_client_bump_epoch(cib_t * cib, int call_options) { op_common(cib); return cib_internal_op(cib, CIB_OP_BUMP, NULL, NULL, NULL, NULL, call_options, NULL); } static int cib_client_upgrade(cib_t * cib, int call_options) { op_common(cib); return cib_internal_op(cib, CIB_OP_UPGRADE, NULL, NULL, NULL, NULL, call_options, NULL); } static int cib_client_sync(cib_t * cib, const char *section, int call_options) { return cib->cmds->sync_from(cib, NULL, section, call_options); } static int cib_client_sync_from(cib_t * cib, const char *host, const char *section, int call_options) { op_common(cib); return cib_internal_op(cib, CIB_OP_SYNC, host, section, NULL, NULL, call_options, NULL); } static int cib_client_create(cib_t * cib, const char *section, xmlNode * data, int call_options) { op_common(cib); return cib_internal_op(cib, CIB_OP_CREATE, NULL, section, data, NULL, call_options, NULL); } static int cib_client_modify(cib_t * cib, const char *section, xmlNode * data, int call_options) { op_common(cib); return cib_internal_op(cib, CIB_OP_MODIFY, NULL, section, data, NULL, call_options, NULL); } static int cib_client_update(cib_t * cib, const char *section, xmlNode * data, int call_options) { op_common(cib); return cib_internal_op(cib, CIB_OP_MODIFY, NULL, section, data, NULL, call_options, NULL); } static int cib_client_replace(cib_t * cib, const char *section, xmlNode * data, int call_options) { op_common(cib); return cib_internal_op(cib, CIB_OP_REPLACE, NULL, section, data, NULL, call_options, NULL); } static int cib_client_delete(cib_t * cib, const char *section, xmlNode * data, int call_options) { op_common(cib); return cib_internal_op(cib, CIB_OP_DELETE, NULL, section, data, NULL, call_options, NULL); } static int cib_client_delete_absolute(cib_t * cib, const char *section, xmlNode * data, int call_options) { op_common(cib); return cib_internal_op(cib, CIB_OP_DELETE_ALT, NULL, section, data, NULL, call_options, NULL); } static int cib_client_erase(cib_t * cib, xmlNode ** output_data, int call_options) { op_common(cib); return cib_internal_op(cib, CIB_OP_ERASE, NULL, NULL, NULL, output_data, call_options, NULL); } static void cib_destroy_op_callback(gpointer data) { cib_callback_client_t *blob = data; if (blob->timer && blob->timer->ref > 0) { g_source_remove(blob->timer->ref); } free(blob->timer); if (blob->user_data && blob->free_func) { blob->free_func(blob->user_data); } free(blob); } static void destroy_op_callback_table(void) { if (cib_op_callback_table != NULL) { g_hash_table_destroy(cib_op_callback_table); cib_op_callback_table = NULL; } } char * get_shadow_file(const char *suffix) { char *cib_home = NULL; char *fullname = NULL; char *name = crm_strdup_printf("shadow.%s", suffix); const char *dir = getenv("CIB_shadow_dir"); if (dir == NULL) { uid_t uid = geteuid(); struct passwd *pwent = getpwuid(uid); const char *user = NULL; if (pwent) { user = pwent->pw_name; } else { user = getenv("USER"); crm_perror(LOG_ERR, "Assuming %s because cannot get user details for user ID %d", (user? user : "unprivileged user"), uid); } if (pcmk__strcase_any_of(user, "root", CRM_DAEMON_USER, NULL)) { dir = CRM_CONFIG_DIR; } else { const char *home = NULL; if ((home = getenv("HOME")) == NULL) { if (pwent) { home = pwent->pw_dir; } } dir = pcmk__get_tmpdir(); if (home && home[0] == '/') { int rc = 0; cib_home = crm_strdup_printf("%s/.cib", home); rc = mkdir(cib_home, 0700); if (rc < 0 && errno != EEXIST) { crm_perror(LOG_ERR, "Couldn't create user-specific shadow directory: %s", cib_home); errno = 0; } else { dir = cib_home; } } } } fullname = crm_strdup_printf("%s/%s", dir, name); free(cib_home); free(name); return fullname; } cib_t * cib_shadow_new(const char *shadow) { cib_t *new_cib = NULL; char *shadow_file = NULL; CRM_CHECK(shadow != NULL, return NULL); shadow_file = get_shadow_file(shadow); new_cib = cib_file_new(shadow_file); free(shadow_file); return new_cib; } cib_t * cib_new_no_shadow(void) { unsetenv("CIB_shadow"); return cib_new(); } cib_t * cib_new(void) { const char *value = getenv("CIB_shadow"); + int port; if (value && value[0] != 0) { return cib_shadow_new(value); } value = getenv("CIB_file"); if (value) { return cib_file_new(value); } value = getenv("CIB_port"); if (value) { gboolean encrypted = TRUE; - int port = crm_parse_int(value, NULL); const char *server = getenv("CIB_server"); const char *user = getenv("CIB_user"); const char *pass = getenv("CIB_passwd"); + /* We don't ensure port is valid (>= 0) because cib_new() currently + * can't return NULL in practice, and introducing a NULL return here + * could cause core dumps that would previously just cause signon() + * failures. + */ + pcmk__scan_port(value, &port); + value = getenv("CIB_encrypted"); if (value && crm_is_true(value) == FALSE) { crm_info("Disabling TLS"); encrypted = FALSE; } if (user == NULL) { user = CRM_DAEMON_USER; crm_info("Defaulting to user: %s", user); } if (server == NULL) { server = "localhost"; crm_info("Defaulting to localhost"); } return cib_remote_new(server, user, pass, port, encrypted); } return cib_native_new(); } /*! * \internal * \brief Create a generic CIB connection instance * * \return Newly allocated and initialized cib_t instance * * \note This is called by each variant's cib_*_new() function before setting * variant-specific values. */ cib_t * cib_new_variant(void) { cib_t *new_cib = NULL; new_cib = calloc(1, sizeof(cib_t)); remove_cib_op_callback(0, TRUE); /* remove all */ new_cib->call_id = 1; new_cib->variant = cib_undefined; new_cib->type = cib_no_connection; new_cib->state = cib_disconnected; new_cib->op_callback = NULL; new_cib->variant_opaque = NULL; new_cib->notify_list = NULL; /* the rest will get filled in by the variant constructor */ new_cib->cmds = calloc(1, sizeof(cib_api_operations_t)); new_cib->cmds->set_op_callback = cib_client_set_op_callback; new_cib->cmds->add_notify_callback = cib_client_add_notify_callback; new_cib->cmds->del_notify_callback = cib_client_del_notify_callback; new_cib->cmds->register_callback = cib_client_register_callback; new_cib->cmds->register_callback_full = cib_client_register_callback_full; new_cib->cmds->noop = cib_client_noop; new_cib->cmds->ping = cib_client_ping; new_cib->cmds->query = cib_client_query; new_cib->cmds->sync = cib_client_sync; new_cib->cmds->query_from = cib_client_query_from; new_cib->cmds->sync_from = cib_client_sync_from; new_cib->cmds->is_master = cib_client_is_master; new_cib->cmds->set_master = cib_client_set_master; new_cib->cmds->set_slave = cib_client_set_slave; new_cib->cmds->set_slave_all = cib_client_set_slave_all; new_cib->cmds->upgrade = cib_client_upgrade; new_cib->cmds->bump_epoch = cib_client_bump_epoch; new_cib->cmds->create = cib_client_create; new_cib->cmds->modify = cib_client_modify; new_cib->cmds->update = cib_client_update; new_cib->cmds->replace = cib_client_replace; new_cib->cmds->remove = cib_client_delete; new_cib->cmds->erase = cib_client_erase; new_cib->cmds->delete_absolute = cib_client_delete_absolute; return new_cib; } void cib_free_notify(cib_t *cib) { if (cib) { GList *list = cib->notify_list; while (list != NULL) { cib_notify_client_t *client = g_list_nth_data(list, 0); list = g_list_remove(list, client); free(client); } cib->notify_list = NULL; } } /*! * \brief Free all callbacks for a CIB connection * * \param[in] cib CIB connection to clean up */ void cib_free_callbacks(cib_t *cib) { cib_free_notify(cib); destroy_op_callback_table(); } /*! * \brief Free all memory used by CIB connection * * \param[in] cib CIB connection to delete */ void cib_delete(cib_t *cib) { cib_free_callbacks(cib); if (cib) { cib->cmds->free(cib); } } int cib_client_set_op_callback(cib_t * cib, void (*callback) (const xmlNode * msg, int call_id, int rc, xmlNode * output)) { if (callback == NULL) { crm_info("Un-Setting operation callback"); } else { crm_trace("Setting operation callback"); } cib->op_callback = callback; return pcmk_ok; } int cib_client_add_notify_callback(cib_t * cib, const char *event, void (*callback) (const char *event, xmlNode * msg)) { GList *list_item = NULL; cib_notify_client_t *new_client = NULL; if (cib->variant != cib_native && cib->variant != cib_remote) { return -EPROTONOSUPPORT; } crm_trace("Adding callback for %s events (%d)", event, g_list_length(cib->notify_list)); new_client = calloc(1, sizeof(cib_notify_client_t)); new_client->event = event; new_client->callback = callback; list_item = g_list_find_custom(cib->notify_list, new_client, ciblib_GCompareFunc); if (list_item != NULL) { crm_warn("Callback already present"); free(new_client); return -EINVAL; } else { cib->notify_list = g_list_append(cib->notify_list, new_client); cib->cmds->register_notification(cib, event, 1); crm_trace("Callback added (%d)", g_list_length(cib->notify_list)); } return pcmk_ok; } static int get_notify_list_event_count(cib_t * cib, const char *event) { GList *l = NULL; int count = 0; for (l = g_list_first(cib->notify_list); l; l = g_list_next(l)) { cib_notify_client_t *client = (cib_notify_client_t *)l->data; if (strcmp(client->event, event) == 0) { count++; } } crm_trace("event(%s) count : %d", event, count); return count; } int cib_client_del_notify_callback(cib_t * cib, const char *event, void (*callback) (const char *event, xmlNode * msg)) { GList *list_item = NULL; cib_notify_client_t *new_client = NULL; if (cib->variant != cib_native && cib->variant != cib_remote) { return -EPROTONOSUPPORT; } if (get_notify_list_event_count(cib, event) == 0) { crm_debug("The callback of the event does not exist(%s)", event); return pcmk_ok; } crm_debug("Removing callback for %s events", event); new_client = calloc(1, sizeof(cib_notify_client_t)); new_client->event = event; new_client->callback = callback; list_item = g_list_find_custom(cib->notify_list, new_client, ciblib_GCompareFunc); if (list_item != NULL) { cib_notify_client_t *list_client = list_item->data; cib->notify_list = g_list_remove(cib->notify_list, list_client); free(list_client); crm_trace("Removed callback"); } else { crm_trace("Callback not present"); } if (get_notify_list_event_count(cib, event) == 0) { /* When there is not the registration of the event, the processing turns off a notice. */ cib->cmds->register_notification(cib, event, 0); } free(new_client); return pcmk_ok; } gint ciblib_GCompareFunc(gconstpointer a, gconstpointer b) { int rc = 0; const cib_notify_client_t *a_client = a; const cib_notify_client_t *b_client = b; CRM_CHECK(a_client->event != NULL && b_client->event != NULL, return 0); rc = strcmp(a_client->event, b_client->event); if (rc == 0) { if (a_client->callback == b_client->callback) { return 0; } else if (((long)a_client->callback) < ((long)b_client->callback)) { crm_trace("callbacks for %s are not equal: %p < %p", a_client->event, a_client->callback, b_client->callback); return -1; } crm_trace("callbacks for %s are not equal: %p > %p", a_client->event, a_client->callback, b_client->callback); return 1; } return rc; } static gboolean cib_async_timeout_handler(gpointer data) { struct timer_rec_s *timer = data; crm_debug("Async call %d timed out after %ds", timer->call_id, timer->timeout); cib_native_callback(timer->cib, NULL, timer->call_id, -ETIME); /* Always return TRUE, never remove the handler * We do that in remove_cib_op_callback() */ return TRUE; } gboolean cib_client_register_callback(cib_t * cib, int call_id, int timeout, gboolean only_success, void *user_data, const char *callback_name, void (*callback) (xmlNode *, int, int, xmlNode *, void *)) { return cib_client_register_callback_full(cib, call_id, timeout, only_success, user_data, callback_name, callback, NULL); } gboolean cib_client_register_callback_full(cib_t *cib, int call_id, int timeout, gboolean only_success, void *user_data, const char *callback_name, void (*callback)(xmlNode *, int, int, xmlNode *, void *), void (*free_func)(void *)) { cib_callback_client_t *blob = NULL; if (call_id < 0) { if (only_success == FALSE) { callback(NULL, call_id, call_id, NULL, user_data); } else { crm_warn("CIB call failed: %s", pcmk_strerror(call_id)); } if (user_data && free_func) { free_func(user_data); } return FALSE; } blob = calloc(1, sizeof(cib_callback_client_t)); blob->id = callback_name; blob->only_success = only_success; blob->user_data = user_data; blob->callback = callback; blob->free_func = free_func; if (timeout > 0) { struct timer_rec_s *async_timer = NULL; async_timer = calloc(1, sizeof(struct timer_rec_s)); blob->timer = async_timer; async_timer->cib = cib; async_timer->call_id = call_id; async_timer->timeout = timeout * 1000; async_timer->ref = g_timeout_add(async_timer->timeout, cib_async_timeout_handler, async_timer); } crm_trace("Adding callback %s for call %d", callback_name, call_id); pcmk__intkey_table_insert(cib_op_callback_table, call_id, blob); return TRUE; } void remove_cib_op_callback(int call_id, gboolean all_callbacks) { if (all_callbacks) { destroy_op_callback_table(); cib_op_callback_table = pcmk__intkey_table(cib_destroy_op_callback); } else { pcmk__intkey_table_remove(cib_op_callback_table, call_id); } } int num_cib_op_callbacks(void) { if (cib_op_callback_table == NULL) { return 0; } return g_hash_table_size(cib_op_callback_table); } static void cib_dump_pending_op(gpointer key, gpointer value, gpointer user_data) { int call = GPOINTER_TO_INT(key); cib_callback_client_t *blob = value; crm_debug("Call %d (%s): pending", call, crm_str(blob->id)); } void cib_dump_pending_callbacks(void) { if (cib_op_callback_table == NULL) { return; } return g_hash_table_foreach(cib_op_callback_table, cib_dump_pending_op, NULL); } diff --git a/lib/common/strings.c b/lib/common/strings.c index cc580dcd1c..8a004618fc 100644 --- a/lib/common/strings.c +++ b/lib/common/strings.c @@ -1,1221 +1,1250 @@ /* * Copyright 2004-2021 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 // DBL_MIN #include #include // fabs() #include #include /*! * \internal * \brief Scan a long long integer from a string * * \param[in] text String to scan * \param[out] result If not NULL, where to store scanned value * \param[in] default_value Value to use if text is NULL or invalid * \param[out] end_text If not NULL, where to store pointer to first * non-integer character * * \return Standard Pacemaker return code (\c pcmk_rc_ok on success, * \c EINVAL on failed string conversion due to invalid input, * or \c EOVERFLOW on arithmetic overflow) * \note Sets \c errno on error */ static int scan_ll(const char *text, long long *result, long long default_value, char **end_text) { long long local_result = default_value; char *local_end_text = NULL; int rc = pcmk_rc_ok; errno = 0; if (text != NULL) { local_result = strtoll(text, &local_end_text, 10); if (errno == ERANGE) { rc = EOVERFLOW; crm_warn("Integer parsed from '%s' was clipped to %lld", text, local_result); } else if (errno != 0) { rc = errno; local_result = default_value; crm_warn("Could not parse integer from '%s' (using %lld instead): " "%s", text, default_value, pcmk_rc_str(rc)); } else if (local_end_text == text) { rc = EINVAL; local_result = default_value; crm_warn("Could not parse integer from '%s' (using %lld instead): " "No digits found", text, default_value); } if ((end_text == NULL) && !pcmk__str_empty(local_end_text)) { crm_warn("Characters left over after parsing '%s': '%s'", text, local_end_text); } errno = rc; } if (end_text != NULL) { *end_text = local_end_text; } if (result != NULL) { *result = local_result; } return rc; } /*! * \internal * \brief Scan a long long integer value from a string * * \param[in] text The string to scan (may be NULL) * \param[out] result Where to store result (or NULL to ignore) * \param[in] default_value Value to use if text is NULL or invalid * * \return Standard Pacemaker return code */ int pcmk__scan_ll(const char *text, long long *result, long long default_value) { long long local_result = default_value; int rc = pcmk_rc_ok; if (text != NULL) { rc = scan_ll(text, &local_result, default_value, NULL); if (rc != pcmk_rc_ok) { local_result = default_value; } } if (result != NULL) { *result = local_result; } return rc; } +/*! + * \internal + * \brief Scan a TCP port number from a string + * + * \param[in] text The string to scan + * \param[out] port Where to store result (or NULL to ignore) + * + * \return Standard Pacemaker return code + * \note \p port will be -1 if \p text is NULL or invalid + */ +int +pcmk__scan_port(const char *text, int *port) +{ + long long port_ll; + int rc = pcmk__scan_ll(text, &port_ll, -1LL); + + if ((text != NULL) && (rc == pcmk_rc_ok) // wasn't default or invalid + && ((port_ll < 0LL) || (port_ll > 65535LL))) { + crm_warn("Ignoring port specification '%s' " + "not in valid range (0-65535)", text); + rc = (port_ll < 0LL)? pcmk_rc_before_range : pcmk_rc_after_range; + port_ll = -1LL; + } + if (port != NULL) { + *port = (int) port_ll; + } + return rc; +} + /*! * \brief Parse a long long integer value from a string * * \param[in] text The string to parse * \param[in] default_text Default string to parse if text is NULL * * \return Parsed value on success, PCMK__PARSE_INT_DEFAULT (and set * errno) on error */ long long crm_parse_ll(const char *text, const char *default_text) { long long result; if (text == NULL) { text = default_text; if (text == NULL) { crm_err("No default conversion value supplied"); errno = EINVAL; return PCMK__PARSE_INT_DEFAULT; } } scan_ll(text, &result, PCMK__PARSE_INT_DEFAULT, NULL); return result; } /*! * \brief Parse an integer value from a string * * \param[in] text The string to parse * \param[in] default_text Default string to parse if text is NULL * * \return Parsed value on success, INT_MIN or INT_MAX (and set errno to * ERANGE) if parsed value is out of integer range, otherwise * PCMK__PARSE_INT_DEFAULT (and set errno) */ int crm_parse_int(const char *text, const char *default_text) { long long result = crm_parse_ll(text, default_text); if (result < INT_MIN) { // If errno is ERANGE, crm_parse_ll() has already logged a message if (errno != ERANGE) { crm_err("Conversion of %s was clipped: %lld", text, result); errno = ERANGE; } return INT_MIN; } else if (result > INT_MAX) { // If errno is ERANGE, crm_parse_ll() has already logged a message if (errno != ERANGE) { crm_err("Conversion of %s was clipped: %lld", text, result); errno = ERANGE; } return INT_MAX; } return (int) result; } /*! * \internal * \brief Scan a double-precision floating-point value from a string * * \param[in] text The string to parse * \param[out] result Parsed value on success, or * \c PCMK__PARSE_DBL_DEFAULT on error * \param[in] default_text Default string to parse if \p text is * \c NULL * \param[out] end_text If not \c NULL, where to store a pointer * to the position immediately after the * value * * \return Standard Pacemaker return code (\c pcmk_rc_ok on success, * \c EINVAL on failed string conversion due to invalid input, * \c EOVERFLOW on arithmetic overflow, \c pcmk_rc_underflow * on arithmetic underflow, or \c errno from \c strtod() on * other parse errors) */ int pcmk__scan_double(const char *text, double *result, const char *default_text, char **end_text) { int rc = pcmk_rc_ok; char *local_end_text = NULL; CRM_ASSERT(result != NULL); *result = PCMK__PARSE_DBL_DEFAULT; text = (text != NULL) ? text : default_text; if (text == NULL) { rc = EINVAL; crm_debug("No text and no default conversion value supplied"); } else { errno = 0; *result = strtod(text, &local_end_text); if (errno == ERANGE) { /* * Overflow: strtod() returns +/- HUGE_VAL and sets errno to * ERANGE * * Underflow: strtod() returns "a value whose magnitude is * no greater than the smallest normalized * positive" double. Whether ERANGE is set is * implementation-defined. */ const char *over_under; if (fabs(*result) > DBL_MIN) { rc = EOVERFLOW; over_under = "over"; } else { rc = pcmk_rc_underflow; over_under = "under"; } crm_debug("Floating-point value parsed from '%s' would %sflow " "(using %g instead)", text, over_under, *result); } else if (errno != 0) { rc = errno; // strtod() set *result = 0 on parse failure *result = PCMK__PARSE_DBL_DEFAULT; crm_debug("Could not parse floating-point value from '%s' (using " "%.1f instead): %s", text, PCMK__PARSE_DBL_DEFAULT, pcmk_rc_str(rc)); } else if (local_end_text == text) { // errno == 0, but nothing was parsed rc = EINVAL; *result = PCMK__PARSE_DBL_DEFAULT; crm_debug("Could not parse floating-point value from '%s' (using " "%.1f instead): No digits found", text, PCMK__PARSE_DBL_DEFAULT); } else if (fabs(*result) <= DBL_MIN) { /* * errno == 0 and text was parsed, but value might have * underflowed. * * ERANGE might not be set for underflow. Check magnitude * of *result, but also make sure the input number is not * actually zero (0 <= DBL_MIN is not underflow). * * This check must come last. A parse failure in strtod() * also sets *result == 0, so a parse failure would match * this test condition prematurely. */ for (const char *p = text; p != local_end_text; p++) { if (strchr("0.eE", *p) == NULL) { rc = pcmk_rc_underflow; crm_debug("Floating-point value parsed from '%s' would " "underflow (using %g instead)", text, *result); break; } } } else { crm_trace("Floating-point value parsed successfully from " "'%s': %g", text, *result); } if ((end_text == NULL) && !pcmk__str_empty(local_end_text)) { crm_debug("Characters left over after parsing '%s': '%s'", text, local_end_text); } } if (end_text != NULL) { *end_text = local_end_text; } return rc; } /*! * \internal * \brief Parse a guint from a string stored in a hash table * * \param[in] table Hash table to search * \param[in] key Hash table key to use to retrieve string * \param[in] default_val What to use if key has no entry in table * \param[out] result If not NULL, where to store parsed integer * * \return Standard Pacemaker return code */ int pcmk__guint_from_hash(GHashTable *table, const char *key, guint default_val, guint *result) { const char *value; long long value_ll; int rc = pcmk_rc_ok; CRM_CHECK((table != NULL) && (key != NULL), return EINVAL); if (result != NULL) { *result = default_val; } value = g_hash_table_lookup(table, key); if (value == NULL) { return pcmk_rc_ok; } rc = pcmk__scan_ll(value, &value_ll, 0LL); if (rc != pcmk_rc_ok) { return rc; } if ((value_ll < 0) || (value_ll > G_MAXUINT)) { crm_warn("Could not parse non-negative integer from %s", value); return ERANGE; } if (result != NULL) { *result = (guint) value_ll; } return pcmk_rc_ok; } #ifndef NUMCHARS # define NUMCHARS "0123456789." #endif #ifndef WHITESPACE # define WHITESPACE " \t\n\r\f" #endif /*! * \brief Parse a time+units string and return milliseconds equivalent * * \param[in] input String with a number and units (optionally with whitespace * before and/or after the number) * * \return Milliseconds corresponding to string expression, or * PCMK__PARSE_INT_DEFAULT on error */ long long crm_get_msec(const char *input) { const char *num_start = NULL; const char *units; long long multiplier = 1000; long long divisor = 1; long long msec = PCMK__PARSE_INT_DEFAULT; size_t num_len = 0; char *end_text = NULL; if (input == NULL) { return PCMK__PARSE_INT_DEFAULT; } num_start = input + strspn(input, WHITESPACE); num_len = strspn(num_start, NUMCHARS); if (num_len < 1) { return PCMK__PARSE_INT_DEFAULT; } units = num_start + num_len; units += strspn(units, WHITESPACE); if (!strncasecmp(units, "ms", 2) || !strncasecmp(units, "msec", 4)) { multiplier = 1; divisor = 1; } else if (!strncasecmp(units, "us", 2) || !strncasecmp(units, "usec", 4)) { multiplier = 1; divisor = 1000; } else if (!strncasecmp(units, "s", 1) || !strncasecmp(units, "sec", 3)) { multiplier = 1000; divisor = 1; } else if (!strncasecmp(units, "m", 1) || !strncasecmp(units, "min", 3)) { multiplier = 60 * 1000; divisor = 1; } else if (!strncasecmp(units, "h", 1) || !strncasecmp(units, "hr", 2)) { multiplier = 60 * 60 * 1000; divisor = 1; } else if ((*units != '\0') && (*units != '\n') && (*units != '\r')) { return PCMK__PARSE_INT_DEFAULT; } scan_ll(num_start, &msec, PCMK__PARSE_INT_DEFAULT, &end_text); if (msec > (LLONG_MAX / multiplier)) { // Arithmetics overflow while multiplier/divisor mutually exclusive return LLONG_MAX; } msec *= multiplier; msec /= divisor; return msec; } gboolean crm_is_true(const char *s) { gboolean ret = FALSE; if (s != NULL) { crm_str_to_boolean(s, &ret); } return ret; } int crm_str_to_boolean(const char *s, int *ret) { if (s == NULL) { return -1; } else if (strcasecmp(s, "true") == 0 || strcasecmp(s, "on") == 0 || strcasecmp(s, "yes") == 0 || strcasecmp(s, "y") == 0 || strcasecmp(s, "1") == 0) { *ret = TRUE; return 1; } else if (strcasecmp(s, "false") == 0 || strcasecmp(s, "off") == 0 || strcasecmp(s, "no") == 0 || strcasecmp(s, "n") == 0 || strcasecmp(s, "0") == 0) { *ret = FALSE; return 1; } return -1; } char * crm_strip_trailing_newline(char *str) { int len; if (str == NULL) { return str; } for (len = strlen(str) - 1; len >= 0 && str[len] == '\n'; len--) { str[len] = '\0'; } return str; } /*! * \brief Check whether a string starts with a certain sequence * * \param[in] str String to check * \param[in] prefix Sequence to match against beginning of \p str * * \return \c true if \p str begins with match, \c false otherwise * \note This is equivalent to !strncmp(s, prefix, strlen(prefix)) * but is likely less efficient when prefix is a string literal * if the compiler optimizes away the strlen() at compile time, * and more efficient otherwise. */ bool pcmk__starts_with(const char *str, const char *prefix) { const char *s = str; const char *p = prefix; if (!s || !p) { return false; } while (*s && *p) { if (*s++ != *p++) { return false; } } return (*p == 0); } static inline bool ends_with(const char *s, const char *match, bool as_extension) { if (pcmk__str_empty(match)) { return true; } else if (s == NULL) { return false; } else { size_t slen, mlen; /* Besides as_extension, we could also check !strchr(&match[1], match[0]) but that would be inefficient. */ if (as_extension) { s = strrchr(s, match[0]); return (s == NULL)? false : !strcmp(s, match); } mlen = strlen(match); slen = strlen(s); return ((slen >= mlen) && !strcmp(s + slen - mlen, match)); } } /*! * \internal * \brief Check whether a string ends with a certain sequence * * \param[in] s String to check * \param[in] match Sequence to match against end of \p s * * \return \c true if \p s ends case-sensitively with match, \c false otherwise * \note pcmk__ends_with_ext() can be used if the first character of match * does not recur in match. */ bool pcmk__ends_with(const char *s, const char *match) { return ends_with(s, match, false); } /*! * \internal * \brief Check whether a string ends with a certain "extension" * * \param[in] s String to check * \param[in] match Extension to match against end of \p s, that is, * its first character must not occur anywhere * in the rest of that very sequence (example: file * extension where the last dot is its delimiter, * e.g., ".html"); incorrect results may be * returned otherwise. * * \return \c true if \p s ends (verbatim, i.e., case sensitively) * with "extension" designated as \p match (including empty * string), \c false otherwise * * \note Main incentive to prefer this function over \c pcmk__ends_with() * where possible is the efficiency (at the cost of added * restriction on \p match as stated; the complexity class * remains the same, though: BigO(M+N) vs. BigO(M+2N)). */ bool pcmk__ends_with_ext(const char *s, const char *match) { return ends_with(s, match, true); } /*! * \internal * \brief Create a hash of a string suitable for use with GHashTable * * \param[in] v String to hash * * \return A hash of \p v compatible with g_str_hash() before glib 2.28 * \note glib changed their hash implementation: * * https://gitlab.gnome.org/GNOME/glib/commit/354d655ba8a54b754cb5a3efb42767327775696c * * Note that the new g_str_hash is presumably a *better* hash (it's actually * a correct implementation of DJB's hash), but we need to preserve existing * behaviour, because the hash key ultimately determines the "sort" order * when iterating through GHashTables, which affects allocation of scores to * clone instances when iterating through rsc->allowed_nodes. It (somehow) * also appears to have some minor impact on the ordering of a few * pseudo_event IDs in the transition graph. */ static guint pcmk__str_hash(gconstpointer v) { const signed char *p; guint32 h = 0; for (p = v; *p != '\0'; p++) h = (h << 5) - h + *p; return h; } /*! * \internal * \brief Create a hash table with case-sensitive strings as keys * * \param[in] key_destroy_func Function to free a key * \param[in] value_destroy_func Function to free a value * * \return Newly allocated hash table * \note It is the caller's responsibility to free the result, using * g_hash_table_destroy(). */ GHashTable * pcmk__strkey_table(GDestroyNotify key_destroy_func, GDestroyNotify value_destroy_func) { return g_hash_table_new_full(pcmk__str_hash, g_str_equal, key_destroy_func, value_destroy_func); } /* used with hash tables where case does not matter */ static gboolean pcmk__strcase_equal(gconstpointer a, gconstpointer b) { return pcmk__str_eq((const char *)a, (const char *)b, pcmk__str_casei); } static guint pcmk__strcase_hash(gconstpointer v) { const signed char *p; guint32 h = 0; for (p = v; *p != '\0'; p++) h = (h << 5) - h + g_ascii_tolower(*p); return h; } /*! * \internal * \brief Create a hash table with case-insensitive strings as keys * * \param[in] key_destroy_func Function to free a key * \param[in] value_destroy_func Function to free a value * * \return Newly allocated hash table * \note It is the caller's responsibility to free the result, using * g_hash_table_destroy(). */ GHashTable * pcmk__strikey_table(GDestroyNotify key_destroy_func, GDestroyNotify value_destroy_func) { return g_hash_table_new_full(pcmk__strcase_hash, pcmk__strcase_equal, key_destroy_func, value_destroy_func); } static void copy_str_table_entry(gpointer key, gpointer value, gpointer user_data) { if (key && value && user_data) { g_hash_table_insert((GHashTable*)user_data, strdup(key), strdup(value)); } } /*! * \internal * \brief Copy a hash table that uses dynamically allocated strings * * \param[in] old_table Hash table to duplicate * * \return New hash table with copies of everything in \p old_table * \note This assumes the hash table uses dynamically allocated strings -- that * is, both the key and value free functions are free(). */ GHashTable * pcmk__str_table_dup(GHashTable *old_table) { GHashTable *new_table = NULL; if (old_table) { new_table = pcmk__strkey_table(free, free); g_hash_table_foreach(old_table, copy_str_table_entry, new_table); } return new_table; } /*! * \internal * \brief Add a word to a string list of words * * \param[in,out] list Pointer to current string list (may not be NULL) * \param[in,out] len If not NULL, must be set to length of \p list, * and will be updated to new length of \p list * \param[in] word String to add to \p list (\p list will be * unchanged if this is NULL or the empty string) * \param[in] separator String to separate words in \p list * (a space will be used if this is NULL) * * \note This dynamically reallocates \p list as needed. \p word may contain * \p separator, though that would be a bad idea if the string needs to be * parsed later. */ void pcmk__add_separated_word(char **list, size_t *len, const char *word, const char *separator) { size_t orig_len, new_len; CRM_ASSERT(list != NULL); if (pcmk__str_empty(word)) { return; } // Use provided length, or calculate it if not available orig_len = (len != NULL)? *len : ((*list == NULL)? 0 : strlen(*list)); // Don't add a separator before the first word in the list if (orig_len == 0) { separator = ""; // Default to space-separated } else if (separator == NULL) { separator = " "; } new_len = orig_len + strlen(separator) + strlen(word); if (len != NULL) { *len = new_len; } // +1 for null terminator *list = pcmk__realloc(*list, new_len + 1); sprintf(*list + orig_len, "%s%s", separator, word); } /*! * \internal * \brief Compress data * * \param[in] data Data to compress * \param[in] length Number of characters of data to compress * \param[in] max Maximum size of compressed data (or 0 to estimate) * \param[out] result Where to store newly allocated compressed result * \param[out] result_len Where to store actual compressed length of result * * \return Standard Pacemaker return code */ int pcmk__compress(const char *data, unsigned int length, unsigned int max, char **result, unsigned int *result_len) { int rc; char *compressed = NULL; char *uncompressed = strdup(data); #ifdef CLOCK_MONOTONIC struct timespec after_t; struct timespec before_t; #endif if (max == 0) { max = (length * 1.01) + 601; // Size guaranteed to hold result } #ifdef CLOCK_MONOTONIC clock_gettime(CLOCK_MONOTONIC, &before_t); #endif compressed = calloc((size_t) max, sizeof(char)); CRM_ASSERT(compressed); *result_len = max; rc = BZ2_bzBuffToBuffCompress(compressed, result_len, uncompressed, length, CRM_BZ2_BLOCKS, 0, CRM_BZ2_WORK); free(uncompressed); if (rc != BZ_OK) { crm_err("Compression of %d bytes failed: %s " CRM_XS " bzerror=%d", length, bz2_strerror(rc), rc); free(compressed); return pcmk_rc_error; } #ifdef CLOCK_MONOTONIC clock_gettime(CLOCK_MONOTONIC, &after_t); crm_trace("Compressed %d bytes into %d (ratio %d:1) in %.0fms", length, *result_len, length / (*result_len), (after_t.tv_sec - before_t.tv_sec) * 1000 + (after_t.tv_nsec - before_t.tv_nsec) / 1e6); #else crm_trace("Compressed %d bytes into %d (ratio %d:1)", length, *result_len, length / (*result_len)); #endif *result = compressed; return pcmk_rc_ok; } char * crm_strdup_printf(char const *format, ...) { va_list ap; int len = 0; char *string = NULL; va_start(ap, format); len = vasprintf (&string, format, ap); CRM_ASSERT(len > 0); va_end(ap); return string; } int pcmk__parse_ll_range(const char *srcstring, long long *start, long long *end) { char *remainder = NULL; CRM_ASSERT(start != NULL && end != NULL); *start = PCMK__PARSE_INT_DEFAULT; *end = PCMK__PARSE_INT_DEFAULT; crm_trace("Attempting to decode: [%s]", srcstring); if (pcmk__str_empty(srcstring) || !strcmp(srcstring, "-")) { return pcmk_rc_unknown_format; } /* String starts with a dash, so this is either a range with * no beginning or garbage. * */ if (*srcstring == '-') { int rc = scan_ll(srcstring+1, end, PCMK__PARSE_INT_DEFAULT, &remainder); if (rc != pcmk_rc_ok || *remainder != '\0') { return pcmk_rc_unknown_format; } else { return pcmk_rc_ok; } } if (scan_ll(srcstring, start, PCMK__PARSE_INT_DEFAULT, &remainder) != pcmk_rc_ok) { return pcmk_rc_unknown_format; } if (*remainder && *remainder == '-') { if (*(remainder+1)) { char *more_remainder = NULL; int rc = scan_ll(remainder+1, end, PCMK__PARSE_INT_DEFAULT, &more_remainder); if (rc != pcmk_rc_ok || *more_remainder != '\0') { return pcmk_rc_unknown_format; } } } else if (*remainder && *remainder != '-') { *start = PCMK__PARSE_INT_DEFAULT; return pcmk_rc_unknown_format; } else { /* The input string contained only one number. Set start and end * to the same value and return pcmk_rc_ok. This gives the caller * a way to tell this condition apart from a range with no end. */ *end = *start; } return pcmk_rc_ok; } /*! * \internal * \brief Find a string in a list of strings * * Search \p lst for \p s, taking case into account. As a special case, * if "*" is the only element of \p lst, the search is successful. * * \param[in] lst List to search * \param[in] s String to search for * * \return \c TRUE if \p s is in \p lst, or \c FALSE otherwise */ gboolean pcmk__str_in_list(GList *lst, const gchar *s) { if (lst == NULL) { return FALSE; } if (strcmp(lst->data, "*") == 0 && lst->next == NULL) { return TRUE; } return g_list_find_custom(lst, s, (GCompareFunc) strcmp) != NULL; } static bool str_any_of(bool casei, const char *s, va_list args) { bool rc = false; if (s != NULL) { while (1) { const char *ele = va_arg(args, const char *); if (ele == NULL) { break; } else if (pcmk__str_eq(s, ele, casei? pcmk__str_casei : pcmk__str_none)) { rc = true; break; } } } return rc; } /*! * \internal * \brief Is a string a member of a list of strings? * * \param[in] s String to search for in \p ... * \param[in] ... Strings to compare \p s against. The final string * must be NULL. * * \note The comparison is done case-insensitively. The function name is * meant to be reminiscent of strcasecmp. * * \return \c true if \p s is in \p ..., or \c false otherwise */ bool pcmk__strcase_any_of(const char *s, ...) { va_list ap; bool rc; va_start(ap, s); rc = str_any_of(true, s, ap); va_end(ap); return rc; } /*! * \internal * \brief Is a string a member of a list of strings? * * \param[in] s String to search for in \p ... * \param[in] ... Strings to compare \p s against. The final string * must be NULL. * * \note The comparison is done taking case into account. * * \return \c true if \p s is in \p ..., or \c false otherwise */ bool pcmk__str_any_of(const char *s, ...) { va_list ap; bool rc; va_start(ap, s); rc = str_any_of(false, s, ap); va_end(ap); return rc; } /*! * \internal * \brief Check whether a character is in any of a list of strings * * \param[in] ch Character (ASCII) to search for * \param[in] ... Strings to search. Final argument must be * \c NULL. * * \return \c true if any of \p ... contain \p ch, \c false otherwise * \note \p ... must contain at least one argument (\c NULL). */ bool pcmk__char_in_any_str(int ch, ...) { bool rc = false; va_list ap; /* * Passing a char to va_start() can generate compiler warnings, * so ch is declared as an int. */ va_start(ap, ch); while (1) { const char *ele = va_arg(ap, const char *); if (ele == NULL) { break; } else if (strchr(ele, ch) != NULL) { rc = true; break; } } va_end(ap); return rc; } /*! * \brief Sort strings, with numeric portions sorted numerically * * Sort two strings case-insensitively like strcasecmp(), but with any numeric * portions of the string sorted numerically. This is particularly useful for * node names (for example, "node10" will sort higher than "node9" but lower * than "remotenode9"). * * \param[in] s1 First string to compare (must not be NULL) * \param[in] s2 Second string to compare (must not be NULL) * * \retval -1 \p s1 comes before \p s2 * \retval 0 \p s1 and \p s2 are equal * \retval 1 \p s1 comes after \p s2 */ int pcmk_numeric_strcasecmp(const char *s1, const char *s2) { while (*s1 && *s2) { if (isdigit(*s1) && isdigit(*s2)) { // If node names contain a number, sort numerically char *end1 = NULL; char *end2 = NULL; long num1 = strtol(s1, &end1, 10); long num2 = strtol(s2, &end2, 10); // allow ordering e.g. 007 > 7 size_t len1 = end1 - s1; size_t len2 = end2 - s2; if (num1 < num2) { return -1; } else if (num1 > num2) { return 1; } else if (len1 < len2) { return -1; } else if (len1 > len2) { return 1; } s1 = end1; s2 = end2; } else { // Compare non-digits case-insensitively int lower1 = tolower(*s1); int lower2 = tolower(*s2); if (lower1 < lower2) { return -1; } else if (lower1 > lower2) { return 1; } ++s1; ++s2; } } if (!*s1 && *s2) { return -1; } else if (*s1 && !*s2) { return 1; } return 0; } /* * \brief Sort strings. * * This is your one-stop function for string comparison. By default, this * function works like g_strcmp0. That is, like strcmp but a NULL string * sorts before a non-NULL string. * * Behavior can be changed with various flags: * * - pcmk__str_regex - The second string is a regular expression that the * first string will be matched against. * - pcmk__str_casei - By default, comparisons are done taking case into * account. This flag makes comparisons case-insensitive. * This can be combined with pcmk__str_regex. * - pcmk__str_null_matches - If one string is NULL and the other is not, * still return 0. * * \param[in] s1 First string to compare * \param[in] s2 Second string to compare, or a regular expression to * match if pcmk__str_regex is set * \param[in] flags A bitfield of pcmk__str_flags to modify operation * * \retval -1 \p s1 is NULL or comes before \p s2 * \retval 0 \p s1 and \p s2 are equal, or \p s1 is found in \p s2 if * pcmk__str_regex is set * \retval 1 \p s2 is NULL or \p s1 comes after \p s2, or if \p s2 * is an invalid regular expression, or \p s1 was not found * in \p s2 if pcmk__str_regex is set. */ int pcmk__strcmp(const char *s1, const char *s2, uint32_t flags) { /* If this flag is set, the second string is a regex. */ if (pcmk_is_set(flags, pcmk__str_regex)) { regex_t *r_patt = calloc(1, sizeof(regex_t)); int reg_flags = REG_EXTENDED | REG_NOSUB; int regcomp_rc = 0; int rc = 0; if (s1 == NULL || s2 == NULL) { free(r_patt); return 1; } if (pcmk_is_set(flags, pcmk__str_casei)) { reg_flags |= REG_ICASE; } regcomp_rc = regcomp(r_patt, s2, reg_flags); if (regcomp_rc != 0) { rc = 1; crm_err("Bad regex '%s' for update: %s", s2, strerror(regcomp_rc)); } else { rc = regexec(r_patt, s1, 0, NULL, 0); if (rc != 0) { rc = 1; } } regfree(r_patt); free(r_patt); return rc; } /* If the strings are the same pointer, return 0 immediately. */ if (s1 == s2) { return 0; } /* If this flag is set, return 0 if either (or both) of the input strings * are NULL. If neither one is NULL, we need to continue and compare * them normally. */ if (pcmk_is_set(flags, pcmk__str_null_matches)) { if (s1 == NULL || s2 == NULL) { return 0; } } /* Handle the cases where one is NULL and the str_null_matches flag is not set. * A NULL string always sorts to the beginning. */ if (s1 == NULL) { return -1; } else if (s2 == NULL) { return 1; } if (pcmk_is_set(flags, pcmk__str_casei)) { return strcasecmp(s1, s2); } else { return strcmp(s1, s2); } } // Deprecated functions kept only for backward API compatibility #include gboolean safe_str_neq(const char *a, const char *b) { if (a == b) { return FALSE; } else if (a == NULL || b == NULL) { return TRUE; } else if (strcasecmp(a, b) == 0) { return FALSE; } return TRUE; } gboolean crm_str_eq(const char *a, const char *b, gboolean use_case) { if (use_case) { return g_strcmp0(a, b) == 0; /* TODO - Figure out which calls, if any, really need to be case independent */ } else if (a == b) { return TRUE; } else if (a == NULL || b == NULL) { /* shouldn't be comparing NULLs */ return FALSE; } else if (strcasecmp(a, b) == 0) { return TRUE; } return FALSE; } char * crm_itoa_stack(int an_int, char *buffer, size_t len) { if (buffer != NULL) { snprintf(buffer, len, "%d", an_int); } return buffer; } guint g_str_hash_traditional(gconstpointer v) { return pcmk__str_hash(v); } gboolean crm_strcase_equal(gconstpointer a, gconstpointer b) { return pcmk__strcase_equal(a, b); } guint crm_strcase_hash(gconstpointer v) { return pcmk__strcase_hash(v); } GHashTable * crm_str_table_dup(GHashTable *old_table) { return pcmk__str_table_dup(old_table); } // End deprecated API