diff --git a/daemons/based/based_io.c b/daemons/based/based_io.c index 2bb3c51619..514f56952d 100644 --- a/daemons/based/based_io.c +++ b/daemons/based/based_io.c @@ -1,465 +1,465 @@ /* * Copyright 2004-2018 Andrew Beekhof * * 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 #include #include #include #include #include extern const char *cib_root; crm_trigger_t *cib_writer = NULL; extern int cib_status; int write_cib_contents(gpointer p); static void cib_rename(const char *old) { int new_fd; char *new = crm_strdup_printf("%s/cib.auto.XXXXXX", cib_root); crm_err("Archiving unusable file %s as %s", old, new); umask(S_IWGRP | S_IWOTH | S_IROTH); new_fd = mkstemp(new); if ((new_fd < 0) || (rename(old, new) < 0)) { crm_perror(LOG_ERR, "Couldn't rename %s as %s", old, new); crm_err("Disabling disk writes and continuing"); cib_writes_enabled = FALSE; } if (new_fd > 0) { close(new_fd); } free(new); } /* * It is the callers responsibility to free the output of this function */ static xmlNode * retrieveCib(const char *filename, const char *sigfile) { xmlNode *root = NULL; crm_info("Reading cluster configuration file %s (digest: %s)", filename, sigfile); switch (cib_file_read_and_verify(filename, sigfile, &root)) { case -pcmk_err_cib_corrupt: crm_warn("Continuing but %s will NOT be used.", filename); break; case -pcmk_err_cib_modified: /* Archive the original files so the contents are not lost */ crm_warn("Continuing but %s will NOT be used.", filename); cib_rename(filename); cib_rename(sigfile); break; } return root; } /* * for OSs without support for direntry->d_type, like Solaris */ #ifndef DT_UNKNOWN # define DT_UNKNOWN 0 # define DT_FIFO 1 # define DT_CHR 2 # define DT_DIR 4 # define DT_BLK 6 # define DT_REG 8 # define DT_LNK 10 # define DT_SOCK 12 # define DT_WHT 14 #endif /*DT_UNKNOWN*/ static int cib_archive_filter(const struct dirent * a) { int rc = 0; /* Looking for regular files (d_type = 8) starting with 'cib-' and not ending in .sig */ struct stat s; char *a_path = crm_strdup_printf("%s/%s", cib_root, a->d_name); if(stat(a_path, &s) != 0) { rc = errno; crm_trace("%s - stat failed: %s (%d)", a->d_name, pcmk_strerror(rc), rc); rc = 0; } else if ((s.st_mode & S_IFREG) != S_IFREG) { unsigned char dtype; #ifdef HAVE_STRUCT_DIRENT_D_TYPE dtype = a->d_type; #else switch (s.st_mode & S_IFMT) { case S_IFREG: dtype = DT_REG; break; case S_IFDIR: dtype = DT_DIR; break; case S_IFCHR: dtype = DT_CHR; break; case S_IFBLK: dtype = DT_BLK; break; case S_IFLNK: dtype = DT_LNK; break; case S_IFIFO: dtype = DT_FIFO; break; case S_IFSOCK: dtype = DT_SOCK; break; default: dtype = DT_UNKNOWN; break; } #endif crm_trace("%s - wrong type (%d)", a->d_name, dtype); } else if(strstr(a->d_name, "cib-") != a->d_name) { crm_trace("%s - wrong prefix", a->d_name); } else if (crm_ends_with_ext(a->d_name, ".sig")) { crm_trace("%s - wrong suffix", a->d_name); } else { crm_debug("%s - candidate", a->d_name); rc = 1; } free(a_path); return rc; } static int cib_archive_sort(const struct dirent ** a, const struct dirent **b) { /* Order by creation date - most recently created file first */ int rc = 0; struct stat buf; time_t a_age = 0; time_t b_age = 0; char *a_path = crm_strdup_printf("%s/%s", cib_root, a[0]->d_name); char *b_path = crm_strdup_printf("%s/%s", cib_root, b[0]->d_name); if(stat(a_path, &buf) == 0) { a_age = buf.st_ctime; } if(stat(b_path, &buf) == 0) { b_age = buf.st_ctime; } free(a_path); free(b_path); if(a_age > b_age) { rc = 1; } else if(a_age < b_age) { rc = -1; } crm_trace("%s (%lu) vs. %s (%lu) : %d", a[0]->d_name, (unsigned long)a_age, b[0]->d_name, (unsigned long)b_age, rc); return rc; } xmlNode * readCibXmlFile(const char *dir, const char *file, gboolean discard_status) { struct dirent **namelist = NULL; int lpc = 0; char *sigfile = NULL; char *filename = NULL; const char *name = NULL; const char *value = NULL; const char *validation = NULL; const char *use_valgrind = getenv("PCMK_valgrind_enabled"); xmlNode *root = NULL; xmlNode *status = NULL; - if (!crm_is_writable(dir, file, CRM_DAEMON_USER, NULL, FALSE)) { + if (pcmk__daemon_can_write(dir, file) == FALSE) { cib_status = -EACCES; return NULL; } filename = crm_concat(dir, file, '/'); sigfile = crm_concat(filename, "sig", '.'); cib_status = pcmk_ok; root = retrieveCib(filename, sigfile); free(filename); free(sigfile); if (root == NULL) { crm_warn("Primary configuration corrupt or unusable, trying backups in %s", cib_root); lpc = scandir(cib_root, &namelist, cib_archive_filter, cib_archive_sort); if (lpc < 0) { crm_perror(LOG_NOTICE, "scandir(%s) failed", cib_root); } } while (root == NULL && lpc > 1) { crm_debug("Testing %d candidates", lpc); lpc--; filename = crm_strdup_printf("%s/%s", cib_root, namelist[lpc]->d_name); sigfile = crm_concat(filename, "sig", '.'); crm_info("Reading cluster configuration file %s (digest: %s)", filename, sigfile); if (cib_file_read_and_verify(filename, sigfile, &root) < 0) { crm_warn("Continuing but %s will NOT be used.", filename); } else { crm_notice("Continuing with last valid configuration archive: %s", filename); } free(namelist[lpc]); free(filename); free(sigfile); } free(namelist); if (root == NULL) { root = createEmptyCib(0); crm_warn("Continuing with an empty configuration."); } if (cib_writes_enabled && use_valgrind && (crm_is_true(use_valgrind) || strstr(use_valgrind, "pacemaker-based"))) { cib_writes_enabled = FALSE; crm_err("*** Disabling disk writes to avoid confusing Valgrind ***"); } status = find_xml_node(root, XML_CIB_TAG_STATUS, FALSE); if (discard_status && status != NULL) { /* strip out the status section if there is one */ free_xml(status); status = NULL; } if (status == NULL) { create_xml_node(root, XML_CIB_TAG_STATUS); } /* Do this before schema validation happens */ /* fill in some defaults */ name = XML_ATTR_GENERATION_ADMIN; value = crm_element_value(root, name); if (value == NULL) { crm_warn("No value for %s was specified in the configuration.", name); crm_warn("The recommended course of action is to shutdown," " run crm_verify and fix any errors it reports."); crm_warn("We will default to zero and continue but may get" " confused about which configuration to use if" " multiple nodes are powered up at the same time."); crm_xml_add_int(root, name, 0); } name = XML_ATTR_GENERATION; value = crm_element_value(root, name); if (value == NULL) { crm_xml_add_int(root, name, 0); } name = XML_ATTR_NUMUPDATES; value = crm_element_value(root, name); if (value == NULL) { crm_xml_add_int(root, name, 0); } // Unset (DC should set appropriate value) xml_remove_prop(root, XML_ATTR_DC_UUID); if (discard_status) { crm_log_xml_trace(root, "[on-disk]"); } validation = crm_element_value(root, XML_ATTR_VALIDATION); if (validate_xml(root, NULL, TRUE) == FALSE) { crm_err("CIB does not validate with %s", crm_str(validation)); cib_status = -pcmk_err_schema_validation; } else if (validation == NULL) { int version = 0; update_validation(&root, &version, 0, FALSE, FALSE); if (version > 0) { crm_notice("Enabling %s validation on" " the existing (sane) configuration", get_schema_name(version)); } else { crm_err("CIB does not validate with any known schema"); cib_status = -pcmk_err_schema_validation; } } return root; } gboolean uninitializeCib(void) { xmlNode *tmp_cib = the_cib; if (tmp_cib == NULL) { crm_debug("The CIB has already been deallocated."); return FALSE; } the_cib = NULL; crm_debug("Deallocating the CIB."); free_xml(tmp_cib); crm_debug("The CIB has been deallocated."); return TRUE; } /* * This method will free the old CIB pointer on success and the new one * on failure. */ int activateCibXml(xmlNode * new_cib, gboolean to_disk, const char *op) { if (new_cib) { xmlNode *saved_cib = the_cib; CRM_ASSERT(new_cib != saved_cib); the_cib = new_cib; free_xml(saved_cib); if (cib_writes_enabled && cib_status == pcmk_ok && to_disk) { crm_debug("Triggering CIB write for %s op", op); mainloop_set_trigger(cib_writer); } return pcmk_ok; } crm_err("Ignoring invalid CIB"); if (the_cib) { crm_warn("Reverting to last known CIB"); } else { crm_crit("Could not write out new CIB and no saved version to revert to"); } return -ENODATA; } static void cib_diskwrite_complete(mainloop_child_t * p, pid_t pid, int core, int signo, int exitcode) { if (signo) { crm_notice("Disk write process terminated with signal %d (pid=%d, core=%d)", signo, pid, core); } else { do_crm_log(exitcode == 0 ? LOG_TRACE : LOG_ERR, "Disk write process exited (pid=%d, rc=%d)", pid, exitcode); } if (exitcode != 0 && cib_writes_enabled) { crm_err("Disabling disk writes after write failure"); cib_writes_enabled = FALSE; } mainloop_trigger_complete(cib_writer); } int write_cib_contents(gpointer p) { int exit_rc = pcmk_ok; xmlNode *cib_local = NULL; /* Make a copy of the CIB to write (possibly in a forked child) */ if (p) { /* Synchronous write out */ cib_local = copy_xml(p); } else { int pid = 0; int bb_state = qb_log_ctl(QB_LOG_BLACKBOX, QB_LOG_CONF_STATE_GET, 0); /* Turn it off before the fork() to avoid: * - 2 processes writing to the same shared mem * - the child needing to disable it * (which would close it from underneath the parent) * This way, the shared mem files are already closed */ qb_log_ctl(QB_LOG_BLACKBOX, QB_LOG_CONF_ENABLED, QB_FALSE); pid = fork(); if (pid < 0) { crm_perror(LOG_ERR, "Disabling disk writes after fork failure"); cib_writes_enabled = FALSE; return FALSE; } if (pid) { /* Parent */ mainloop_child_add(pid, 0, "disk-writer", NULL, cib_diskwrite_complete); if (bb_state == QB_LOG_STATE_ENABLED) { /* Re-enable now that it it safe */ qb_log_ctl(QB_LOG_BLACKBOX, QB_LOG_CONF_ENABLED, QB_TRUE); } return -1; /* -1 means 'still work to do' */ } /* Asynchronous write-out after a fork() */ /* In theory, we can scribble on the_cib here and not affect the parent, * but let's be safe anyway. */ cib_local = copy_xml(the_cib); } /* Write the CIB */ exit_rc = cib_file_write_with_digest(cib_local, cib_root, "cib.xml"); /* A nonzero exit code will cause further writes to be disabled */ free_xml(cib_local); if (p == NULL) { crm_exit_t exit_code = CRM_EX_OK; switch (exit_rc) { case pcmk_ok: exit_code = CRM_EX_OK; break; case pcmk_err_cib_modified: exit_code = CRM_EX_DIGEST; // Existing CIB doesn't match digest break; case pcmk_err_cib_backup: // Existing CIB couldn't be backed up case pcmk_err_cib_save: // New CIB couldn't be saved exit_code = CRM_EX_CANTCREAT; break; default: exit_code = CRM_EX_ERROR; break; } /* Use _exit() because exit() could affect the parent adversely */ _exit(exit_code); } return exit_rc; } diff --git a/daemons/based/pacemaker-based.c b/daemons/based/pacemaker-based.c index dafbc98420..5d1510d776 100644 --- a/daemons/based/pacemaker-based.c +++ b/daemons/based/pacemaker-based.c @@ -1,371 +1,372 @@ /* * Copyright 2004-2018 Andrew Beekhof * * 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 #if HAVE_BZLIB_H # include #endif 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; gboolean preserve_status = FALSE; /* volatile because it may be changed in a signal handler */ volatile gboolean cib_writes_enabled = TRUE; int remote_fd = 0; int remote_tls_fd = 0; GHashTable *config_hash = NULL; GHashTable *local_notify_queue = NULL; int 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 void log_cib_client(gpointer key, gpointer value, gpointer user_data) { crm_info("Client %s", crm_client_name(value)); } /* *INDENT-OFF* */ static struct crm_option long_options[] = { /* Top-level Options */ {"help", 0, 0, '?', "\tThis text"}, {"verbose", 0, 0, 'V', "\tIncrease debug output"}, {"per-action-cib", 0, 0, 'a', "\tAdvanced use only"}, {"stand-alone", 0, 0, 's', "\tAdvanced use only"}, {"disk-writes", 0, 0, 'w', "\tAdvanced use only"}, {"cib-root", 1, 0, 'r', "\tAdvanced use only"}, {0, 0, 0, 0} }; /* *INDENT-ON* */ int main(int argc, char **argv) { int flag; int rc = 0; int index = 0; int argerr = 0; struct passwd *pwentry = NULL; crm_log_preinit(NULL, argc, argv); crm_set_options(NULL, "[options]", long_options, "Daemon for storing and replicating the cluster configuration"); crm_peer_init(); 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 = crm_get_option(argc, argv, &index); 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 */ crm_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 && safe_str_eq("metadata", argv[optind])) { cib_metadata(); return CRM_EX_OK; } if (optind > argc) { ++argerr; } if (argerr) { crm_help('?', CRM_EX_USAGE); } crm_log_init(NULL, LOG_INFO, TRUE, FALSE, argc, argv, FALSE); if (cib_root == NULL) { cib_root = CRM_CONFIG_DIR; } else { crm_notice("Using custom config location: %s", cib_root); } - if (crm_is_writable(cib_root, NULL, CRM_DAEMON_USER, CRM_DAEMON_GROUP, FALSE) == FALSE) { - crm_err("Bad permissions on %s. Terminating", cib_root); - fprintf(stderr, "ERROR: Bad permissions on %s. See logs for details\n", 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; } /* read local config file */ cib_init(); // This should not be reachable CRM_CHECK(crm_hash_table_size(client_connections) == 0, crm_warn("Not all clients gone at exit")); g_hash_table_foreach(client_connections, log_cib_client, NULL); cib_cleanup(); crm_info("Done"); return CRM_EX_OK; } void cib_cleanup(void) { crm_peer_destroy(); if (local_notify_queue) { g_hash_table_destroy(local_notify_queue); } crm_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_err("Corosync connection lost! Exiting."); terminate_cib(__FUNCTION__, 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() && is_not_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) && crm_hash_table_size(client_connections) == 0) { crm_info("No more peers"); terminate_cib(__FUNCTION__, -1); } break; } } int 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 = crm_str_table_new(); 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"); } cib_ipc_servers_init(&ipcs_ro, &ipcs_rw, &ipcs_shm, &ipc_ro_callbacks, &ipc_rw_callbacks); if (stand_alone) { cib_is_master = TRUE; } /* Create the mainloop and run it... */ mainloop = g_main_loop_new(NULL, FALSE); crm_info("Starting %s mainloop", crm_system_name); 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); cib_ipc_servers_destroy(ipcs_ro, ipcs_rw, ipcs_shm); return crm_exit(CRM_EX_OK); } 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"); 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"); remote_fd = init_remote_listener(port, FALSE); } crm_info("CIB Initialization completed successfully"); } return active; } diff --git a/daemons/controld/pacemaker-controld.c b/daemons/controld/pacemaker-controld.c index 62ca5124d5..1f3fa8bfdd 100644 --- a/daemons/controld/pacemaker-controld.c +++ b/daemons/controld/pacemaker-controld.c @@ -1,167 +1,168 @@ /* * Copyright 2004-2018 Andrew Beekhof * * 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 #define OPTARGS "hV" void usage(const char *cmd, int exit_status); int crmd_init(void); void crmd_hamsg_callback(const xmlNode * msg, void *private_data); extern void init_dotfile(void); GMainLoop *crmd_mainloop = NULL; /* *INDENT-OFF* */ static struct crm_option long_options[] = { /* Top-level Options */ {"help", 0, 0, '?', "\tThis text"}, {"verbose", 0, 0, 'V', "\tIncrease debug output"}, {0, 0, 0, 0} }; /* *INDENT-ON* */ int main(int argc, char **argv) { int flag; int index = 0; int argerr = 0; crmd_mainloop = g_main_loop_new(NULL, FALSE); crm_log_preinit(NULL, argc, argv); crm_set_options(NULL, "[options]", long_options, "Daemon for aggregating resource and node failures as well as co-ordinating the cluster's response"); while (1) { flag = crm_get_option(argc, argv, &index); if (flag == -1) break; switch (flag) { case 'V': crm_bump_log_level(argc, argv); break; case 'h': /* Help message */ crm_help(flag, CRM_EX_OK); break; default: ++argerr; break; } } if (argc - optind == 1 && safe_str_eq("metadata", argv[optind])) { crmd_metadata(); return CRM_EX_OK; } else if (argc - optind == 1 && safe_str_eq("version", argv[optind])) { fprintf(stdout, "CRM Version: %s (%s)\n", PACEMAKER_VERSION, BUILD_VERSION); return CRM_EX_OK; } crm_log_init(NULL, LOG_INFO, TRUE, FALSE, argc, argv, FALSE); crm_info("CRM Git Version: %s (%s)", PACEMAKER_VERSION, BUILD_VERSION); if (optind > argc) { ++argerr; } if (argerr) { crm_help('?', CRM_EX_USAGE); } - if (crm_is_writable(PE_STATE_DIR, NULL, CRM_DAEMON_USER, CRM_DAEMON_GROUP, FALSE) == FALSE) { - crm_err("Bad permissions on " PE_STATE_DIR ". Terminating"); - fprintf(stderr, "ERROR: Bad permissions on " PE_STATE_DIR ". See logs for details\n"); + if (pcmk__daemon_can_write(PE_STATE_DIR, NULL) == FALSE) { + crm_err("Terminating due to bad permissions on " PE_STATE_DIR); + fprintf(stderr, + "ERROR: Bad permissions on " PE_STATE_DIR " (see logs for details)\n"); fflush(stderr); return CRM_EX_FATAL; - } else if (crm_is_writable(CRM_CONFIG_DIR, NULL, CRM_DAEMON_USER, CRM_DAEMON_GROUP, FALSE) == - FALSE) { - crm_err("Bad permissions on " CRM_CONFIG_DIR ". Terminating"); - fprintf(stderr, "ERROR: Bad permissions on " CRM_CONFIG_DIR ". See logs for details\n"); + } else if (pcmk__daemon_can_write(CRM_CONFIG_DIR, NULL) == FALSE) { + crm_err("Terminating due to bad permissions on " CRM_CONFIG_DIR); + fprintf(stderr, + "ERROR: Bad permissions on " CRM_CONFIG_DIR " (see logs for details)\n"); fflush(stderr); return CRM_EX_FATAL; } return crmd_init(); } static void log_deprecation_warnings() { // Add deprecations here as needed } int crmd_init(void) { crm_exit_t exit_code = CRM_EX_OK; enum crmd_fsa_state state; log_deprecation_warnings(); fsa_state = S_STARTING; fsa_input_register = 0; /* zero out the regester */ init_dotfile(); crm_debug("Starting %s", crm_system_name); register_fsa_input(C_STARTUP, I_STARTUP, NULL); crm_peer_init(); state = s_crmd_fsa(C_STARTUP); if (state == S_PENDING || state == S_STARTING) { /* Create the mainloop and run it... */ crm_trace("Starting %s's mainloop", crm_system_name); #ifdef REALTIME_SUPPORT static int crm_realtime = 1; if (crm_realtime == 1) { cl_enable_realtime(); } else if (crm_realtime == 0) { cl_disable_realtime(); } cl_make_realtime(SCHED_RR, 5, 64, 64); #endif g_main_loop_run(crmd_mainloop); if (is_set(fsa_input_register, R_STAYDOWN)) { crm_info("Inhibiting automated respawn"); exit_code = CRM_EX_FATAL; } } else { crm_err("Startup of %s failed. Current state: %s", crm_system_name, fsa_state2string(state)); exit_code = CRM_EX_ERROR; } crm_info("%s[%lu] exiting with status %d (%s)", crm_system_name, (unsigned long) getpid(), exit_code, crm_exit_str(exit_code)); return crmd_fast_exit(exit_code); } diff --git a/daemons/schedulerd/pacemaker-schedulerd.c b/daemons/schedulerd/pacemaker-schedulerd.c index 6c6fbb4fb0..50b2f39622 100644 --- a/daemons/schedulerd/pacemaker-schedulerd.c +++ b/daemons/schedulerd/pacemaker-schedulerd.c @@ -1,182 +1,183 @@ /* * Copyright 2004-2018 Andrew Beekhof * * 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 #define OPTARGS "hVc" GMainLoop *mainloop = NULL; qb_ipcs_service_t *ipcs = NULL; void pengine_shutdown(int nsig); static int32_t pe_ipc_accept(qb_ipcs_connection_t * c, uid_t uid, gid_t gid) { crm_trace("Connection %p", c); if (crm_client_new(c, uid, gid) == NULL) { return -EIO; } return 0; } static void pe_ipc_created(qb_ipcs_connection_t * c) { crm_trace("Connection %p", c); } gboolean process_pe_message(xmlNode * msg, xmlNode * xml_data, crm_client_t * sender); static int32_t pe_ipc_dispatch(qb_ipcs_connection_t * qbc, void *data, size_t size) { uint32_t id = 0; uint32_t flags = 0; crm_client_t *c = crm_client_get(qbc); xmlNode *msg = crm_ipcs_recv(c, data, size, &id, &flags); crm_ipcs_send_ack(c, id, flags, "ack", __FUNCTION__, __LINE__); if (msg != NULL) { xmlNode *data_xml = get_message_xml(msg, F_CRM_DATA); process_pe_message(msg, data_xml, c); free_xml(msg); } return 0; } /* Error code means? */ static int32_t pe_ipc_closed(qb_ipcs_connection_t * c) { crm_client_t *client = crm_client_get(c); if (client == NULL) { return 0; } crm_trace("Connection %p", c); crm_client_destroy(client); return 0; } static void pe_ipc_destroy(qb_ipcs_connection_t * c) { crm_trace("Connection %p", c); pe_ipc_closed(c); } struct qb_ipcs_service_handlers ipc_callbacks = { .connection_accept = pe_ipc_accept, .connection_created = pe_ipc_created, .msg_process = pe_ipc_dispatch, .connection_closed = pe_ipc_closed, .connection_destroyed = pe_ipc_destroy }; /* *INDENT-OFF* */ static struct crm_option long_options[] = { /* Top-level Options */ {"help", 0, 0, '?', "\tThis text"}, {"verbose", 0, 0, 'V', "\tIncrease debug output"}, {0, 0, 0, 0} }; /* *INDENT-ON* */ int main(int argc, char **argv) { int flag; int index = 0; int argerr = 0; crm_log_preinit(NULL, argc, argv); crm_set_options(NULL, "[options]", long_options, "Daemon for calculating the cluster's response to events"); mainloop_add_signal(SIGTERM, pengine_shutdown); while (1) { flag = crm_get_option(argc, argv, &index); if (flag == -1) break; switch (flag) { case 'V': crm_bump_log_level(argc, argv); break; case 'h': /* Help message */ crm_help('?', CRM_EX_OK); break; default: ++argerr; break; } } if (argc - optind == 1 && safe_str_eq("metadata", argv[optind])) { pe_metadata(); return CRM_EX_OK; } if (optind > argc) { ++argerr; } if (argerr) { crm_help('?', CRM_EX_USAGE); } crm_log_init(NULL, LOG_INFO, TRUE, FALSE, argc, argv, FALSE); - if (crm_is_writable(PE_STATE_DIR, NULL, CRM_DAEMON_USER, CRM_DAEMON_GROUP, FALSE) == FALSE) { - crm_err("Bad permissions on " PE_STATE_DIR ". Terminating"); - fprintf(stderr, "ERROR: Bad permissions on " PE_STATE_DIR ". See logs for details\n"); + if (pcmk__daemon_can_write(PE_STATE_DIR, NULL) == FALSE) { + crm_err("Terminating due to bad permissions on " PE_STATE_DIR); + fprintf(stderr, + "ERROR: Bad permissions on " PE_STATE_DIR " (see logs for details)\n"); fflush(stderr); return CRM_EX_FATAL; } crm_debug("Init server comms"); ipcs = mainloop_add_ipc_server(CRM_SYSTEM_PENGINE, QB_IPC_SHM, &ipc_callbacks); if (ipcs == NULL) { crm_err("Failed to create IPC server: shutting down and inhibiting respawn"); crm_exit(CRM_EX_FATAL); } /* Create the mainloop and run it... */ crm_info("Starting %s", crm_system_name); mainloop = g_main_loop_new(NULL, FALSE); g_main_loop_run(mainloop); crm_info("Exiting %s", crm_system_name); return crm_exit(CRM_EX_OK); } void pengine_shutdown(int nsig) { mainloop_del_ipc_server(ipcs); crm_exit(CRM_EX_OK); } diff --git a/include/crm/common/internal.h b/include/crm/common/internal.h index 4aa0c49c63..d33f041a5d 100644 --- a/include/crm/common/internal.h +++ b/include/crm/common/internal.h @@ -1,148 +1,146 @@ /* * Copyright 2015-2018 Andrew Beekhof * * This source code is licensed under the GNU Lesser General Public License * version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY. */ #ifndef CRM_COMMON_INTERNAL__H #define CRM_COMMON_INTERNAL__H #include /* for gboolean */ #include /* for struct dirent */ #include /* for getpid() */ #include /* for uid_t and gid_t */ #include /* internal I/O utilities (from io.c) */ char *generate_series_filename(const char *directory, const char *series, int sequence, gboolean bzip); int get_last_sequence(const char *directory, const char *series); void write_last_sequence(const char *directory, const char *series, int sequence, int max); int crm_chown_last_sequence(const char *directory, const char *series, uid_t uid, gid_t gid); -gboolean crm_is_writable(const char *dir, const char *file, const char *user, const char *group, - gboolean need_both); - +bool pcmk__daemon_can_write(const char *dir, const char *file); void crm_sync_directory(const char *name); char *crm_read_contents(const char *filename); int crm_write_sync(int fd, const char *contents); int crm_set_nonblocking(int fd); const char *crm_get_tmpdir(void); /* internal procfs utilities (from procfs.c) */ int crm_procfs_process_info(struct dirent *entry, char *name, int *pid); int crm_procfs_pid_of(const char *name); unsigned int crm_procfs_num_cores(void); /* internal XML schema functions (from xml.c) */ void crm_schema_init(void); void crm_schema_cleanup(void); /* internal functions related to process IDs (from pid.c) */ int crm_pid_active(long pid, const char *daemon); long crm_pidfile_inuse(const char *filename, long mypid, const char *daemon); long crm_read_pidfile(const char *filename); int crm_lock_pidfile(const char *filename, const char *name); /* interal functions related to resource operations (from operations.c) */ char *generate_op_key(const char *rsc_id, const char *op_type, guint interval_ms); char *generate_notify_key(const char *rsc_id, const char *notify_type, const char *op_type); char *generate_transition_magic(const char *transition_key, int op_status, int op_rc); char *generate_transition_key(int action, int transition_id, int target_rc, const char *node); void filter_action_parameters(xmlNode *param_set, const char *version); xmlNode *create_operation_update(xmlNode *parent, lrmd_event_data_t *event, const char *caller_version, int target_rc, const char *node, const char *origin, int level); /* internal generic string functions (from strings.c) */ long long crm_int_helper(const char *text, char **end_text); guint crm_parse_ms(const char *text); bool crm_starts_with(const char *str, const char *prefix); gboolean crm_ends_with(const char *s, const char *match); gboolean crm_ends_with_ext(const char *s, const char *match); char *add_list_element(char *list, const char *value); bool crm_compress_string(const char *data, int length, int max, char **result, unsigned int *result_len); gint crm_alpha_sort(gconstpointer a, gconstpointer b); static inline char * crm_concat(const char *prefix, const char *suffix, char join) { CRM_ASSERT(prefix && suffix); return crm_strdup_printf("%s%c%s", prefix, join, suffix); } static inline int crm_strlen_zero(const char *s) { return !s || *s == '\0'; } static inline char * crm_getpid_s() { return crm_strdup_printf("%lu", (unsigned long) getpid()); } /* convenience functions for failure-related node attributes */ #define CRM_FAIL_COUNT_PREFIX "fail-count" #define CRM_LAST_FAILURE_PREFIX "last-failure" /*! * \internal * \brief Generate a failure-related node attribute name for a resource * * \param[in] prefix Start of attribute name * \param[in] rsc_id Resource name * \param[in] op Operation name * \param[in] interval_ms Operation interval * * \return Newly allocated string with attribute name * * \note Failure attributes are named like PREFIX-RSC#OP_INTERVAL (for example, * "fail-count-myrsc#monitor_30000"). The '#' is used because it is not * a valid character in a resource ID, to reliably distinguish where the * operation name begins. The '_' is used simply to be more comparable to * action labels like "myrsc_monitor_30000". */ static inline char * crm_fail_attr_name(const char *prefix, const char *rsc_id, const char *op, guint interval_ms) { CRM_CHECK(prefix && rsc_id && op, return NULL); return crm_strdup_printf("%s-%s#%s_%u", prefix, rsc_id, op, interval_ms); } static inline char * crm_failcount_name(const char *rsc_id, const char *op, guint interval_ms) { return crm_fail_attr_name(CRM_FAIL_COUNT_PREFIX, rsc_id, op, interval_ms); } static inline char * crm_lastfailure_name(const char *rsc_id, const char *op, guint interval_ms) { return crm_fail_attr_name(CRM_LAST_FAILURE_PREFIX, rsc_id, op, interval_ms); } #endif /* CRM_COMMON_INTERNAL__H */ diff --git a/lib/common/io.c b/lib/common/io.c index 448e380fff..fd7cbef69c 100644 --- a/lib/common/io.c +++ b/lib/common/io.c @@ -1,467 +1,505 @@ /* - * Copyright (C) 2004 Andrew Beekhof + * Copyright 2004-2018 Andrew Beekhof * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 2.1 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * 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 #include /*! * \brief Create a directory, including any parent directories needed * * \param[in] path_c Pathname of the directory to create * \param[in] mode Permissions to be used (with current umask) when creating * * \note This logs errors but does not return them to the caller. */ void crm_build_path(const char *path_c, mode_t mode) { int offset = 1, len = 0; char *path = strdup(path_c); CRM_CHECK(path != NULL, return); for (len = strlen(path); offset < len; offset++) { if (path[offset] == '/') { path[offset] = 0; if (mkdir(path, mode) < 0 && errno != EEXIST) { crm_perror(LOG_ERR, "Could not create directory '%s'", path); break; } path[offset] = '/'; } } if (mkdir(path, mode) < 0 && errno != EEXIST) { crm_perror(LOG_ERR, "Could not create directory '%s'", path); } free(path); } /*! * \internal * \brief Allocate and create a file path using a sequence number * * \param[in] directory Directory that contains the file series * \param[in] series Start of file name * \param[in] sequence Sequence number (MUST be less than 33 digits) * \param[in] bzip Whether to use ".bz2" instead of ".raw" as extension * * \return Newly allocated file path, or NULL on error * \note Caller is responsible for freeing the returned memory */ char * generate_series_filename(const char *directory, const char *series, int sequence, gboolean bzip) { const char *ext = "raw"; CRM_CHECK(directory != NULL, return NULL); CRM_CHECK(series != NULL, return NULL); #if !HAVE_BZLIB_H bzip = FALSE; #endif if (bzip) { ext = "bz2"; } return crm_strdup_printf("%s/%s-%d.%s", directory, series, sequence, ext); } /*! * \internal * \brief Read and return sequence number stored in a file series' .last file * * \param[in] directory Directory that contains the file series * \param[in] series Start of file name * * \return The last sequence number, or 0 on error */ int get_last_sequence(const char *directory, const char *series) { FILE *file_strm = NULL; int start = 0, length = 0, read_len = 0; char *series_file = NULL; char *buffer = NULL; int seq = 0; CRM_CHECK(directory != NULL, return 0); CRM_CHECK(series != NULL, return 0); series_file = crm_strdup_printf("%s/%s.last", directory, series); file_strm = fopen(series_file, "r"); if (file_strm == NULL) { crm_debug("Series file %s does not exist", series_file); free(series_file); return 0; } /* see how big the file is */ start = ftell(file_strm); fseek(file_strm, 0L, SEEK_END); length = ftell(file_strm); fseek(file_strm, 0L, start); CRM_ASSERT(length >= 0); CRM_ASSERT(start == ftell(file_strm)); if (length <= 0) { crm_info("%s was not valid", series_file); free(buffer); buffer = NULL; } else { crm_trace("Reading %d bytes from file", length); buffer = calloc(1, (length + 1)); read_len = fread(buffer, 1, length, file_strm); if (read_len != length) { crm_err("Calculated and read bytes differ: %d vs. %d", length, read_len); free(buffer); buffer = NULL; } } seq = crm_parse_int(buffer, "0"); fclose(file_strm); crm_trace("Found %d in %s", seq, series_file); free(series_file); free(buffer); return seq; } /*! * \internal * \brief Write sequence number to a file series' .last file * * \param[in] directory Directory that contains the file series * \param[in] series Start of file name * \param[in] sequence Sequence number to write * \param[in] max Maximum sequence value, after which sequence is reset to 0 * * \note This function logs some errors but does not return any to the caller */ void write_last_sequence(const char *directory, const char *series, int sequence, int max) { int rc = 0; FILE *file_strm = NULL; char *series_file = NULL; CRM_CHECK(directory != NULL, return); CRM_CHECK(series != NULL, return); if (max == 0) { return; } if (max > 0 && sequence >= max) { sequence = 0; } series_file = crm_strdup_printf("%s/%s.last", directory, series); file_strm = fopen(series_file, "w"); if (file_strm != NULL) { rc = fprintf(file_strm, "%d", sequence); if (rc < 0) { crm_perror(LOG_ERR, "Cannot write to series file %s", series_file); } } else { crm_err("Cannot open series file %s for writing", series_file); } if (file_strm != NULL) { fflush(file_strm); fclose(file_strm); } crm_trace("Wrote %d to %s", sequence, series_file); free(series_file); } /*! * \internal * \brief Change the owner and group of a file series' .last file * * \param[in] dir Directory that contains series * \param[in] uid Uid of desired file owner * \param[in] gid Gid of desired file group * * \return 0 on success, -1 on error (in which case errno will be set) * \note The caller must have the appropriate privileges. */ int crm_chown_last_sequence(const char *directory, const char *series, uid_t uid, gid_t gid) { char *series_file = NULL; int rc; CRM_CHECK((directory != NULL) && (series != NULL), errno = EINVAL; return -1); series_file = crm_strdup_printf("%s/%s.last", directory, series); CRM_CHECK(series_file != NULL, return -1); rc = chown(series_file, uid, gid); free(series_file); return rc; } +static bool +pcmk__daemon_user_can_write(const char *target_name, struct stat *target_stat) +{ + struct passwd *sys_user = NULL; + + errno = 0; + sys_user = getpwnam(CRM_DAEMON_USER); + if (sys_user == NULL) { + crm_notice("Could not find user %s: %s", + CRM_DAEMON_USER, pcmk_strerror(errno)); + return FALSE; + } + if (target_stat->st_uid != sys_user->pw_uid) { + crm_notice("%s is not owned by user %s " CRM_XS " uid %d != %d", + target_name, CRM_DAEMON_USER, sys_user->pw_uid, + target_stat->st_uid); + return FALSE; + } + if ((target_stat->st_mode & (S_IRUSR | S_IWUSR)) == 0) { + crm_notice("%s is not readable and writable by user %s " + CRM_XS " st_mode=0%lo", + target_name, CRM_DAEMON_USER, + (unsigned long) target_stat->st_mode); + return FALSE; + } + return TRUE; +} + +static bool +pcmk__daemon_group_can_write(const char *target_name, struct stat *target_stat) +{ + struct group *sys_grp = NULL; + + errno = 0; + sys_grp = getgrnam(CRM_DAEMON_GROUP); + if (sys_grp == NULL) { + crm_notice("Could not find group %s: %s", + CRM_DAEMON_GROUP, pcmk_strerror(errno)); + return FALSE; + } + + if (target_stat->st_gid != sys_grp->gr_gid) { + crm_notice("%s is not owned by group %s " CRM_XS " uid %d != %d", + target_name, CRM_DAEMON_GROUP, + sys_grp->gr_gid, target_stat->st_gid); + return FALSE; + } + + if ((target_stat->st_mode & (S_IRGRP | S_IWGRP)) == 0) { + crm_notice("%s is not readable and writable by group %s " + CRM_XS " st_mode=0%lo", + target_name, CRM_DAEMON_GROUP, + (unsigned long) target_stat->st_mode); + return FALSE; + } + return TRUE; +} + /*! * \internal - * \brief Return whether a directory or file is writable by a user/group + * \brief Check whether a directory or file is writable by the cluster daemon + * + * Return TRUE if either the cluster daemon user or cluster daemon group has + * write permission on a specified file or directory. * - * \param[in] dir Directory to check or that contains file - * \param[in] file File name to check (or NULL to check directory) - * \param[in] user Name of user that should have write permission - * \param[in] group Name of group that should have write permission - * \param[in] need_both Whether both user and group must be able to write + * \param[in] dir Directory to check (this argument must be specified, and + * the directory must exist) + * \param[in] file File to check (only the directory will be checked if this + * argument is not specified or the file does not exist) * - * \return TRUE if permissions match, FALSE if they don't or on error + * \return TRUE if target is writable by cluster daemon, FALSE otherwise */ -gboolean -crm_is_writable(const char *dir, const char *file, - const char *user, const char *group, gboolean need_both) +bool +pcmk__daemon_can_write(const char *dir, const char *file) { - int s_res = -1; + int s_res = 0; struct stat buf; char *full_file = NULL; const char *target = NULL; - gboolean pass = TRUE; - gboolean readwritable = FALSE; - + // Caller must supply directory CRM_ASSERT(dir != NULL); + + // If file is given, check whether it exists as a regular file if (file != NULL) { full_file = crm_concat(dir, file, '/'); target = full_file; + s_res = stat(full_file, &buf); - if (s_res == 0 && S_ISREG(buf.st_mode) == FALSE) { - crm_err("%s must be a regular file", target); - pass = FALSE; - goto out; + if (s_res < 0) { + crm_notice("%s not found: %s", target, pcmk_strerror(errno)); + free(full_file); + full_file = NULL; + target = NULL; + + } else if (S_ISREG(buf.st_mode) == FALSE) { + crm_err("%s must be a regular file " CRM_XS " st_mode=0%lo", + target, (unsigned long) buf.st_mode); + free(full_file); + return FALSE; } } - if (s_res != 0) { + // If file is not given, ensure dir exists as directory + if (target == NULL) { target = dir; s_res = stat(dir, &buf); - if (s_res != 0) { - crm_err("%s must exist and be a directory", dir); - pass = FALSE; - goto out; + if (s_res < 0) { + crm_err("%s not found: %s", dir, pcmk_strerror(errno)); + return FALSE; } else if (S_ISDIR(buf.st_mode) == FALSE) { - crm_err("%s must be a directory", dir); - pass = FALSE; + crm_err("%s must be a directory " CRM_XS " st_mode=0%lo", + dir, (unsigned long) buf.st_mode); + return FALSE; } } - if (user) { - struct passwd *sys_user = NULL; + if (!pcmk__daemon_user_can_write(target, &buf) + && !pcmk__daemon_group_can_write(target, &buf)) { - sys_user = getpwnam(user); - readwritable = (sys_user != NULL - && buf.st_uid == sys_user->pw_uid && (buf.st_mode & (S_IRUSR | S_IWUSR))); - if (readwritable == FALSE) { - crm_err("%s must be owned and r/w by user %s", target, user); - if (need_both) { - pass = FALSE; - } - } - } - - if (group) { - struct group *sys_grp = getgrnam(group); - - readwritable = (sys_grp != NULL - && buf.st_gid == sys_grp->gr_gid && (buf.st_mode & (S_IRGRP | S_IWGRP))); - if (readwritable == FALSE) { - if (need_both || user == NULL) { - pass = FALSE; - crm_err("%s must be owned and r/w by group %s", target, group); - } else { - crm_warn("%s should be owned and r/w by group %s", target, group); - } - } + crm_err("%s must be owned and writable by either user %s or group %s " + CRM_XS " st_mode=0%ol", + target, CRM_DAEMON_USER, CRM_DAEMON_GROUP, + (unsigned long) buf.st_mode); + free(full_file); + return FALSE; } - out: free(full_file); - return pass; + return TRUE; } /*! * \internal * \brief Flush and sync a directory to disk * * \param[in] name Directory to flush and sync * \note This function logs errors but does not return them to the caller */ void crm_sync_directory(const char *name) { int fd; DIR *directory; directory = opendir(name); if (directory == NULL) { crm_perror(LOG_ERR, "Could not open %s for syncing", name); return; } fd = dirfd(directory); if (fd < 0) { crm_perror(LOG_ERR, "Could not obtain file descriptor for %s", name); return; } if (fsync(fd) < 0) { crm_perror(LOG_ERR, "Could not sync %s", name); } if (closedir(directory) < 0) { crm_perror(LOG_ERR, "Could not close %s after fsync", name); } } /*! * \internal * \brief Allocate, read and return the contents of a file * * \param[in] filename Name of file to read * * \return Newly allocated memory with contents of file, or NULL on error * \note On success, the caller is responsible for freeing the returned memory; * on error, errno will be 0 (indicating file was nonexistent or empty) * or one of the errno values set by fopen, ftell, or calloc */ char * crm_read_contents(const char *filename) { char *contents = NULL; FILE *fp; int length, read_len; errno = 0; /* enable caller to distinguish error from empty file */ fp = fopen(filename, "r"); if (fp == NULL) { return NULL; } fseek(fp, 0L, SEEK_END); length = ftell(fp); if (length > 0) { contents = calloc(length + 1, sizeof(char)); if (contents == NULL) { fclose(fp); return NULL; } crm_trace("Reading %d bytes from %s", length, filename); rewind(fp); read_len = fread(contents, 1, length, fp); /* Coverity: False positive */ if (read_len != length) { free(contents); contents = NULL; } } fclose(fp); return contents; } /*! * \internal * \brief Write text to a file, flush and sync it to disk, then close the file * * \param[in] fd File descriptor opened for writing * \param[in] contents String to write to file * * \return 0 on success, -1 on error (in which case errno will be set) */ int crm_write_sync(int fd, const char *contents) { int rc = 0; FILE *fp = fdopen(fd, "w"); if (fp == NULL) { return -1; } if ((contents != NULL) && (fprintf(fp, "%s", contents) < 0)) { rc = -1; } if (fflush(fp) != 0) { rc = -1; } if (fsync(fileno(fp)) < 0) { rc = -1; } fclose(fp); return rc; } /*! * \internal * \brief Set a file descriptor to non-blocking * * \param[in] fd File descriptor to use * * \return pcmk_ok on success, -errno on error */ int crm_set_nonblocking(int fd) { int flag = fcntl(fd, F_GETFL); if (flag < 0) { return -errno; } if (fcntl(fd, F_SETFL, flag | O_NONBLOCK) < 0) { return -errno; } return pcmk_ok; } const char * crm_get_tmpdir() { const char *dir = getenv("TMPDIR"); return (dir && (*dir == '/'))? dir : "/tmp"; }