diff --git a/include/crm/common/internal.h b/include/crm/common/internal.h index aed8daf499..76c8537e5c 100644 --- a/include/crm/common/internal.h +++ b/include/crm/common/internal.h @@ -1,403 +1,404 @@ /* * Copyright 2015-2022 the Pacemaker project contributors * * The version control history for this file may have further details. * * This source code is licensed under the GNU Lesser General Public License * version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY. */ #ifndef CRM_COMMON_INTERNAL__H #define CRM_COMMON_INTERNAL__H #include // getpid() #include // bool #include // uint8_t, uint64_t #include // strcmp() #include // open() #include // uid_t, gid_t, pid_t #include // guint, GList, GHashTable #include // xmlNode #include // crm_strdup_printf() #include // do_crm_log_unlikely(), etc. #include // mainloop_io_t, struct ipc_client_callbacks #include #include #include #include #include /* This says whether the current application is a Pacemaker daemon or not, * and is used to change default logging settings such as whether to log to * stderr, etc., as well as a few other details such as whether blackbox signal * handling is enabled. * * It is set when logging is initialized, and does not need to be set directly. */ extern bool pcmk__is_daemon; // Number of elements in a statically defined array #define PCMK__NELEM(a) ((int) (sizeof(a)/sizeof(a[0])) ) /* internal ACL-related utilities */ char *pcmk__uid2username(uid_t uid); const char *pcmk__update_acl_user(xmlNode *request, const char *field, const char *peer_user); static inline bool pcmk__is_privileged(const char *user) { return user && (!strcmp(user, CRM_DAEMON_USER) || !strcmp(user, "root")); } void pcmk__enable_acl(xmlNode *acl_source, xmlNode *target, const char *user); bool pcmk__check_acl(xmlNode *xml, const char *name, enum xml_private_flags mode); #if SUPPORT_CIBSECRETS /* internal CIB utilities (from cib_secrets.c) */ int pcmk__substitute_secrets(const char *rsc_id, GHashTable *params); #endif /* internal digest-related utilities (from digest.c) */ bool pcmk__verify_digest(xmlNode *input, const char *expected); /* internal I/O utilities (from io.c) */ int pcmk__real_path(const char *path, char **resolved_path); char *pcmk__series_filename(const char *directory, const char *series, int sequence, bool bzip); int pcmk__read_series_sequence(const char *directory, const char *series, unsigned int *seq); void pcmk__write_series_sequence(const char *directory, const char *series, unsigned int sequence, int max); int pcmk__chown_series_sequence(const char *directory, const char *series, uid_t uid, gid_t gid); int pcmk__build_path(const char *path_c, mode_t mode); char *pcmk__full_path(const char *filename, const char *dirname); bool pcmk__daemon_can_write(const char *dir, const char *file); void pcmk__sync_directory(const char *name); int pcmk__file_contents(const char *filename, char **contents); int pcmk__write_sync(int fd, const char *contents); int pcmk__set_nonblocking(int fd); const char *pcmk__get_tmpdir(void); void pcmk__close_fds_in_child(bool); /*! * \internal * \brief Open /dev/null to consume next available file descriptor * * Open /dev/null, disregarding the result. This is intended when daemonizing to * be able to null stdin, stdout, and stderr. * * \param[in] flags O_RDONLY (stdin) or O_WRONLY (stdout and stderr) */ static inline void pcmk__open_devnull(int flags) { // Static analysis clutter // cppcheck-suppress leakReturnValNotUsed (void) open("/dev/null", flags); } /* internal main loop utilities (from mainloop.c) */ int pcmk__add_mainloop_ipc(crm_ipc_t *ipc, int priority, void *userdata, struct ipc_client_callbacks *callbacks, mainloop_io_t **source); guint pcmk__mainloop_timer_get_period(mainloop_timer_t *timer); /* internal name/value utilities (from nvpair.c) */ int pcmk__scan_nvpair(const char *input, char **name, char **value); char *pcmk__format_nvpair(const char *name, const char *value, const char *units); char *pcmk__format_named_time(const char *name, time_t epoch_time); /*! * \internal * \brief Add a boolean attribute to an XML node. * * \param[in,out] node XML node to add attributes to * \param[in] name XML attribute to create * \param[in] value Value to give to the attribute */ void pcmk__xe_set_bool_attr(xmlNodePtr node, const char *name, bool value); /*! * \internal * \brief Extract a boolean attribute's value from an XML element * * \param[in] node XML node to get attribute from * \param[in] name XML attribute to get * * \return True if the given \p name is an attribute on \p node and has * the value "true", False in all other cases */ bool pcmk__xe_attr_is_true(xmlNodePtr node, const char *name); /*! * \internal * \brief Extract a boolean attribute's value from an XML element, with * error checking * * \param[in] node XML node to get attribute from * \param[in] name XML attribute to get * \param[out] value Destination for the value of the attribute * * \return EINVAL if \p name or \p value are NULL, ENODATA if \p node is * NULL or the attribute does not exist, pcmk_rc_unknown_format * if the attribute is not a boolean, and pcmk_rc_ok otherwise. * * \note \p value only has any meaning if the return value is pcmk_rc_ok. */ int pcmk__xe_get_bool_attr(xmlNodePtr node, const char *name, bool *value); /* internal procfs utilities (from procfs.c) */ pid_t pcmk__procfs_pid_of(const char *name); unsigned int pcmk__procfs_num_cores(void); int pcmk__procfs_pid2path(pid_t pid, char path[], size_t path_size); +bool pcmk__procfs_has_pids(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) */ /*! * \internal * \brief Check whether process exists (by PID and optionally executable path) * * \param[in] pid PID of process to check * \param[in] daemon If not NULL, path component to match with procfs entry * * \return Standard Pacemaker return code * \note Particular return codes of interest include pcmk_rc_ok for alive, * ESRCH for process is not alive (verified by kill and/or executable path * match), EACCES for caller unable or not allowed to check. A result of * "alive" is less reliable when \p daemon is not provided or procfs is * not available, since there is no guarantee that the PID has not been * recycled for another process. * \note This function cannot be used to verify \e authenticity of the process. */ int pcmk__pid_active(pid_t pid, const char *daemon); int pcmk__read_pidfile(const char *filename, pid_t *pid); int pcmk__pidfile_matches(const char *filename, pid_t expected_pid, const char *expected_name, pid_t *pid); int pcmk__lock_pidfile(const char *filename, const char *name); /* internal functions related to resource operations (from operations.c) */ // printf-style format to create operation ID from resource, action, interval #define PCMK__OP_FMT "%s_%s_%u" char *pcmk__op_key(const char *rsc_id, const char *op_type, guint interval_ms); char *pcmk__notify_key(const char *rsc_id, const char *notify_type, const char *op_type); char *pcmk__transition_key(int transition_id, int action_id, int target_rc, const char *node); void pcmk__filter_op_for_digest(xmlNode *param_set); bool pcmk__is_fencing_action(const char *action); // bitwise arithmetic utilities /*! * \internal * \brief Set specified flags in a flag group * * \param[in] function Function name of caller * \param[in] line Line number of caller * \param[in] log_level Log a message at this level * \param[in] flag_type Label describing this flag group (for logging) * \param[in] target Name of object whose flags these are (for logging) * \param[in] flag_group Flag group being manipulated * \param[in] flags Which flags in the group should be set * \param[in] flags_str Readable equivalent of \p flags (for logging) * * \return Possibly modified flag group */ static inline uint64_t pcmk__set_flags_as(const char *function, int line, uint8_t log_level, const char *flag_type, const char *target, uint64_t flag_group, uint64_t flags, const char *flags_str) { uint64_t result = flag_group | flags; if (result != flag_group) { do_crm_log_unlikely(log_level, "%s flags %#.8llx (%s) for %s set by %s:%d", ((flag_type == NULL)? "Group of" : flag_type), (unsigned long long) flags, ((flags_str == NULL)? "flags" : flags_str), ((target == NULL)? "target" : target), function, line); } return result; } /*! * \internal * \brief Clear specified flags in a flag group * * \param[in] function Function name of caller * \param[in] line Line number of caller * \param[in] log_level Log a message at this level * \param[in] flag_type Label describing this flag group (for logging) * \param[in] target Name of object whose flags these are (for logging) * \param[in] flag_group Flag group being manipulated * \param[in] flags Which flags in the group should be cleared * \param[in] flags_str Readable equivalent of \p flags (for logging) * * \return Possibly modified flag group */ static inline uint64_t pcmk__clear_flags_as(const char *function, int line, uint8_t log_level, const char *flag_type, const char *target, uint64_t flag_group, uint64_t flags, const char *flags_str) { uint64_t result = flag_group & ~flags; if (result != flag_group) { do_crm_log_unlikely(log_level, "%s flags %#.8llx (%s) for %s cleared by %s:%d", ((flag_type == NULL)? "Group of" : flag_type), (unsigned long long) flags, ((flags_str == NULL)? "flags" : flags_str), ((target == NULL)? "target" : target), function, line); } return result; } // miscellaneous utilities (from utils.c) void pcmk__daemonize(const char *name, const char *pidfile); void pcmk__panic(const char *origin); pid_t pcmk__locate_sbd(void); void pcmk__sleep_ms(unsigned int ms); extern int pcmk__score_red; extern int pcmk__score_green; extern int pcmk__score_yellow; /*! * \internal * \brief Resize a dynamically allocated memory block * * \param[in] ptr Memory block to resize (or NULL to allocate new memory) * \param[in] size New size of memory block in bytes (must be > 0) * * \return Pointer to resized memory block * * \note This asserts on error, so the result is guaranteed to be non-NULL * (which is the main advantage of this over directly using realloc()). */ static inline void * pcmk__realloc(void *ptr, size_t size) { void *new_ptr; // realloc(p, 0) can replace free(p) but this wrapper can't CRM_ASSERT(size > 0); new_ptr = realloc(ptr, size); if (new_ptr == NULL) { free(ptr); abort(); } return new_ptr; } static inline char * pcmk__getpid_s(void) { return crm_strdup_printf("%lu", (unsigned long) getpid()); } // More efficient than g_list_length(list) == 1 static inline bool pcmk__list_of_1(GList *list) { return list && (list->next == NULL); } // More efficient than g_list_length(list) > 1 static inline bool pcmk__list_of_multiple(GList *list) { return list && (list->next != NULL); } /* convenience functions for failure-related node attributes */ #define PCMK__FAIL_COUNT_PREFIX "fail-count" #define PCMK__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 * pcmk__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 * pcmk__failcount_name(const char *rsc_id, const char *op, guint interval_ms) { return pcmk__fail_attr_name(PCMK__FAIL_COUNT_PREFIX, rsc_id, op, interval_ms); } static inline char * pcmk__lastfailure_name(const char *rsc_id, const char *op, guint interval_ms) { return pcmk__fail_attr_name(PCMK__LAST_FAILURE_PREFIX, rsc_id, op, interval_ms); } // internal resource agent functions (from agents.c) int pcmk__effective_rc(int rc); #endif /* CRM_COMMON_INTERNAL__H */ diff --git a/lib/common/pid.c b/lib/common/pid.c index fab49ceea6..b9749c6b76 100644 --- a/lib/common/pid.c +++ b/lib/common/pid.c @@ -1,256 +1,240 @@ /* * Copyright 2004-2022 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 int pcmk__pid_active(pid_t pid, const char *daemon) { static pid_t last_asked_pid = 0; /* log spam prevention */ -#if SUPPORT_PROCFS - static int have_proc_pid = 0; -#else - static int have_proc_pid = -1; -#endif int rc = 0; - if (have_proc_pid == 0) { - /* evaluation of /proc/PID/exe applicability via self-introspection */ - char path[PATH_MAX]; - - if (pcmk__procfs_pid2path(getpid(), path, sizeof(path)) == pcmk_rc_ok) { - have_proc_pid = 1; - } else { - have_proc_pid = -1; - } - } - if (pid <= 0) { return EINVAL; } rc = kill(pid, 0); if ((rc < 0) && (errno == ESRCH)) { return ESRCH; /* no such PID detected */ - } else if ((daemon == NULL) || (have_proc_pid == -1)) { + } else if ((daemon == NULL) || !pcmk__procfs_has_pids()) { // The kill result is all we have, we can't check the name if (rc == 0) { return pcmk_rc_ok; } rc = errno; if (last_asked_pid != pid) { crm_info("Cannot examine PID %lld: %s", (long long) pid, pcmk_rc_str(rc)); last_asked_pid = pid; } return rc; /* errno != ESRCH */ } else { /* make sure PID hasn't been reused by another process XXX: might still be just a zombie, which could confuse decisions */ bool checked_through_kill = (rc == 0); char exe_path[PATH_MAX], myexe_path[PATH_MAX]; rc = pcmk__procfs_pid2path(pid, exe_path, sizeof(exe_path)); if (rc != pcmk_rc_ok) { if (rc != EACCES) { // Check again to filter out races if ((kill(pid, 0) < 0) && (errno == ESRCH)) { return ESRCH; } } if (last_asked_pid != pid) { if (rc == EACCES) { crm_info("Could not get executable for PID %lld: %s " CRM_XS " rc=%d", (long long) pid, pcmk_rc_str(rc), rc); } else { crm_err("Could not get executable for PID %lld: %s " CRM_XS " rc=%d", (long long) pid, pcmk_rc_str(rc), rc); } last_asked_pid = pid; } if (rc == EACCES) { // Trust kill if it was OK (we can't double-check via path) return checked_through_kill? pcmk_rc_ok : EACCES; } else { return ESRCH; /* most likely errno == ENOENT */ } } if (daemon[0] != '/') { rc = snprintf(myexe_path, sizeof(myexe_path), CRM_DAEMON_DIR"/%s", daemon); } else { rc = snprintf(myexe_path, sizeof(myexe_path), "%s", daemon); } if (rc > 0 && rc < sizeof(myexe_path) && !strcmp(exe_path, myexe_path)) { return pcmk_rc_ok; } } return ESRCH; } #define LOCKSTRLEN 11 /*! * \internal * \brief Read a process ID from a file * * \param[in] filename Process ID file to read * \param[out] pid Where to put PID that was read * * \return Standard Pacemaker return code */ int pcmk__read_pidfile(const char *filename, pid_t *pid) { int fd; struct stat sbuf; int rc = pcmk_rc_unknown_format; long long pid_read = 0; char buf[LOCKSTRLEN + 1]; CRM_CHECK((filename != NULL) && (pid != NULL), return EINVAL); fd = open(filename, O_RDONLY); if (fd < 0) { return errno; } if ((fstat(fd, &sbuf) >= 0) && (sbuf.st_size < LOCKSTRLEN)) { sleep(2); /* if someone was about to create one, * give'm a sec to do so */ } if (read(fd, buf, sizeof(buf)) < 1) { rc = errno; goto bail; } if (sscanf(buf, "%lld", &pid_read) > 0) { if (pid_read <= 0) { rc = ESRCH; } else { rc = pcmk_rc_ok; *pid = (pid_t) pid_read; crm_trace("Read pid %lld from %s", pid_read, filename); } } bail: close(fd); return rc; } /*! * \internal * \brief Check whether a process from a PID file matches expected values * * \param[in] filename Path of PID file * \param[in] expected_pid If positive, compare to this PID * \param[in] expected_name If not NULL, the PID from the PID file is valid * only if it is active as a process with this name * \param[out] pid If not NULL, store PID found in PID file here * * \return Standard Pacemaker return code */ int pcmk__pidfile_matches(const char *filename, pid_t expected_pid, const char *expected_name, pid_t *pid) { pid_t pidfile_pid = 0; int rc = pcmk__read_pidfile(filename, &pidfile_pid); if (pid) { *pid = pidfile_pid; } if (rc != pcmk_rc_ok) { // Error reading PID file or invalid contents unlink(filename); rc = ENOENT; } else if ((expected_pid > 0) && (pidfile_pid == expected_pid)) { // PID in file matches what was expected rc = pcmk_rc_ok; } else if (pcmk__pid_active(pidfile_pid, expected_name) == ESRCH) { // Contains a stale value unlink(filename); rc = ENOENT; } else if ((expected_pid > 0) && (pidfile_pid != expected_pid)) { // Locked by existing process rc = EEXIST; } return rc; } /*! * \internal * \brief Create a PID file for the current process (if not already existent) * * \param[in] filename Name of PID file to create * \param[in] name Name of current process * * \return Standard Pacemaker return code */ int pcmk__lock_pidfile(const char *filename, const char *name) { pid_t mypid = getpid(); int fd = 0; int rc = 0; char buf[LOCKSTRLEN + 2]; rc = pcmk__pidfile_matches(filename, 0, name, NULL); if ((rc != pcmk_rc_ok) && (rc != ENOENT)) { // Locked by existing process return rc; } fd = open(filename, O_CREAT | O_WRONLY | O_EXCL, 0644); if (fd < 0) { return errno; } snprintf(buf, sizeof(buf), "%*lld\n", LOCKSTRLEN - 1, (long long) mypid); rc = write(fd, buf, LOCKSTRLEN); close(fd); if (rc != LOCKSTRLEN) { crm_perror(LOG_ERR, "Incomplete write to %s", filename); return errno; } rc = pcmk__pidfile_matches(filename, mypid, name, NULL); if (rc != pcmk_rc_ok) { // Something is really wrong -- maybe I/O error on read back? unlink(filename); } return rc; } diff --git a/lib/common/procfs.c b/lib/common/procfs.c index 01fb2b689e..37ab62b428 100644 --- a/lib/common/procfs.c +++ b/lib/common/procfs.c @@ -1,202 +1,227 @@ /* * Copyright 2015-2022 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 /*! * \internal * \brief Get process ID and name associated with a /proc directory entry * * \param[in] entry Directory entry (must be result of readdir() on /proc) * \param[out] name If not NULL, a char[16] to hold the process name * \param[out] pid If not NULL, will be set to process ID of entry * * \return Standard Pacemaker return code * \note This should be called only on Linux systems, as not all systems that * support /proc store process names and IDs in the same way. The kernel * limits the process name to the first 15 characters (plus terminator). * It would be nice if there were a public kernel API constant for that * limit, but there isn't. */ static int pcmk__procfs_process_info(struct dirent *entry, char *name, pid_t *pid) { int fd, local_pid; FILE *file; struct stat statbuf; char procpath[128] = { 0 }; /* We're only interested in entries whose name is a PID, * so skip anything non-numeric or that is too long. * * 114 = 128 - strlen("/proc/") - strlen("/status") - 1 */ local_pid = atoi(entry->d_name); if ((local_pid <= 0) || (strlen(entry->d_name) > 114)) { return -1; } if (pid) { *pid = (pid_t) local_pid; } /* Get this entry's file information */ strcpy(procpath, "/proc/"); strcat(procpath, entry->d_name); fd = open(procpath, O_RDONLY); if (fd < 0 ) { return -1; } if (fstat(fd, &statbuf) < 0) { close(fd); return -1; } close(fd); /* We're only interested in subdirectories */ if (!S_ISDIR(statbuf.st_mode)) { return -1; } /* Read the first entry ("Name:") from the process's status file. * We could handle the valgrind case if we parsed the cmdline file * instead, but that's more of a pain than it's worth. */ if (name != NULL) { strcat(procpath, "/status"); file = fopen(procpath, "r"); if (!file) { return -1; } if (fscanf(file, "Name:\t%15[^\n]", name) != 1) { fclose(file); return -1; } name[15] = 0; fclose(file); } return 0; } /*! * \internal * \brief Return process ID of a named process * * \param[in] name Process name (as used in /proc/.../status) * * \return Process ID of named process if running, 0 otherwise * * \note This will return 0 if the process is being run via valgrind. * This should be called only on Linux systems. */ pid_t pcmk__procfs_pid_of(const char *name) { DIR *dp; struct dirent *entry; pid_t pid = 0; char entry_name[64] = { 0 }; dp = opendir("/proc"); if (dp == NULL) { crm_notice("Can not read /proc directory to track existing components"); return 0; } while ((entry = readdir(dp)) != NULL) { if ((pcmk__procfs_process_info(entry, entry_name, &pid) == pcmk_rc_ok) && pcmk__str_eq(entry_name, name, pcmk__str_casei) && (pcmk__pid_active(pid, NULL) == pcmk_rc_ok)) { crm_info("Found %s active as process %lld", name, (long long) pid); break; } pid = 0; } closedir(dp); return pid; } /*! * \internal * \brief Calculate number of logical CPU cores from procfs * * \return Number of cores (or 1 if unable to determine) */ unsigned int pcmk__procfs_num_cores(void) { int cores = 0; FILE *stream = NULL; /* Parse /proc/stat instead of /proc/cpuinfo because it's smaller */ stream = fopen("/proc/stat", "r"); if (stream == NULL) { crm_perror(LOG_INFO, "Could not open /proc/stat"); } else { char buffer[2048]; while (fgets(buffer, sizeof(buffer), stream)) { if (pcmk__starts_with(buffer, "cpu") && isdigit(buffer[3])) { ++cores; } } fclose(stream); } return cores? cores : 1; } /*! * \internal * \brief Get the executable path corresponding to a process ID * * \param[in] pid Process ID to check * \param[out] path Where to store executable path * \param[in] path_size Size of \p path in characters (ideally PATH_MAX) * * \return Standard Pacemaker error code (as possible errno values from * readlink()) */ int pcmk__procfs_pid2path(pid_t pid, char path[], size_t path_size) { #if SUPPORT_PROCFS char procfs_exe_path[PATH_MAX]; ssize_t link_rc; if (snprintf(procfs_exe_path, path_size, "/proc/%lld/exe", (long long) pid) >= path_size) { return ENAMETOOLONG; // Truncated (shouldn't be possible in practice) } link_rc = readlink(procfs_exe_path, path, path_size - 1); if (link_rc < 0) { return errno; } else if (link_rc >= (path_size - 1)) { return ENAMETOOLONG; } path[link_rc] = '\0'; return pcmk_rc_ok; #else return EOPNOTSUPP; #endif } + +/*! + * \internal + * \brief Check whether process ID information is available from procfs + * + * \return true if process ID information is available, otherwise false + */ +bool +pcmk__procfs_has_pids(void) +{ +#if SUPPORT_PROCFS + static bool have_pids = false; + static bool checked = false; + + if (!checked) { + char path[PATH_MAX]; + + have_pids = pcmk__procfs_pid2path(getpid(), path, sizeof(path)) == pcmk_rc_ok; + checked = true; + } + return have_pids; +#else + return false; +#endif +}