Page Menu
Home
ClusterLabs Projects
Search
Configure Global Search
Log In
Files
F1841549
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Flag For Later
Award Token
Size
38 KB
Referenced Files
None
Subscribers
None
View Options
diff --git a/include/crm/common/internal.h b/include/crm/common/internal.h
index a354f0cb32..ddbe67457d 100644
--- a/include/crm/common/internal.h
+++ b/include/crm/common/internal.h
@@ -1,360 +1,361 @@
/*
* Copyright 2015-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.
*/
#ifndef PCMK__CRM_COMMON_INTERNAL__H
#define PCMK__CRM_COMMON_INTERNAL__H
#include <unistd.h> // pid_t, getpid()
#include <stdbool.h> // bool
#include <stdint.h> // uint8_t, uint64_t
#include <glib.h> // guint, GList, GHashTable
#include <libxml/tree.h> // xmlNode
#include <crm/common/logging.h> // do_crm_log_unlikely(), etc.
#include <crm/common/mainloop.h> // mainloop_io_t, struct ipc_client_callbacks
#include <crm/common/strings.h> // crm_strdup_printf()
#include <crm/common/actions_internal.h>
#include <crm/common/digest_internal.h>
#include <crm/common/health_internal.h>
#include <crm/common/io_internal.h>
#include <crm/common/iso8601_internal.h>
#include <crm/common/results_internal.h>
#include <crm/common/messages_internal.h>
#include <crm/common/nvpair_internal.h>
#include <crm/common/scores_internal.h>
#include <crm/common/strings_internal.h>
#include <crm/common/acl_internal.h>
#ifdef __cplusplus
extern "C" {
#endif
/* 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])) )
#if PCMK__ENABLE_CIBSECRETS
/* internal CIB utilities (from cib_secrets.c) */
int pcmk__substitute_secrets(const char *rsc_id, GHashTable *params);
#endif
/* internal main loop utilities (from mainloop.c) */
int pcmk__add_mainloop_ipc(crm_ipc_t *ipc, int priority, void *userdata,
const struct ipc_client_callbacks *callbacks,
mainloop_io_t **source);
guint pcmk__mainloop_timer_get_period(const mainloop_timer_t *timer);
/* internal node-related XML utilities (from nodes.c) */
/*!
* \internal
* \brief Add local node name and ID to an XML node
*
* \param[in,out] request XML node to modify
* \param[in] node The local node's name
* \param[in] nodeid The local node's ID (can be 0)
*/
void pcmk__xe_add_node(xmlNode *xml, const char *node, int nodeid);
/* 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);
/* 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);
+DIR *pcmk__procfs_fd_dir(void);
void pcmk__sysrq_trigger(char t);
/* 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);
// 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;
}
/*!
* \internal
* \brief Get readable string for whether specified flags are set
*
* \param[in] flag_group Group of flags to check
* \param[in] flags Which flags in \p flag_group should be checked
*
* \return "true" if all \p flags are set in \p flag_group, otherwise "false"
*/
static inline const char *
pcmk__flag_text(uint64_t flag_group, uint64_t flags)
{
return pcmk__btoa(pcmk_all_flags_set(flag_group, flags));
}
// miscellaneous utilities (from utils.c)
void pcmk__daemonize(const char *name, const char *pidfile);
void pcmk__panic(const char *reason);
pid_t pcmk__locate_sbd(void);
void pcmk__sleep_ms(unsigned int ms);
guint pcmk__create_timer(guint interval_ms, GSourceFunc fn, gpointer data);
guint pcmk__timeout_ms2s(guint timeout_ms);
extern int pcmk__score_red;
extern int pcmk__score_green;
extern int pcmk__score_yellow;
/*!
* \internal
* \brief Allocate new zero-initialized memory, asserting on failure
*
* \param[in] file File where \p function is located
* \param[in] function Calling function
* \param[in] line Line within \p file
* \param[in] nmemb Number of elements to allocate memory for
* \param[in] size Size of each element
*
* \return Newly allocated memory of of size <tt>nmemb * size</tt> (guaranteed
* not to be \c NULL)
*
* \note The caller is responsible for freeing the return value using \c free().
*/
static inline void *
pcmk__assert_alloc_as(const char *file, const char *function, uint32_t line,
size_t nmemb, size_t size)
{
void *ptr = calloc(nmemb, size);
if (ptr == NULL) {
crm_abort(file, function, line, "Out of memory", FALSE, TRUE);
crm_exit(CRM_EX_OSERR);
}
return ptr;
}
/*!
* \internal
* \brief Allocate new zero-initialized memory, asserting on failure
*
* \param[in] nmemb Number of elements to allocate memory for
* \param[in] size Size of each element
*
* \return Newly allocated memory of of size <tt>nmemb * size</tt> (guaranteed
* not to be \c NULL)
*
* \note The caller is responsible for freeing the return value using \c free().
*/
#define pcmk__assert_alloc(nmemb, size) \
pcmk__assert_alloc_as(__FILE__, __func__, __LINE__, nmemb, size)
/*!
* \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
pcmk__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);
#ifdef __cplusplus
}
#endif
#endif // PCMK__CRM_COMMON_INTERNAL__H
diff --git a/lib/common/io.c b/lib/common/io.c
index 102d7f1bf0..972b927888 100644
--- a/lib/common/io.c
+++ b/lib/common/io.c
@@ -1,639 +1,635 @@
/*
* 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 Lesser General Public License
* version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY.
*/
#include <crm_internal.h>
#include <sys/param.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/resource.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <fcntl.h>
#include <dirent.h>
#include <errno.h>
#include <limits.h>
#include <pwd.h>
#include <grp.h>
#include <crm/crm.h>
#include <crm/common/util.h>
/*!
* \internal
* \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
*
* \return Standard Pacemaker return code
*/
int
pcmk__build_path(const char *path_c, mode_t mode)
{
int offset = 1, len = 0;
int rc = pcmk_rc_ok;
char *path = strdup(path_c);
// cppcheck seems not to understand the abort logic in CRM_CHECK
// cppcheck-suppress memleak
CRM_CHECK(path != NULL, return -ENOMEM);
for (len = strlen(path); offset < len; offset++) {
if (path[offset] == '/') {
path[offset] = 0;
if ((mkdir(path, mode) < 0) && (errno != EEXIST)) {
rc = errno;
goto done;
}
path[offset] = '/';
}
}
if ((mkdir(path, mode) < 0) && (errno != EEXIST)) {
rc = errno;
}
done:
free(path);
return rc;
}
/*!
* \internal
* \brief Return canonicalized form of a path name
*
* \param[in] path Pathname to canonicalize
* \param[out] resolved_path Where to store canonicalized pathname
*
* \return Standard Pacemaker return code
* \note The caller is responsible for freeing \p resolved_path on success.
* \note This function exists because not all C library versions of
* realpath(path, resolved_path) support a NULL resolved_path.
*/
int
pcmk__real_path(const char *path, char **resolved_path)
{
CRM_CHECK((path != NULL) && (resolved_path != NULL), return EINVAL);
#if _POSIX_VERSION >= 200809L
/* Recent C libraries can dynamically allocate memory as needed */
*resolved_path = realpath(path, NULL);
return (*resolved_path == NULL)? errno : pcmk_rc_ok;
#elif defined(PATH_MAX)
/* Older implementations require pre-allocated memory */
/* (this is less desirable because PATH_MAX may be huge or not defined) */
*resolved_path = malloc(PATH_MAX);
if ((*resolved_path == NULL) || (realpath(path, *resolved_path) == NULL)) {
return errno;
}
return pcmk_rc_ok;
#else
*resolved_path = NULL;
return ENOTSUP;
#endif
}
/*!
* \internal
* \brief Create a file name 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
* \param[in] bzip Whether to use ".bz2" instead of ".raw" as extension
*
* \return Newly allocated file path (asserts on error, so always non-NULL)
* \note The caller is responsible for freeing the return value.
*/
char *
pcmk__series_filename(const char *directory, const char *series,
unsigned int sequence, bool bzip)
{
pcmk__assert((directory != NULL) && (series != NULL));
return crm_strdup_printf("%s/%s-%u.%s", directory, series, sequence,
(bzip? "bz2" : "raw"));
}
/*!
* \internal
* \brief Read 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
* \param[out] seq Where to store the sequence number
*
* \return Standard Pacemaker return code
*/
int
pcmk__read_series_sequence(const char *directory, const char *series,
unsigned int *seq)
{
int rc;
FILE *fp = NULL;
char *series_file = NULL;
if ((directory == NULL) || (series == NULL) || (seq == NULL)) {
return EINVAL;
}
series_file = crm_strdup_printf("%s/%s.last", directory, series);
fp = fopen(series_file, "r");
if (fp == NULL) {
rc = errno;
crm_debug("Could not open series file %s: %s",
series_file, strerror(rc));
free(series_file);
return rc;
}
errno = 0;
if (fscanf(fp, "%u", seq) != 1) {
rc = (errno == 0)? ENODATA : errno;
crm_debug("Could not read sequence number from series file %s: %s",
series_file, pcmk_rc_str(rc));
fclose(fp);
return rc;
}
fclose(fp);
crm_trace("Found last sequence number %u in series file %s",
*seq, series_file);
free(series_file);
return pcmk_rc_ok;
}
/*!
* \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 it is reset to 0
*
* \note This function logs some errors but does not return any to the caller
*/
void
pcmk__write_series_sequence(const char *directory, const char *series,
unsigned 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, "%u", 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] directory Directory that contains series
* \param[in] series Series to change
* \param[in] uid User ID of desired file owner
* \param[in] gid Group ID of desired file group
*
* \return Standard Pacemaker return code
* \note The caller must have the appropriate privileges.
*/
int
pcmk__chown_series_sequence(const char *directory, const char *series,
uid_t uid, gid_t gid)
{
char *series_file = NULL;
int rc = pcmk_rc_ok;
if ((directory == NULL) || (series == NULL)) {
return EINVAL;
}
series_file = crm_strdup_printf("%s/%s.last", directory, series);
if (chown(series_file, uid, gid) < 0) {
rc = errno;
}
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_rc_str(errno));
return FALSE;
}
if (target_stat->st_uid != sys_user->pw_uid) {
crm_notice("%s is not owned by user %s " QB_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 "
QB_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_rc_str(errno));
return FALSE;
}
if (target_stat->st_gid != sys_grp->gr_gid) {
crm_notice("%s is not owned by group %s " QB_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 "
QB_XS " st_mode=0%lo",
target_name, CRM_DAEMON_GROUP,
(unsigned long) target_stat->st_mode);
return FALSE;
}
return TRUE;
}
/*!
* \internal
* \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 (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 target is writable by cluster daemon, false otherwise
*/
bool
pcmk__daemon_can_write(const char *dir, const char *file)
{
int s_res = 0;
struct stat buf;
char *full_file = NULL;
const char *target = NULL;
// Caller must supply directory
pcmk__assert(dir != NULL);
// If file is given, check whether it exists as a regular file
if (file != NULL) {
full_file = crm_strdup_printf("%s/%s", dir, file);
target = full_file;
s_res = stat(full_file, &buf);
if (s_res < 0) {
crm_notice("%s not found: %s", target, pcmk_rc_str(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 " QB_XS " st_mode=0%lo",
target, (unsigned long) buf.st_mode);
free(full_file);
return false;
}
}
// 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 not found: %s", dir, pcmk_rc_str(errno));
return false;
} else if (S_ISDIR(buf.st_mode) == FALSE) {
crm_err("%s must be a directory " QB_XS " st_mode=0%lo",
dir, (unsigned long) buf.st_mode);
return false;
}
}
if (!pcmk__daemon_user_can_write(target, &buf)
&& !pcmk__daemon_group_can_write(target, &buf)) {
crm_err("%s must be owned and writable by either user %s or group %s "
QB_XS " st_mode=0%lo",
target, CRM_DAEMON_USER, CRM_DAEMON_GROUP,
(unsigned long) buf.st_mode);
free(full_file);
return false;
}
free(full_file);
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
pcmk__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 Read the contents of a file
*
* \param[in] filename Name of file to read
* \param[out] contents Where to store file contents
*
* \return Standard Pacemaker return code
* \note On success, the caller is responsible for freeing contents.
*/
int
pcmk__file_contents(const char *filename, char **contents)
{
FILE *fp;
int length, read_len;
int rc = pcmk_rc_ok;
if ((filename == NULL) || (contents == NULL)) {
return EINVAL;
}
fp = fopen(filename, "r");
if ((fp == NULL) || (fseek(fp, 0L, SEEK_END) < 0)) {
rc = errno;
goto bail;
}
length = ftell(fp);
if (length < 0) {
rc = errno;
goto bail;
}
if (length == 0) {
*contents = NULL;
} else {
*contents = calloc(length + 1, sizeof(char));
if (*contents == NULL) {
rc = errno;
goto bail;
}
rewind(fp);
read_len = fread(*contents, 1, length, fp);
if (read_len != length) {
free(*contents);
*contents = NULL;
rc = EIO;
} else {
/* Coverity thinks *contents isn't null-terminated. It doesn't
* understand calloc().
*/
(*contents)[length] = '\0';
}
}
bail:
if (fp != NULL) {
fclose(fp);
}
return rc;
}
/*!
* \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 Standard Pacemaker return code
*/
int
pcmk__write_sync(int fd, const char *contents)
{
int rc = 0;
FILE *fp = fdopen(fd, "w");
if (fp == NULL) {
return errno;
}
if ((contents != NULL) && (fprintf(fp, "%s", contents) < 0)) {
rc = EIO;
}
if (fflush(fp) != 0) {
rc = errno;
}
if (fsync(fileno(fp)) < 0) {
rc = errno;
}
fclose(fp);
return rc;
}
/*!
* \internal
* \brief Set a file descriptor to non-blocking
*
* \param[in] fd File descriptor to use
*
* \return Standard Pacemaker return code
*/
int
pcmk__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_rc_ok;
}
/*!
* \internal
* \brief Get directory name for temporary files
*
* Return the value of the TMPDIR environment variable if it is set to a
* full path, otherwise return "/tmp".
*
* \return Name of directory to be used for temporary files
*/
const char *
pcmk__get_tmpdir(void)
{
const char *dir = getenv("TMPDIR");
return (dir && (*dir == '/'))? dir : "/tmp";
}
/*!
* \internal
* \brief Close open file descriptors
*
* Close all file descriptors (except optionally stdin, stdout, and stderr),
* which is a best practice for a new child process forked for the purpose of
* executing an external program.
*
* \param[in] bool If true, close stdin, stdout, and stderr as well
*/
void
pcmk__close_fds_in_child(bool all)
{
DIR *dir;
struct rlimit rlim;
rlim_t max_fd;
int min_fd = (all? 0 : (STDERR_FILENO + 1));
/* Find the current process's (soft) limit for open files. getrlimit()
* should always work, but have a fallback just in case.
*/
if (getrlimit(RLIMIT_NOFILE, &rlim) == 0) {
max_fd = rlim.rlim_cur - 1;
} else {
long conf_max = sysconf(_SC_OPEN_MAX);
max_fd = (conf_max > 0)? conf_max : 1024;
}
- /* /proc/self/fd (on Linux) or /dev/fd (on most OSes) contains symlinks to
- * all open files for the current process, named as the file descriptor.
- * Use this if available, because it's more efficient than a shotgun
- * approach to closing descriptors.
+ /* First try /proc. If that returns NULL (either because opening the
+ * directory failed, or because procfs isn't supported on this platform),
+ * fall back to /dev/fd.
*/
-#if HAVE_LINUX_PROCFS
- dir = opendir("/proc/self/fd");
+ dir = pcmk__procfs_fd_dir();
if (dir == NULL) {
dir = opendir("/dev/fd");
}
-#else
- dir = opendir("/dev/fd");
-#endif // HAVE_LINUX_PROCFS
+
if (dir != NULL) {
struct dirent *entry;
int dir_fd = dirfd(dir);
while ((entry = readdir(dir)) != NULL) {
int lpc = atoi(entry->d_name);
/* How could one of these entries be higher than max_fd, you ask?
* It isn't possible in normal operation, but when run under
* valgrind, valgrind can open high-numbered file descriptors for
* its own use that are higher than the process's soft limit.
* These will show up in the fd directory but aren't closable.
*/
if ((lpc >= min_fd) && (lpc <= max_fd) && (lpc != dir_fd)) {
close(lpc);
}
}
closedir(dir);
return;
}
/* If no fd directory is available, iterate over all possible descriptors.
* This is less efficient due to the overhead of many system calls.
*/
for (int lpc = max_fd; lpc >= min_fd; lpc--) {
close(lpc);
}
}
/*!
* \brief Duplicate a file path, inserting a prefix if not absolute
*
* \param[in] filename File path to duplicate
* \param[in] dirname If filename is not absolute, prefix to add
*
* \return Newly allocated memory with full path (guaranteed non-NULL)
*/
char *
pcmk__full_path(const char *filename, const char *dirname)
{
pcmk__assert(filename != NULL);
if (filename[0] == '/') {
return pcmk__str_copy(filename);
}
pcmk__assert(dirname != NULL);
return crm_strdup_printf("%s/%s", dirname, filename);
}
diff --git a/lib/common/procfs.c b/lib/common/procfs.c
index 91853aafe7..92738a9beb 100644
--- a/lib/common/procfs.c
+++ b/lib/common/procfs.c
@@ -1,255 +1,277 @@
/*
* Copyright 2015-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 <crm_internal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <dirent.h>
#include <ctype.h>
#if HAVE_LINUX_PROCFS
/*!
* \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(const 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;
}
#endif // HAVE_LINUX_PROCFS
/*!
* \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)
{
#if HAVE_LINUX_PROCFS
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;
#else
return 0;
#endif // HAVE_LINUX_PROCFS
}
/*!
* \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)
{
#if HAVE_LINUX_PROCFS
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;
#else
return 1;
#endif // HAVE_LINUX_PROCFS
}
/*!
* \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 HAVE_LINUX_PROCFS
char procfs_exe_path[PATH_MAX];
ssize_t link_rc;
if (snprintf(procfs_exe_path, PATH_MAX, "/proc/%lld/exe",
(long long) pid) >= PATH_MAX) {
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 // HAVE_LINUX_PROCFS
}
/*!
* \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 HAVE_LINUX_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 // HAVE_LINUX_PROCFS
}
+/*!
+ * \internal
+ * \brief Return an open handle on the directory containing links to open file
+ * descriptors, or NULL on error
+ */
+DIR *
+pcmk__procfs_fd_dir(void)
+{
+ DIR *dir = NULL;
+
+ /* /proc/self/fd (on Linux) or /dev/fd (on most OSes) contains symlinks to
+ * all open files for the current process, named as the file descriptor.
+ * Use this if available, because it's more efficient than a shotgun
+ * approach to closing descriptors.
+ */
+#if HAVE_LINUX_PROCFS
+ dir = opendir("/proc/self/fd");
+#endif // HAVE_LINUX_PROCFS
+
+ return dir;
+}
+
/*!
* \internal
* \brief Trigger a sysrq command if supported on current platform
*
* \param[in] t Sysrq command to trigger
*/
void
pcmk__sysrq_trigger(char t)
{
#if HAVE_LINUX_PROCFS
// Root can always write here, regardless of kernel.sysrq value
FILE *procf = fopen("/proc/sysrq-trigger", "a");
if (procf == NULL) {
crm_warn("Could not open sysrq-trigger: %s", strerror(errno));
} else {
fprintf(procf, "%c\n", t);
fclose(procf);
}
#endif // HAVE_LINUX_PROCFS
}
File Metadata
Details
Attached
Mime Type
text/x-diff
Expires
Sat, Nov 23, 5:36 AM (6 h, 9 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
1018262
Default Alt Text
(38 KB)
Attached To
Mode
rP Pacemaker
Attached
Detach File
Event Timeline
Log In to Comment