diff --git a/daemons/based/based_io.c b/daemons/based/based_io.c index 25e5c65afe..b6a52b7434 100644 --- a/daemons/based/based_io.c +++ b/daemons/based/based_io.c @@ -1,478 +1,479 @@ /* * Copyright 2004-2024 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 #include #include #include #include #include #include crm_trigger_t *cib_writer = NULL; 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); umask(S_IWGRP | S_IWOTH | S_IROTH); new_fd = mkstemp(new); if ((new_fd < 0) || (rename(old, new) < 0)) { crm_err("Couldn't archive unusable file %s (disabling disk writes and continuing)", old); cib_writes_enabled = FALSE; } else { crm_err("Archived unusable file %s as %s", old, new); } 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_rc_str(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 (pcmk__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 *sigfilepath = NULL; char *filename = NULL; const char *name = NULL; const char *value = NULL; const char *validation = NULL; const char *use_valgrind = pcmk__env_option(PCMK__ENV_VALGRIND_ENABLED); xmlNode *root = NULL; xmlNode *status = NULL; sigfile = crm_strdup_printf("%s.sig", file); if (pcmk__daemon_can_write(dir, file) == FALSE || pcmk__daemon_can_write(dir, sigfile) == FALSE) { cib_status = -EACCES; return NULL; } filename = crm_strdup_printf("%s/%s", dir, file); sigfilepath = crm_strdup_printf("%s/%s", dir, sigfile); free(sigfile); cib_status = pcmk_ok; root = retrieveCib(filename, sigfilepath); free(filename); free(sigfilepath); 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_err("scandir(%s) failed: %s", cib_root, pcmk_rc_str(errno)); } } 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_strdup_printf("%s.sig", filename); 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 = pcmk__xe_first_child(root, PCMK_XE_STATUS, NULL, NULL); if (discard_status && status != NULL) { // Strip out the PCMK_XE_STATUS section if there is one free_xml(status); status = NULL; } if (status == NULL) { pcmk__xe_create(root, PCMK_XE_STATUS); } /* Do this before schema validation happens */ /* fill in some defaults */ name = PCMK_XA_ADMIN_EPOCH; 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 = PCMK_XA_EPOCH; value = crm_element_value(root, name); if (value == NULL) { crm_xml_add_int(root, name, 0); } name = PCMK_XA_NUM_UPDATES; value = crm_element_value(root, name); if (value == NULL) { crm_xml_add_int(root, name, 0); } // Unset (DC should set appropriate value) pcmk__xe_remove_attr(root, PCMK_XA_DC_UUID); if (discard_status) { crm_log_xml_trace(root, "[on-disk]"); } validation = crm_element_value(root, PCMK_XA_VALIDATE_WITH); if (!pcmk__configured_schema_validates(root)) { crm_err("CIB does not validate with %s", pcmk__s(validation, "no schema specified")); cib_status = -pcmk_err_schema_validation; // @COMPAT Not specifying validate-with is deprecated since 2.1.8 } else if (validation == NULL) { pcmk__update_schema(&root, NULL, false, false); validation = crm_element_value(root, PCMK_XA_VALIDATE_WITH); if (validation != NULL) { crm_notice("Enabling %s validation on" " the existing (sane) configuration", validation); } 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; pcmk__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) { const char *errmsg = "Could not write CIB to disk"; if ((exitcode != 0) && cib_writes_enabled) { cib_writes_enabled = FALSE; errmsg = "Disabling CIB disk writes after failure"; } if ((signo == 0) && (exitcode == 0)) { crm_trace("Disk write [%d] succeeded", (int) pid); } else if (signo == 0) { crm_err("%s: process %d exited %d", errmsg, (int) pid, exitcode); } else { crm_err("%s: process %d terminated with signal %d (%s)%s", errmsg, (int) pid, signo, strsignal(signo), (core? " and dumped core" : "")); } 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 = pcmk__xml_copy(NULL, 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_err("Disabling disk writes after fork failure: %s", pcmk_rc_str(errno)); 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 = pcmk__xml_copy(NULL, 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 */ + pcmk_common_cleanup(); _exit(exit_code); } return exit_rc; } diff --git a/daemons/execd/remoted_schemas.c b/daemons/execd/remoted_schemas.c index cddbd7cc3f..d8ee8f405e 100644 --- a/daemons/execd/remoted_schemas.c +++ b/daemons/execd/remoted_schemas.c @@ -1,287 +1,290 @@ /* * Copyright 2023-2024 the Pacemaker project contributors * * The version control history for this file may have further details. * * This source code is licensed under the GNU Lesser General Public License * version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY. */ #include #include #include #include #include #include #include #include #include #include #include "pacemaker-execd.h" static pid_t schema_fetch_pid = 0; static int rm_files(const char *pathname, const struct stat *sbuf, int type, struct FTW *ftwb) { /* Don't delete PCMK__REMOTE_SCHEMA_DIR . */ if (ftwb->level == 0) { return 0; } if (remove(pathname) != 0) { int rc = errno; crm_err("Could not remove %s: %s", pathname, pcmk_rc_str(rc)); return -1; } return 0; } static void clean_up_extra_schema_files(void) { const char *remote_schema_dir = pcmk__remote_schema_dir(); struct stat sb; int rc; rc = stat(remote_schema_dir, &sb); if (rc == -1) { if (errno == ENOENT) { /* If the directory doesn't exist, try to make it first. */ if (mkdir(remote_schema_dir, 0755) != 0) { rc = errno; crm_err("Could not create directory for schemas: %s", pcmk_rc_str(rc)); } } else { rc = errno; crm_err("Could not create directory for schemas: %s", pcmk_rc_str(rc)); } } else if (!S_ISDIR(sb.st_mode)) { /* If something exists with the same name that's not a directory, that's * an error. */ crm_err("%s already exists but is not a directory", remote_schema_dir); } else { /* It's a directory - clear it out so we can download potentially new * schema files. */ rc = nftw(remote_schema_dir, rm_files, 10, FTW_DEPTH|FTW_MOUNT|FTW_PHYS); if (rc != 0) { crm_err("Could not remove %s: %s", remote_schema_dir, pcmk_rc_str(rc)); } } } static void write_extra_schema_file(xmlNode *xml, void *user_data) { const char *remote_schema_dir = pcmk__remote_schema_dir(); const char *file = NULL; char *path = NULL; int rc; file = crm_element_value(xml, PCMK_XA_PATH); if (file == NULL) { crm_warn("No destination path given in schema request"); return; } path = crm_strdup_printf("%s/%s", remote_schema_dir, file); /* The schema is a CDATA node, which is a child of the node. Traverse * all children and look for the first CDATA child. There can't be more than * one because we only have one file attribute on the parent. */ for (xmlNode *child = xml->children; child != NULL; child = child->next) { FILE *stream = NULL; if (child->type != XML_CDATA_SECTION_NODE) { continue; } stream = fopen(path, "w+"); if (stream == NULL) { crm_warn("Could not write schema file %s: %s", path, strerror(errno)); } else { rc = fprintf(stream, "%s", child->content); if (rc < 0) { crm_warn("Could not write schema file %s: %s", path, strerror(errno)); } fclose(stream); } break; } free(path); } static void get_schema_files(void) { int rc = pcmk_rc_ok; cib_t *cib = NULL; xmlNode *reply; cib = cib_new(); if (cib == NULL) { + pcmk_common_cleanup(); _exit(CRM_EX_OSERR); } rc = cib->cmds->signon(cib, crm_system_name, cib_query); rc = pcmk_legacy2rc(rc); if (rc != pcmk_rc_ok) { crm_err("Could not connect to the CIB manager: %s", pcmk_rc_str(rc)); + pcmk_common_cleanup(); _exit(pcmk_rc2exitc(rc)); } rc = cib->cmds->fetch_schemas(cib, &reply, pcmk__highest_schema_name(), cib_sync_call); if (rc != pcmk_ok) { crm_err("Could not get schema files: %s", pcmk_strerror(rc)); rc = pcmk_legacy2rc(rc); } else if (reply->children != NULL) { /* The returned document looks something like this: * * * * * * * * * ... * * * * ... * * * * * All the and tags are really just there for organizing * the XML a little better. What we really care about are the nodes, * and specifically the path attributes and the CDATA children (not shown) * of each. We can use an xpath query to reach down and get all the * nodes at once. * * If we already have the latest schema version, or we asked for one later * than what the cluster supports, we'll get back an empty node, * so all this will continue to work. It just won't do anything. */ crm_foreach_xpath_result(reply, "//" PCMK_XA_FILE, write_extra_schema_file, NULL); } free_xml(reply); cib__clean_up_connection(&cib); + pcmk_common_cleanup(); _exit(pcmk_rc2exitc(rc)); } /* Load any additional schema files when the child is finished fetching and * saving them to disk. */ static void get_schema_files_complete(mainloop_child_t *p, pid_t pid, int core, int signo, int exitcode) { const char *errmsg = "Could not load additional schema files"; if ((signo == 0) && (exitcode == 0)) { const char *remote_schema_dir = pcmk__remote_schema_dir(); /* Don't just crm_schema_init here because that will load the base * schemas again too. Instead just load the things we fetched. */ pcmk__load_schemas_from_dir(remote_schema_dir); pcmk__sort_schemas(); crm_info("Fetching extra schema files completed successfully"); } else { if (signo == 0) { crm_err("%s: process %d exited %d", errmsg, (int) pid, exitcode); } else { crm_err("%s: process %d terminated with signal %d (%s)%s", errmsg, (int) pid, signo, strsignal(signo), (core? " and dumped core" : "")); } /* Clean up any incomplete schema data we might have been downloading when * the process timed out or crashed. We don't need to do any extra cleanup * because we never loaded the extra schemas, and we don't need to call * crm_schema_init because that was called in remoted_request_cib_schema_files * before this function. */ clean_up_extra_schema_files(); } } void remoted_request_cib_schema_files(void) { pid_t pid; int rc; /* If a previous schema-fetch process is still running when we're called * again, it's hung. Attempt to kill it before cleaning up the extra * directory. */ if (schema_fetch_pid != 0) { if (mainloop_child_kill(schema_fetch_pid) == FALSE) { crm_warn("Unable to kill pre-existing schema-fetch process"); return; } schema_fetch_pid = 0; } /* Clean up any extra schema files we downloaded from a previous cluster * connection. After the files are gone, we need to wipe them from * known_schemas, but there's no opposite operation for add_schema(). * * Instead, unload all the schemas. This means we'll also forget about all * installed schemas as well, which means that pcmk__highest_schema_name() * would fail. So we need to load the base schemas right now. */ clean_up_extra_schema_files(); crm_schema_cleanup(); crm_schema_init(); crm_info("Fetching extra schema files from cluster"); pid = fork(); switch (pid) { case -1: { rc = errno; crm_warn("Could not spawn process to get schema files: %s", pcmk_rc_str(rc)); break; } case 0: /* child */ get_schema_files(); break; default: /* parent */ schema_fetch_pid = pid; mainloop_child_add_with_flags(pid, 5 * 60 * 1000, "schema-fetch", NULL, mainloop_leave_pid_group, get_schema_files_complete); break; } } diff --git a/lib/services/services_linux.c b/lib/services/services_linux.c index 4b476cdb39..f5928905fc 100644 --- a/lib/services/services_linux.c +++ b/lib/services/services_linux.c @@ -1,1506 +1,1507 @@ /* * Copyright 2010-2024 the Pacemaker project contributors * * The version control history for this file may have further details. * * This source code is licensed under the GNU Lesser General Public License * version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY. */ #include #ifndef _GNU_SOURCE # define _GNU_SOURCE #endif #include #include #include #include #include #include #include #include #include #include #include "crm/crm.h" #include "crm/common/mainloop.h" #include "crm/services.h" #include "crm/services_internal.h" #include "services_private.h" static void close_pipe(int fildes[]); /* We have two alternative ways of handling SIGCHLD when synchronously waiting * for spawned processes to complete. Both rely on polling a file descriptor to * discover SIGCHLD events. * * If sys/signalfd.h is available (e.g. on Linux), we call signalfd() to * generate the file descriptor. Otherwise, we use the "self-pipe trick" * (opening a pipe and writing a byte to it when SIGCHLD is received). */ #ifdef HAVE_SYS_SIGNALFD_H // signalfd() implementation #include // Everything needed to manage SIGCHLD handling struct sigchld_data_s { sigset_t mask; // Signals to block now (including SIGCHLD) sigset_t old_mask; // Previous set of blocked signals bool ignored; // If SIGCHLD for another child has been ignored }; // Initialize SIGCHLD data and prepare for use static bool sigchld_setup(struct sigchld_data_s *data) { sigemptyset(&(data->mask)); sigaddset(&(data->mask), SIGCHLD); sigemptyset(&(data->old_mask)); // Block SIGCHLD (saving previous set of blocked signals to restore later) if (sigprocmask(SIG_BLOCK, &(data->mask), &(data->old_mask)) < 0) { crm_info("Wait for child process completion failed: %s " CRM_XS " source=sigprocmask", pcmk_rc_str(errno)); return false; } data->ignored = false; return true; } // Get a file descriptor suitable for polling for SIGCHLD events static int sigchld_open(struct sigchld_data_s *data) { int fd; CRM_CHECK(data != NULL, return -1); fd = signalfd(-1, &(data->mask), SFD_NONBLOCK); if (fd < 0) { crm_info("Wait for child process completion failed: %s " CRM_XS " source=signalfd", pcmk_rc_str(errno)); } return fd; } // Close a file descriptor returned by sigchld_open() static void sigchld_close(int fd) { if (fd > 0) { close(fd); } } // Return true if SIGCHLD was received from polled fd static bool sigchld_received(int fd, int pid, struct sigchld_data_s *data) { struct signalfd_siginfo fdsi; ssize_t s; if (fd < 0) { return false; } s = read(fd, &fdsi, sizeof(struct signalfd_siginfo)); if (s != sizeof(struct signalfd_siginfo)) { crm_info("Wait for child process completion failed: %s " CRM_XS " source=read", pcmk_rc_str(errno)); } else if (fdsi.ssi_signo == SIGCHLD) { if (fdsi.ssi_pid == pid) { return true; } else { /* This SIGCHLD is for another child. We have to ignore it here but * will still need to resend it after this synchronous action has * completed and SIGCHLD has been restored to be handled by the * previous SIGCHLD handler, so that it will be handled. */ data->ignored = true; return false; } } return false; } // Do anything needed after done waiting for SIGCHLD static void sigchld_cleanup(struct sigchld_data_s *data) { // Restore the original set of blocked signals if ((sigismember(&(data->old_mask), SIGCHLD) == 0) && (sigprocmask(SIG_UNBLOCK, &(data->mask), NULL) < 0)) { crm_warn("Could not clean up after child process completion: %s", pcmk_rc_str(errno)); } // Resend any ignored SIGCHLD for other children so that they'll be handled. if (data->ignored && kill(getpid(), SIGCHLD) != 0) { crm_warn("Could not resend ignored SIGCHLD to ourselves: %s", pcmk_rc_str(errno)); } } #else // HAVE_SYS_SIGNALFD_H not defined // Self-pipe implementation (see above for function descriptions) struct sigchld_data_s { int pipe_fd[2]; // Pipe file descriptors struct sigaction sa; // Signal handling info (with SIGCHLD) struct sigaction old_sa; // Previous signal handling info bool ignored; // If SIGCHLD for another child has been ignored }; // We need a global to use in the signal handler volatile struct sigchld_data_s *last_sigchld_data = NULL; static void sigchld_handler(void) { // We received a SIGCHLD, so trigger pipe polling if ((last_sigchld_data != NULL) && (last_sigchld_data->pipe_fd[1] >= 0) && (write(last_sigchld_data->pipe_fd[1], "", 1) == -1)) { crm_info("Wait for child process completion failed: %s " CRM_XS " source=write", pcmk_rc_str(errno)); } } static bool sigchld_setup(struct sigchld_data_s *data) { int rc; data->pipe_fd[0] = data->pipe_fd[1] = -1; if (pipe(data->pipe_fd) == -1) { crm_info("Wait for child process completion failed: %s " CRM_XS " source=pipe", pcmk_rc_str(errno)); return false; } rc = pcmk__set_nonblocking(data->pipe_fd[0]); if (rc != pcmk_rc_ok) { crm_info("Could not set pipe input non-blocking: %s " CRM_XS " rc=%d", pcmk_rc_str(rc), rc); } rc = pcmk__set_nonblocking(data->pipe_fd[1]); if (rc != pcmk_rc_ok) { crm_info("Could not set pipe output non-blocking: %s " CRM_XS " rc=%d", pcmk_rc_str(rc), rc); } // Set SIGCHLD handler data->sa.sa_handler = (sighandler_t) sigchld_handler; data->sa.sa_flags = 0; sigemptyset(&(data->sa.sa_mask)); if (sigaction(SIGCHLD, &(data->sa), &(data->old_sa)) < 0) { crm_info("Wait for child process completion failed: %s " CRM_XS " source=sigaction", pcmk_rc_str(errno)); } data->ignored = false; // Remember data for use in signal handler last_sigchld_data = data; return true; } static int sigchld_open(struct sigchld_data_s *data) { CRM_CHECK(data != NULL, return -1); return data->pipe_fd[0]; } static void sigchld_close(int fd) { // Pipe will be closed in sigchld_cleanup() return; } static bool sigchld_received(int fd, int pid, struct sigchld_data_s *data) { char ch; if (fd < 0) { return false; } // Clear out the self-pipe while (read(fd, &ch, 1) == 1) /*omit*/; return true; } static void sigchld_cleanup(struct sigchld_data_s *data) { // Restore the previous SIGCHLD handler if (sigaction(SIGCHLD, &(data->old_sa), NULL) < 0) { crm_warn("Could not clean up after child process completion: %s", pcmk_rc_str(errno)); } close_pipe(data->pipe_fd); // Resend any ignored SIGCHLD for other children so that they'll be handled. if (data->ignored && kill(getpid(), SIGCHLD) != 0) { crm_warn("Could not resend ignored SIGCHLD to ourselves: %s", pcmk_rc_str(errno)); } } #endif /*! * \internal * \brief Close the two file descriptors of a pipe * * \param[in,out] fildes Array of file descriptors opened by pipe() */ static void close_pipe(int fildes[]) { if (fildes[0] >= 0) { close(fildes[0]); fildes[0] = -1; } if (fildes[1] >= 0) { close(fildes[1]); fildes[1] = -1; } } #define out_type(is_stderr) ((is_stderr)? "stderr" : "stdout") // Maximum number of bytes of stdout or stderr we'll accept #define MAX_OUTPUT (10 * 1024 * 1024) static gboolean svc_read_output(int fd, svc_action_t * op, bool is_stderr) { char *data = NULL; ssize_t rc = 0; size_t len = 0; size_t discarded = 0; char buf[500]; static const size_t buf_read_len = sizeof(buf) - 1; if (fd < 0) { crm_trace("No fd for %s", op->id); return FALSE; } if (is_stderr && op->stderr_data) { len = strlen(op->stderr_data); data = op->stderr_data; crm_trace("Reading %s stderr into offset %lld", op->id, (long long) len); } else if (is_stderr == FALSE && op->stdout_data) { len = strlen(op->stdout_data); data = op->stdout_data; crm_trace("Reading %s stdout into offset %lld", op->id, (long long) len); } else { crm_trace("Reading %s %s", op->id, out_type(is_stderr)); } do { errno = 0; rc = read(fd, buf, buf_read_len); if (rc > 0) { if (len < MAX_OUTPUT) { buf[rc] = 0; crm_trace("Received %lld bytes of %s %s: %.80s", (long long) rc, op->id, out_type(is_stderr), buf); data = pcmk__realloc(data, len + rc + 1); strcpy(data + len, buf); len += rc; } else { discarded += rc; } } else if (errno != EINTR) { // Fatal error or EOF rc = 0; break; } } while ((rc == buf_read_len) || (rc < 0)); if (discarded > 0) { crm_warn("Truncated %s %s to %lld bytes (discarded %lld)", op->id, out_type(is_stderr), (long long) len, (long long) discarded); } if (is_stderr) { op->stderr_data = data; } else { op->stdout_data = data; } return rc != 0; } static int dispatch_stdout(gpointer userdata) { svc_action_t *op = (svc_action_t *) userdata; return svc_read_output(op->opaque->stdout_fd, op, FALSE); } static int dispatch_stderr(gpointer userdata) { svc_action_t *op = (svc_action_t *) userdata; return svc_read_output(op->opaque->stderr_fd, op, TRUE); } static void pipe_out_done(gpointer user_data) { svc_action_t *op = (svc_action_t *) user_data; crm_trace("%p", op); op->opaque->stdout_gsource = NULL; if (op->opaque->stdout_fd > STDOUT_FILENO) { close(op->opaque->stdout_fd); } op->opaque->stdout_fd = -1; } static void pipe_err_done(gpointer user_data) { svc_action_t *op = (svc_action_t *) user_data; op->opaque->stderr_gsource = NULL; if (op->opaque->stderr_fd > STDERR_FILENO) { close(op->opaque->stderr_fd); } op->opaque->stderr_fd = -1; } static struct mainloop_fd_callbacks stdout_callbacks = { .dispatch = dispatch_stdout, .destroy = pipe_out_done, }; static struct mainloop_fd_callbacks stderr_callbacks = { .dispatch = dispatch_stderr, .destroy = pipe_err_done, }; static void set_ocf_env(const char *key, const char *value, gpointer user_data) { if (setenv(key, value, 1) != 0) { crm_perror(LOG_ERR, "setenv failed for key:%s and value:%s", key, value); } } static void set_ocf_env_with_prefix(gpointer key, gpointer value, gpointer user_data) { char buffer[500]; snprintf(buffer, sizeof(buffer), strcmp(key, "OCF_CHECK_LEVEL") != 0 ? "OCF_RESKEY_%s" : "%s", (char *)key); set_ocf_env(buffer, value, user_data); } static void set_alert_env(gpointer key, gpointer value, gpointer user_data) { int rc; if (value != NULL) { rc = setenv(key, value, 1); } else { rc = unsetenv(key); } if (rc < 0) { crm_perror(LOG_ERR, "setenv %s=%s", (char*)key, (value? (char*)value : "")); } else { crm_trace("setenv %s=%s", (char*)key, (value? (char*)value : "")); } } /*! * \internal * \brief Add environment variables suitable for an action * * \param[in] op Action to use */ static void add_action_env_vars(const svc_action_t *op) { void (*env_setter)(gpointer, gpointer, gpointer) = NULL; if (op->agent == NULL) { env_setter = set_alert_env; /* we deal with alert handler */ } else if (pcmk__str_eq(op->standard, PCMK_RESOURCE_CLASS_OCF, pcmk__str_casei)) { env_setter = set_ocf_env_with_prefix; } if (env_setter != NULL && op->params != NULL) { g_hash_table_foreach(op->params, env_setter, NULL); } if (env_setter == NULL || env_setter == set_alert_env) { return; } set_ocf_env("OCF_RA_VERSION_MAJOR", PCMK_OCF_MAJOR_VERSION, NULL); set_ocf_env("OCF_RA_VERSION_MINOR", PCMK_OCF_MINOR_VERSION, NULL); set_ocf_env("OCF_ROOT", OCF_ROOT_DIR, NULL); set_ocf_env("OCF_EXIT_REASON_PREFIX", PCMK_OCF_REASON_PREFIX, NULL); if (op->rsc) { set_ocf_env("OCF_RESOURCE_INSTANCE", op->rsc, NULL); } if (op->agent != NULL) { set_ocf_env("OCF_RESOURCE_TYPE", op->agent, NULL); } /* Notes: this is not added to specification yet. Sept 10,2004 */ if (op->provider != NULL) { set_ocf_env("OCF_RESOURCE_PROVIDER", op->provider, NULL); } } static void pipe_in_single_parameter(gpointer key, gpointer value, gpointer user_data) { svc_action_t *op = user_data; char *buffer = crm_strdup_printf("%s=%s\n", (char *)key, (char *) value); size_t len = strlen(buffer); size_t total = 0; ssize_t ret = 0; do { errno = 0; ret = write(op->opaque->stdin_fd, buffer + total, len - total); if (ret > 0) { total += ret; } } while ((errno == EINTR) && (total < len)); free(buffer); } /*! * \internal * \brief Pipe parameters in via stdin for action * * \param[in] op Action to use */ static void pipe_in_action_stdin_parameters(const svc_action_t *op) { if (op->params) { g_hash_table_foreach(op->params, pipe_in_single_parameter, (gpointer) op); } } gboolean recurring_action_timer(gpointer data) { svc_action_t *op = data; crm_debug("Scheduling another invocation of %s", op->id); /* Clean out the old result */ free(op->stdout_data); op->stdout_data = NULL; free(op->stderr_data); op->stderr_data = NULL; op->opaque->repeat_timer = 0; services_action_async(op, NULL); return FALSE; } /*! * \internal * \brief Finalize handling of an asynchronous operation * * Given a completed asynchronous operation, cancel or reschedule it as * appropriate if recurring, call its callback if registered, stop tracking it, * and clean it up. * * \param[in,out] op Operation to finalize * * \return Standard Pacemaker return code * \retval EINVAL Caller supplied NULL or invalid \p op * \retval EBUSY Uncanceled recurring action has only been cleaned up * \retval pcmk_rc_ok Action has been freed * * \note If the return value is not pcmk_rc_ok, the caller is responsible for * freeing the action. */ int services__finalize_async_op(svc_action_t *op) { CRM_CHECK((op != NULL) && !(op->synchronous), return EINVAL); if (op->interval_ms != 0) { // Recurring operations must be either cancelled or rescheduled if (op->cancel) { services__set_cancelled(op); cancel_recurring_action(op); } else { op->opaque->repeat_timer = g_timeout_add(op->interval_ms, recurring_action_timer, (void *) op); } } if (op->opaque->callback != NULL) { op->opaque->callback(op); } // Stop tracking the operation (as in-flight or blocked) op->pid = 0; services_untrack_op(op); if ((op->interval_ms != 0) && !(op->cancel)) { // Do not free recurring actions (they will get freed when cancelled) services_action_cleanup(op); return EBUSY; } services_action_free(op); return pcmk_rc_ok; } static void close_op_input(svc_action_t *op) { if (op->opaque->stdin_fd >= 0) { close(op->opaque->stdin_fd); } } static void finish_op_output(svc_action_t *op, bool is_stderr) { mainloop_io_t **source; int fd; if (is_stderr) { source = &(op->opaque->stderr_gsource); fd = op->opaque->stderr_fd; } else { source = &(op->opaque->stdout_gsource); fd = op->opaque->stdout_fd; } if (op->synchronous || *source) { crm_trace("Finish reading %s[%d] %s", op->id, op->pid, (is_stderr? "stderr" : "stdout")); svc_read_output(fd, op, is_stderr); if (op->synchronous) { close(fd); } else { mainloop_del_fd(*source); *source = NULL; } } } // Log an operation's stdout and stderr static void log_op_output(svc_action_t *op) { char *prefix = crm_strdup_printf("%s[%d] error output", op->id, op->pid); /* The library caller has better context to know how important the output * is, so log it at info and debug severity here. They can log it again at * higher severity if appropriate. */ crm_log_output(LOG_INFO, prefix, op->stderr_data); strcpy(prefix + strlen(prefix) - strlen("error output"), "output"); crm_log_output(LOG_DEBUG, prefix, op->stdout_data); free(prefix); } // Truncate exit reasons at this many characters #define EXIT_REASON_MAX_LEN 128 static void parse_exit_reason_from_stderr(svc_action_t *op) { const char *reason_start = NULL; const char *reason_end = NULL; const int prefix_len = strlen(PCMK_OCF_REASON_PREFIX); if ((op->stderr_data == NULL) || // Only OCF agents have exit reasons in stderr !pcmk__str_eq(op->standard, PCMK_RESOURCE_CLASS_OCF, pcmk__str_none)) { return; } // Find the last occurrence of the magic string indicating an exit reason for (const char *cur = strstr(op->stderr_data, PCMK_OCF_REASON_PREFIX); cur != NULL; cur = strstr(cur, PCMK_OCF_REASON_PREFIX)) { cur += prefix_len; // Skip over magic string reason_start = cur; } if ((reason_start == NULL) || (reason_start[0] == '\n') || (reason_start[0] == '\0')) { return; // No or empty exit reason } // Exit reason goes to end of line (or end of output) reason_end = strchr(reason_start, '\n'); if (reason_end == NULL) { reason_end = reason_start + strlen(reason_start); } // Limit size of exit reason to something reasonable if (reason_end > (reason_start + EXIT_REASON_MAX_LEN)) { reason_end = reason_start + EXIT_REASON_MAX_LEN; } free(op->opaque->exit_reason); op->opaque->exit_reason = strndup(reason_start, reason_end - reason_start); } /*! * \internal * \brief Process the completion of an asynchronous child process * * \param[in,out] p Child process that completed * \param[in] pid Process ID of child * \param[in] core (Unused) * \param[in] signo Signal that interrupted child, if any * \param[in] exitcode Exit status of child process */ static void async_action_complete(mainloop_child_t *p, pid_t pid, int core, int signo, int exitcode) { svc_action_t *op = mainloop_child_userdata(p); mainloop_clear_child_userdata(p); CRM_CHECK(op->pid == pid, services__set_result(op, services__generic_error(op), PCMK_EXEC_ERROR, "Bug in mainloop handling"); return); /* Depending on the priority the mainloop gives the stdout and stderr * file descriptors, this function could be called before everything has * been read from them, so force a final read now. */ finish_op_output(op, true); finish_op_output(op, false); close_op_input(op); if (signo == 0) { crm_debug("%s[%d] exited with status %d", op->id, op->pid, exitcode); services__set_result(op, exitcode, PCMK_EXEC_DONE, NULL); log_op_output(op); parse_exit_reason_from_stderr(op); } else if (mainloop_child_timeout(p)) { const char *kind = services__action_kind(op); crm_info("%s %s[%d] timed out after %s", kind, op->id, op->pid, pcmk__readable_interval(op->timeout)); services__format_result(op, services__generic_error(op), PCMK_EXEC_TIMEOUT, "%s did not complete within %s", kind, pcmk__readable_interval(op->timeout)); } else if (op->cancel) { /* If an in-flight recurring operation was killed because it was * cancelled, don't treat that as a failure. */ crm_info("%s[%d] terminated with signal %d (%s)", op->id, op->pid, signo, strsignal(signo)); services__set_result(op, PCMK_OCF_OK, PCMK_EXEC_CANCELLED, NULL); } else { crm_info("%s[%d] terminated with signal %d (%s)", op->id, op->pid, signo, strsignal(signo)); services__format_result(op, PCMK_OCF_UNKNOWN_ERROR, PCMK_EXEC_ERROR, "%s interrupted by %s signal", services__action_kind(op), strsignal(signo)); } services__finalize_async_op(op); } /*! * \internal * \brief Return agent standard's exit status for "generic error" * * When returning an internal error for an action, a value that is appropriate * to the action's agent standard must be used. This function returns a value * appropriate for errors in general. * * \param[in] op Action that error is for * * \return Exit status appropriate to agent standard * \note Actions without a standard will get PCMK_OCF_UNKNOWN_ERROR. */ int services__generic_error(const svc_action_t *op) { if ((op == NULL) || (op->standard == NULL)) { return PCMK_OCF_UNKNOWN_ERROR; } #if PCMK__ENABLE_LSB if (pcmk__str_eq(op->standard, PCMK_RESOURCE_CLASS_LSB, pcmk__str_casei) && pcmk__str_eq(op->action, PCMK_ACTION_STATUS, pcmk__str_casei)) { return PCMK_LSB_STATUS_UNKNOWN; } #endif #if SUPPORT_NAGIOS if (pcmk__str_eq(op->standard, PCMK_RESOURCE_CLASS_NAGIOS, pcmk__str_casei)) { return NAGIOS_STATE_UNKNOWN; } #endif return PCMK_OCF_UNKNOWN_ERROR; } /*! * \internal * \brief Return agent standard's exit status for "not installed" * * When returning an internal error for an action, a value that is appropriate * to the action's agent standard must be used. This function returns a value * appropriate for "not installed" errors. * * \param[in] op Action that error is for * * \return Exit status appropriate to agent standard * \note Actions without a standard will get PCMK_OCF_UNKNOWN_ERROR. */ int services__not_installed_error(const svc_action_t *op) { if ((op == NULL) || (op->standard == NULL)) { return PCMK_OCF_UNKNOWN_ERROR; } #if PCMK__ENABLE_LSB if (pcmk__str_eq(op->standard, PCMK_RESOURCE_CLASS_LSB, pcmk__str_casei) && pcmk__str_eq(op->action, PCMK_ACTION_STATUS, pcmk__str_casei)) { return PCMK_LSB_STATUS_NOT_INSTALLED; } #endif #if SUPPORT_NAGIOS if (pcmk__str_eq(op->standard, PCMK_RESOURCE_CLASS_NAGIOS, pcmk__str_casei)) { return NAGIOS_STATE_UNKNOWN; } #endif return PCMK_OCF_NOT_INSTALLED; } /*! * \internal * \brief Return agent standard's exit status for "insufficient privileges" * * When returning an internal error for an action, a value that is appropriate * to the action's agent standard must be used. This function returns a value * appropriate for "insufficient privileges" errors. * * \param[in] op Action that error is for * * \return Exit status appropriate to agent standard * \note Actions without a standard will get PCMK_OCF_UNKNOWN_ERROR. */ int services__authorization_error(const svc_action_t *op) { if ((op == NULL) || (op->standard == NULL)) { return PCMK_OCF_UNKNOWN_ERROR; } #if PCMK__ENABLE_LSB if (pcmk__str_eq(op->standard, PCMK_RESOURCE_CLASS_LSB, pcmk__str_casei) && pcmk__str_eq(op->action, PCMK_ACTION_STATUS, pcmk__str_casei)) { return PCMK_LSB_STATUS_INSUFFICIENT_PRIV; } #endif #if SUPPORT_NAGIOS if (pcmk__str_eq(op->standard, PCMK_RESOURCE_CLASS_NAGIOS, pcmk__str_casei)) { return NAGIOS_INSUFFICIENT_PRIV; } #endif return PCMK_OCF_INSUFFICIENT_PRIV; } /*! * \internal * \brief Return agent standard's exit status for "not configured" * * When returning an internal error for an action, a value that is appropriate * to the action's agent standard must be used. This function returns a value * appropriate for "not configured" errors. * * \param[in] op Action that error is for * \param[in] is_fatal Whether problem is cluster-wide instead of only local * * \return Exit status appropriate to agent standard * \note Actions without a standard will get PCMK_OCF_UNKNOWN_ERROR. */ int services__configuration_error(const svc_action_t *op, bool is_fatal) { if ((op == NULL) || (op->standard == NULL)) { return PCMK_OCF_UNKNOWN_ERROR; } #if PCMK__ENABLE_LSB if (pcmk__str_eq(op->standard, PCMK_RESOURCE_CLASS_LSB, pcmk__str_casei) && pcmk__str_eq(op->action, PCMK_ACTION_STATUS, pcmk__str_casei)) { return PCMK_LSB_NOT_CONFIGURED; } #endif #if SUPPORT_NAGIOS if (pcmk__str_eq(op->standard, PCMK_RESOURCE_CLASS_NAGIOS, pcmk__str_casei)) { return NAGIOS_STATE_UNKNOWN; } #endif return is_fatal? PCMK_OCF_NOT_CONFIGURED : PCMK_OCF_INVALID_PARAM; } /*! * \internal * \brief Set operation rc and status per errno from stat(), fork() or execvp() * * \param[in,out] op Operation to set rc and status for * \param[in] error Value of errno after system call * * \return void */ void services__handle_exec_error(svc_action_t * op, int error) { const char *name = op->opaque->exec; if (name == NULL) { name = op->agent; if (name == NULL) { name = op->id; } } switch (error) { /* see execve(2), stat(2) and fork(2) */ case ENOENT: /* No such file or directory */ case EISDIR: /* Is a directory */ case ENOTDIR: /* Path component is not a directory */ case EINVAL: /* Invalid executable format */ case ENOEXEC: /* Invalid executable format */ services__format_result(op, services__not_installed_error(op), PCMK_EXEC_NOT_INSTALLED, "%s: %s", name, pcmk_rc_str(error)); break; case EACCES: /* permission denied (various errors) */ case EPERM: /* permission denied (various errors) */ services__format_result(op, services__authorization_error(op), PCMK_EXEC_ERROR, "%s: %s", name, pcmk_rc_str(error)); break; default: services__set_result(op, services__generic_error(op), PCMK_EXEC_ERROR, pcmk_rc_str(error)); } } /*! * \internal * \brief Exit a child process that failed before executing agent * * \param[in] op Action that failed * \param[in] exit_status Exit status code to use * \param[in] exit_reason Exit reason to output if for OCF agent */ static void exit_child(const svc_action_t *op, int exit_status, const char *exit_reason) { if ((op != NULL) && (exit_reason != NULL) && pcmk__str_eq(op->standard, PCMK_RESOURCE_CLASS_OCF, pcmk__str_none)) { fprintf(stderr, PCMK_OCF_REASON_PREFIX "%s\n", exit_reason); } + pcmk_common_cleanup(); _exit(exit_status); } static void action_launch_child(svc_action_t *op) { int rc; /* SIGPIPE is ignored (which is different from signal blocking) by the gnutls library. * Depending on the libqb version in use, libqb may set SIGPIPE to be ignored as well. * We do not want this to be inherited by the child process. By resetting this the signal * to the default behavior, we avoid some potential odd problems that occur during OCF * scripts when SIGPIPE is ignored by the environment. */ signal(SIGPIPE, SIG_DFL); #if defined(HAVE_SCHED_SETSCHEDULER) if (sched_getscheduler(0) != SCHED_OTHER) { struct sched_param sp; memset(&sp, 0, sizeof(sp)); sp.sched_priority = 0; if (sched_setscheduler(0, SCHED_OTHER, &sp) == -1) { crm_info("Could not reset scheduling policy for %s", op->id); } } #endif if (setpriority(PRIO_PROCESS, 0, 0) == -1) { crm_info("Could not reset process priority for %s", op->id); } /* Man: The call setpgrp() is equivalent to setpgid(0,0) * _and_ compiles on BSD variants too * need to investigate if it works the same too. */ setpgid(0, 0); pcmk__close_fds_in_child(false); /* It would be nice if errors in this function could be reported as * execution status (for example, PCMK_EXEC_NO_SECRETS for the secrets error * below) instead of exit status. However, we've already forked, so * exit status is all we have. At least for OCF actions, we can output an * exit reason for the parent to parse. */ #if SUPPORT_CIBSECRETS rc = pcmk__substitute_secrets(op->rsc, op->params); if (rc != pcmk_rc_ok) { if (pcmk__str_eq(op->action, PCMK_ACTION_STOP, pcmk__str_casei)) { crm_info("Proceeding with stop operation for %s " "despite being unable to load CIB secrets (%s)", op->rsc, pcmk_rc_str(rc)); } else { crm_err("Considering %s unconfigured " "because unable to load CIB secrets: %s", op->rsc, pcmk_rc_str(rc)); exit_child(op, services__configuration_error(op, false), "Unable to load CIB secrets"); } } #endif add_action_env_vars(op); /* Become the desired user */ if (op->opaque->uid && (geteuid() == 0)) { // If requested, set effective group if (op->opaque->gid && (setgid(op->opaque->gid) < 0)) { crm_err("Considering %s unauthorized because could not set " "child group to %d: %s", op->id, op->opaque->gid, strerror(errno)); exit_child(op, services__authorization_error(op), "Could not set group for child process"); } // Erase supplementary group list // (We could do initgroups() if we kept a copy of the username) if (setgroups(0, NULL) < 0) { crm_err("Considering %s unauthorized because could not " "clear supplementary groups: %s", op->id, strerror(errno)); exit_child(op, services__authorization_error(op), "Could not clear supplementary groups for child process"); } // Set effective user if (setuid(op->opaque->uid) < 0) { crm_err("Considering %s unauthorized because could not set user " "to %d: %s", op->id, op->opaque->uid, strerror(errno)); exit_child(op, services__authorization_error(op), "Could not set user for child process"); } } // Execute the agent (doesn't return if successful) execvp(op->opaque->exec, op->opaque->args); // An earlier stat() should have avoided most possible errors rc = errno; services__handle_exec_error(op, rc); crm_err("Unable to execute %s: %s", op->id, strerror(rc)); exit_child(op, op->rc, "Child process was unable to execute file"); } /*! * \internal * \brief Wait for synchronous action to complete, and set its result * * \param[in,out] op Action to wait for * \param[in,out] data Child signal data */ static void wait_for_sync_result(svc_action_t *op, struct sigchld_data_s *data) { int status = 0; int timeout = op->timeout; time_t start = time(NULL); struct pollfd fds[3]; int wait_rc = 0; const char *wait_reason = NULL; fds[0].fd = op->opaque->stdout_fd; fds[0].events = POLLIN; fds[0].revents = 0; fds[1].fd = op->opaque->stderr_fd; fds[1].events = POLLIN; fds[1].revents = 0; fds[2].fd = sigchld_open(data); fds[2].events = POLLIN; fds[2].revents = 0; crm_trace("Waiting for %s[%d]", op->id, op->pid); do { int poll_rc = poll(fds, 3, timeout); wait_reason = NULL; if (poll_rc > 0) { if (fds[0].revents & POLLIN) { svc_read_output(op->opaque->stdout_fd, op, FALSE); } if (fds[1].revents & POLLIN) { svc_read_output(op->opaque->stderr_fd, op, TRUE); } if ((fds[2].revents & POLLIN) && sigchld_received(fds[2].fd, op->pid, data)) { wait_rc = waitpid(op->pid, &status, WNOHANG); if ((wait_rc > 0) || ((wait_rc < 0) && (errno == ECHILD))) { // Child process exited or doesn't exist break; } else if (wait_rc < 0) { wait_reason = pcmk_rc_str(errno); crm_info("Wait for completion of %s[%d] failed: %s " CRM_XS " source=waitpid", op->id, op->pid, wait_reason); wait_rc = 0; // Act as if process is still running #ifndef HAVE_SYS_SIGNALFD_H } else { /* The child hasn't exited, so this SIGCHLD could be for * another child. We have to ignore it here but will still * need to resend it after this synchronous action has * completed and SIGCHLD has been restored to be handled by * the previous handler, so that it will be handled. */ data->ignored = true; #endif } } } else if (poll_rc == 0) { // Poll timed out with no descriptors ready timeout = 0; break; } else if ((poll_rc < 0) && (errno != EINTR)) { wait_reason = pcmk_rc_str(errno); crm_info("Wait for completion of %s[%d] failed: %s " CRM_XS " source=poll", op->id, op->pid, wait_reason); break; } timeout = op->timeout - (time(NULL) - start) * 1000; } while ((op->timeout < 0 || timeout > 0)); crm_trace("Stopped waiting for %s[%d]", op->id, op->pid); finish_op_output(op, true); finish_op_output(op, false); close_op_input(op); sigchld_close(fds[2].fd); if (wait_rc <= 0) { if ((op->timeout > 0) && (timeout <= 0)) { services__format_result(op, services__generic_error(op), PCMK_EXEC_TIMEOUT, "%s did not exit within specified timeout", services__action_kind(op)); crm_info("%s[%d] timed out after %dms", op->id, op->pid, op->timeout); } else { services__set_result(op, services__generic_error(op), PCMK_EXEC_ERROR, wait_reason); } /* If only child hasn't been successfully waited for, yet. This is to limit killing wrong target a bit more. */ if ((wait_rc == 0) && (waitpid(op->pid, &status, WNOHANG) == 0)) { if (kill(op->pid, SIGKILL)) { crm_warn("Could not kill rogue child %s[%d]: %s", op->id, op->pid, pcmk_rc_str(errno)); } /* Safe to skip WNOHANG here as we sent non-ignorable signal. */ while ((waitpid(op->pid, &status, 0) == (pid_t) -1) && (errno == EINTR)) { /* keep waiting */; } } } else if (WIFEXITED(status)) { services__set_result(op, WEXITSTATUS(status), PCMK_EXEC_DONE, NULL); parse_exit_reason_from_stderr(op); crm_info("%s[%d] exited with status %d", op->id, op->pid, op->rc); } else if (WIFSIGNALED(status)) { int signo = WTERMSIG(status); services__format_result(op, services__generic_error(op), PCMK_EXEC_ERROR, "%s interrupted by %s signal", services__action_kind(op), strsignal(signo)); crm_info("%s[%d] terminated with signal %d (%s)", op->id, op->pid, signo, strsignal(signo)); #ifdef WCOREDUMP if (WCOREDUMP(status)) { crm_warn("%s[%d] dumped core", op->id, op->pid); } #endif } else { // Shouldn't be possible to get here services__set_result(op, services__generic_error(op), PCMK_EXEC_ERROR, "Unable to wait for child to complete"); } } /*! * \internal * \brief Execute an action whose standard uses executable files * * \param[in,out] op Action to execute * * \return Standard Pacemaker return value * \retval EBUSY Recurring operation could not be initiated * \retval pcmk_rc_error Synchronous action failed * \retval pcmk_rc_ok Synchronous action succeeded, or asynchronous action * should not be freed (because it's pending or because * it failed to execute and was already freed) * * \note If the return value for an asynchronous action is not pcmk_rc_ok, the * caller is responsible for freeing the action. */ int services__execute_file(svc_action_t *op) { int stdout_fd[2]; int stderr_fd[2]; int stdin_fd[2] = {-1, -1}; int rc; struct stat st; struct sigchld_data_s data = { .ignored = false }; // Catch common failure conditions early if (stat(op->opaque->exec, &st) != 0) { rc = errno; crm_info("Cannot execute '%s': %s " CRM_XS " stat rc=%d", op->opaque->exec, pcmk_rc_str(rc), rc); services__handle_exec_error(op, rc); goto done; } if (pipe(stdout_fd) < 0) { rc = errno; crm_info("Cannot execute '%s': %s " CRM_XS " pipe(stdout) rc=%d", op->opaque->exec, pcmk_rc_str(rc), rc); services__handle_exec_error(op, rc); goto done; } if (pipe(stderr_fd) < 0) { rc = errno; close_pipe(stdout_fd); crm_info("Cannot execute '%s': %s " CRM_XS " pipe(stderr) rc=%d", op->opaque->exec, pcmk_rc_str(rc), rc); services__handle_exec_error(op, rc); goto done; } if (pcmk_is_set(pcmk_get_ra_caps(op->standard), pcmk_ra_cap_stdin)) { if (pipe(stdin_fd) < 0) { rc = errno; close_pipe(stdout_fd); close_pipe(stderr_fd); crm_info("Cannot execute '%s': %s " CRM_XS " pipe(stdin) rc=%d", op->opaque->exec, pcmk_rc_str(rc), rc); services__handle_exec_error(op, rc); goto done; } } if (op->synchronous && !sigchld_setup(&data)) { close_pipe(stdin_fd); close_pipe(stdout_fd); close_pipe(stderr_fd); sigchld_cleanup(&data); services__set_result(op, services__generic_error(op), PCMK_EXEC_ERROR, "Could not manage signals for child process"); goto done; } op->pid = fork(); switch (op->pid) { case -1: rc = errno; close_pipe(stdin_fd); close_pipe(stdout_fd); close_pipe(stderr_fd); crm_info("Cannot execute '%s': %s " CRM_XS " fork rc=%d", op->opaque->exec, pcmk_rc_str(rc), rc); services__handle_exec_error(op, rc); if (op->synchronous) { sigchld_cleanup(&data); } goto done; break; case 0: /* Child */ close(stdout_fd[0]); close(stderr_fd[0]); if (stdin_fd[1] >= 0) { close(stdin_fd[1]); } if (STDOUT_FILENO != stdout_fd[1]) { if (dup2(stdout_fd[1], STDOUT_FILENO) != STDOUT_FILENO) { crm_warn("Can't redirect output from '%s': %s " CRM_XS " errno=%d", op->opaque->exec, pcmk_rc_str(errno), errno); } close(stdout_fd[1]); } if (STDERR_FILENO != stderr_fd[1]) { if (dup2(stderr_fd[1], STDERR_FILENO) != STDERR_FILENO) { crm_warn("Can't redirect error output from '%s': %s " CRM_XS " errno=%d", op->opaque->exec, pcmk_rc_str(errno), errno); } close(stderr_fd[1]); } if ((stdin_fd[0] >= 0) && (STDIN_FILENO != stdin_fd[0])) { if (dup2(stdin_fd[0], STDIN_FILENO) != STDIN_FILENO) { crm_warn("Can't redirect input to '%s': %s " CRM_XS " errno=%d", op->opaque->exec, pcmk_rc_str(errno), errno); } close(stdin_fd[0]); } if (op->synchronous) { sigchld_cleanup(&data); } action_launch_child(op); pcmk__assert(false); // action_launch_child() should not return } /* Only the parent reaches here */ close(stdout_fd[1]); close(stderr_fd[1]); if (stdin_fd[0] >= 0) { close(stdin_fd[0]); } op->opaque->stdout_fd = stdout_fd[0]; rc = pcmk__set_nonblocking(op->opaque->stdout_fd); if (rc != pcmk_rc_ok) { crm_info("Could not set '%s' output non-blocking: %s " CRM_XS " rc=%d", op->opaque->exec, pcmk_rc_str(rc), rc); } op->opaque->stderr_fd = stderr_fd[0]; rc = pcmk__set_nonblocking(op->opaque->stderr_fd); if (rc != pcmk_rc_ok) { crm_info("Could not set '%s' error output non-blocking: %s " CRM_XS " rc=%d", op->opaque->exec, pcmk_rc_str(rc), rc); } op->opaque->stdin_fd = stdin_fd[1]; if (op->opaque->stdin_fd >= 0) { // using buffer behind non-blocking-fd here - that could be improved // as long as no other standard uses stdin_fd assume stonith rc = pcmk__set_nonblocking(op->opaque->stdin_fd); if (rc != pcmk_rc_ok) { crm_info("Could not set '%s' input non-blocking: %s " CRM_XS " fd=%d,rc=%d", op->opaque->exec, pcmk_rc_str(rc), op->opaque->stdin_fd, rc); } pipe_in_action_stdin_parameters(op); // as long as we are handling parameters directly in here just close close(op->opaque->stdin_fd); op->opaque->stdin_fd = -1; } // after fds are setup properly and before we plug anything into mainloop if (op->opaque->fork_callback) { op->opaque->fork_callback(op); } if (op->synchronous) { wait_for_sync_result(op, &data); sigchld_cleanup(&data); goto done; } crm_trace("Waiting async for '%s'[%d]", op->opaque->exec, op->pid); mainloop_child_add_with_flags(op->pid, op->timeout, op->id, op, pcmk_is_set(op->flags, SVC_ACTION_LEAVE_GROUP)? mainloop_leave_pid_group : 0, async_action_complete); op->opaque->stdout_gsource = mainloop_add_fd(op->id, G_PRIORITY_LOW, op->opaque->stdout_fd, op, &stdout_callbacks); op->opaque->stderr_gsource = mainloop_add_fd(op->id, G_PRIORITY_LOW, op->opaque->stderr_fd, op, &stderr_callbacks); services_add_inflight_op(op); return pcmk_rc_ok; done: if (op->synchronous) { return (op->rc == PCMK_OCF_OK)? pcmk_rc_ok : pcmk_rc_error; } else { return services__finalize_async_op(op); } } GList * services_os_get_single_directory_list(const char *root, gboolean files, gboolean executable) { GList *list = NULL; struct dirent **namelist; int entries = 0, lpc = 0; char buffer[PATH_MAX]; entries = scandir(root, &namelist, NULL, alphasort); if (entries <= 0) { return list; } for (lpc = 0; lpc < entries; lpc++) { struct stat sb; if ('.' == namelist[lpc]->d_name[0]) { free(namelist[lpc]); continue; } snprintf(buffer, sizeof(buffer), "%s/%s", root, namelist[lpc]->d_name); if (stat(buffer, &sb)) { continue; } if (S_ISDIR(sb.st_mode)) { if (files) { free(namelist[lpc]); continue; } } else if (S_ISREG(sb.st_mode)) { if (files == FALSE) { free(namelist[lpc]); continue; } else if (executable && (sb.st_mode & S_IXUSR) == 0 && (sb.st_mode & S_IXGRP) == 0 && (sb.st_mode & S_IXOTH) == 0) { free(namelist[lpc]); continue; } } list = g_list_append(list, strdup(namelist[lpc]->d_name)); free(namelist[lpc]); } free(namelist); return list; } GList * services_os_get_directory_list(const char *root, gboolean files, gboolean executable) { GList *result = NULL; char *dirs = strdup(root); char *dir = NULL; if (pcmk__str_empty(dirs)) { free(dirs); return result; } for (dir = strtok(dirs, ":"); dir != NULL; dir = strtok(NULL, ":")) { GList *tmp = services_os_get_single_directory_list(dir, files, executable); if (tmp) { result = g_list_concat(result, tmp); } } free(dirs); return result; }