diff --git a/include/crm/common/internal.h b/include/crm/common/internal.h index f2944f54c6..6775d14632 100644 --- a/include/crm/common/internal.h +++ b/include/crm/common/internal.h @@ -1,151 +1,172 @@ /* * Copyright 2015-2019 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 /* 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); 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) */ +/*! + * \internal + * \brief Detect if process per PID and optionally exe path (component) exists + * + * \param[in] pid PID of process assumed alive, disproving of which to try + * \param[in] daemon exe path (component) to possibly match with procfs entry + * + * \return -1 on invalid PID specification, -2 when the calling process has no + * (is refused an) ability to (dis)prove the predicate, + * 0 if the negation of the predicate is confirmed (check-through-kill + * indicates so, or the subsequent check-through-procfs-match on + * \p daemon when provided and procfs available at the standard path), + * 1 if it cannot be disproved (reliably [modulo race conditions] + * when \p daemon provided, procfs available at the standard path + * and the calling process has permissions to access the respective + * procfs location, less so otherwise, since mere check-through-kill + * is exercised without powers to exclude PID recycled in the interim). + * + * \note This function cannot be used to verify \e authenticity of the process. + */ 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_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); // miscellaneous utilities (from utils.c) const char *pcmk_message_name(const char *name); /* 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/pid.c b/lib/common/pid.c index 803799e640..2439680c43 100644 --- a/lib/common/pid.c +++ b/lib/common/pid.c @@ -1,183 +1,201 @@ /* - * Copyright 2004-2018 Andrew Beekhof + * Copyright 2004-2019 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 crm_pid_active(long pid, const char *daemon) { + static int 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 proc_path[PATH_MAX], exe_path[PATH_MAX]; - - // Make sure pid hasn't been reused by another process snprintf(proc_path, sizeof(proc_path), "/proc/%lu/exe", - (long unsigned int)getpid()); - + (long unsigned int) getpid()); have_proc_pid = 1; - if (readlink(proc_path, exe_path, PATH_MAX - 1) < 0) { + if (readlink(proc_path, exe_path, sizeof(exe_path) - 1) < 0) { have_proc_pid = -1; } } if (pid <= 0) { return -1; - } else if ((kill(pid, 0) < 0) && (errno == ESRCH)) { - return 0; + } else if ((rc = kill(pid, 0)) < 0 && errno == ESRCH) { + return 0; /* no such PID detected */ - } else if ((daemon == NULL) || (have_proc_pid == -1)) { - return 1; + } else if (rc < 0 && have_proc_pid == -1) { + if (last_asked_pid != pid) { + crm_info("Cannot examine PID %ld: %s", pid, strerror(errno)); + last_asked_pid = pid; + } + return -2; /* errno != ESRCH */ + + } else if (rc == 0 && (daemon == NULL || have_proc_pid == -1)) { + return 1; /* kill as the only indicator, cannot double check */ } else { - int rc = 0; + /* 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 proc_path[PATH_MAX], exe_path[PATH_MAX], myexe_path[PATH_MAX]; - - // Make sure pid hasn't been reused by another process snprintf(proc_path, sizeof(proc_path), "/proc/%ld/exe", pid); - rc = readlink(proc_path, exe_path, PATH_MAX - 1); + rc = readlink(proc_path, exe_path, sizeof(exe_path) - 1); if ((rc < 0) && (errno == EACCES)) { - crm_perror(LOG_INFO, "Could not read from %s", proc_path); - return 1; + if (last_asked_pid != pid) { + crm_info("Could not read from %s: %s", proc_path, + strerror(errno)); + last_asked_pid = pid; + } + return checked_through_kill ? 1 : -2; } else if (rc < 0) { - crm_perror(LOG_ERR, "Could not read from %s", proc_path); - return 0; + if (last_asked_pid != pid) { + crm_err("Could not read from %s: %s (%d)", proc_path, + strerror(errno), errno); + last_asked_pid = pid; + } + return 0; /* most likely errno == ENOENT */ } - - exe_path[rc] = 0; + exe_path[rc] = '\0'; if (daemon[0] != '/') { - rc = snprintf(myexe_path, sizeof(proc_path), CRM_DAEMON_DIR"/%s", + rc = snprintf(myexe_path, sizeof(myexe_path), CRM_DAEMON_DIR"/%s", daemon); - myexe_path[rc] = 0; } else { - rc = snprintf(myexe_path, sizeof(proc_path), "%s", daemon); - myexe_path[rc] = 0; + rc = snprintf(myexe_path, sizeof(myexe_path), "%s", daemon); } - if (strcmp(exe_path, myexe_path) == 0) { + if (rc > 0 && rc < sizeof(myexe_path) && !strcmp(exe_path, myexe_path)) { return 1; } } return 0; } #define LOCKSTRLEN 11 long crm_read_pidfile(const char *filename) { int fd; struct stat sbuf; long pid = -ENOENT; char buf[LOCKSTRLEN + 1]; fd = open(filename, O_RDONLY); if (fd < 0) { goto bail; } 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) { goto bail; } if (sscanf(buf, "%ld", &pid) > 0) { if (pid <= 0) { pid = -ESRCH; } else { crm_trace("Got pid %lu from %s\n", pid, filename); } } bail: if (fd >= 0) { close(fd); } return pid; } long crm_pidfile_inuse(const char *filename, long mypid, const char *daemon) { long pid = crm_read_pidfile(filename); if (pid < 2) { // Invalid pid pid = -ENOENT; unlink(filename); } else if (mypid && (pid == mypid)) { // In use by us pid = pcmk_ok; } else if (crm_pid_active(pid, daemon) == FALSE) { // Contains a stale value unlink(filename); pid = -ENOENT; } else if (mypid && (pid != mypid)) { // Locked by existing process pid = -EEXIST; } return pid; } int crm_lock_pidfile(const char *filename, const char *name) { long mypid = 0; int fd = 0; int rc = 0; char buf[LOCKSTRLEN + 2]; mypid = (unsigned long) getpid(); rc = crm_pidfile_inuse(filename, 0, name); if (rc == -ENOENT) { // Exists, but the process is not active } else if (rc != pcmk_ok) { // 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), "%*ld\n", LOCKSTRLEN - 1, mypid); rc = write(fd, buf, LOCKSTRLEN); close(fd); if (rc != LOCKSTRLEN) { crm_perror(LOG_ERR, "Incomplete write to %s", filename); return -errno; } return crm_pidfile_inuse(filename, mypid, name); }