Page MenuHomeClusterLabs Projects

No OneTemporary

diff --git a/daemons/schedulerd/schedulerd_messages.c b/daemons/schedulerd/schedulerd_messages.c
index ac666d0a0c..79b0f7d0ac 100644
--- a/daemons/schedulerd/schedulerd_messages.c
+++ b/daemons/schedulerd/schedulerd_messages.c
@@ -1,340 +1,340 @@
/*
* 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 <crm_internal.h>
#include <crm/crm.h>
#include <crm/common/xml.h>
#include <pacemaker-internal.h>
#include <stdbool.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
#include "pacemaker-schedulerd.h"
static GHashTable *schedulerd_handlers = NULL;
static pcmk_scheduler_t *
init_working_set(void)
{
pcmk_scheduler_t *scheduler = pe_new_working_set();
pcmk__mem_assert(scheduler);
crm_config_error = FALSE;
crm_config_warning = FALSE;
was_processing_error = FALSE;
was_processing_warning = FALSE;
scheduler->priv = logger_out;
return scheduler;
}
static xmlNode *
handle_pecalc_request(pcmk__request_t *request)
{
static struct series_s {
const char *name;
const char *param;
/* Maximum number of inputs of this kind to save to disk.
* If -1, save all; if 0, save none.
*/
int wrap;
} series[] = {
{ "pe-error", PCMK_OPT_PE_ERROR_SERIES_MAX, -1 },
{ "pe-warn", PCMK_OPT_PE_WARN_SERIES_MAX, 5000 },
{ "pe-input", PCMK_OPT_PE_INPUT_SERIES_MAX, 4000 },
};
xmlNode *msg = request->xml;
xmlNode *wrapper = pcmk__xe_first_child(msg, PCMK__XE_CRM_XML, NULL, NULL);
xmlNode *xml_data = pcmk__xe_first_child(wrapper, NULL, NULL, NULL);
static char *last_digest = NULL;
static char *filename = NULL;
- unsigned int seq;
+ unsigned int seq = 0U;
int series_id = 0;
int series_wrap = 0;
char *digest = NULL;
const char *value = NULL;
time_t execution_date = time(NULL);
xmlNode *converted = NULL;
xmlNode *reply = NULL;
bool is_repoke = false;
bool process = true;
pcmk_scheduler_t *scheduler = init_working_set();
pcmk__ipc_send_ack(request->ipc_client, request->ipc_id, request->ipc_flags,
PCMK__XE_ACK, NULL, CRM_EX_INDETERMINATE);
digest = calculate_xml_versioned_digest(xml_data, FALSE, FALSE,
CRM_FEATURE_SET);
converted = pcmk__xml_copy(NULL, xml_data);
if (pcmk__update_configured_schema(&converted, true) != pcmk_rc_ok) {
scheduler->graph = pcmk__xe_create(NULL, PCMK__XE_TRANSITION_GRAPH);
crm_xml_add_int(scheduler->graph, "transition_id", 0);
crm_xml_add_int(scheduler->graph, PCMK_OPT_CLUSTER_DELAY, 0);
process = false;
free(digest);
} else if (pcmk__str_eq(digest, last_digest, pcmk__str_casei)) {
is_repoke = true;
free(digest);
} else {
free(last_digest);
last_digest = digest;
}
if (process) {
pcmk__schedule_actions(converted,
pcmk_sched_no_counts
|pcmk_sched_no_compat
|pcmk_sched_show_utilization, scheduler);
}
// Get appropriate index into series[] array
if (was_processing_error || crm_config_error) {
series_id = 0;
} else if (was_processing_warning || crm_config_warning) {
series_id = 1;
} else {
series_id = 2;
}
value = pcmk__cluster_option(scheduler->config_hash,
series[series_id].param);
if ((value == NULL)
|| (pcmk__scan_min_int(value, &series_wrap, -1) != pcmk_rc_ok)) {
series_wrap = series[series_id].wrap;
}
if (pcmk__read_series_sequence(PE_STATE_DIR, series[series_id].name,
&seq) != pcmk_rc_ok) {
// @TODO maybe handle errors better ...
- seq = 0;
+ seq = 0U;
}
crm_trace("Series %s: wrap=%d, seq=%u, pref=%s",
series[series_id].name, series_wrap, seq, value);
scheduler->input = NULL;
reply = create_reply(msg, scheduler->graph);
if (reply == NULL) {
pcmk__format_result(&request->result, CRM_EX_ERROR, PCMK_EXEC_ERROR,
"Failed building ping reply for client %s",
pcmk__client_name(request->ipc_client));
goto done;
}
if (series_wrap == 0) { // Don't save any inputs of this kind
free(filename);
filename = NULL;
} else if (!is_repoke) { // Input changed, save to disk
free(filename);
filename = pcmk__series_filename(PE_STATE_DIR,
series[series_id].name, seq, true);
}
crm_xml_add(reply, PCMK__XA_CRM_TGRAPH_IN, filename);
crm_xml_add_int(reply, PCMK__XA_GRAPH_ERRORS, was_processing_error);
crm_xml_add_int(reply, PCMK__XA_GRAPH_WARNINGS, was_processing_warning);
crm_xml_add_int(reply, PCMK__XA_CONFIG_ERRORS, crm_config_error);
crm_xml_add_int(reply, PCMK__XA_CONFIG_WARNINGS, crm_config_warning);
pcmk__log_transition_summary(filename);
if (series_wrap == 0) {
crm_debug("Not saving input to disk (disabled by configuration)");
} else if (is_repoke) {
crm_info("Input has not changed since last time, not saving to disk");
} else {
unlink(filename);
crm_xml_add_ll(xml_data, PCMK_XA_EXECUTION_DATE,
(long long) execution_date);
pcmk__xml_write_file(xml_data, filename, true, NULL);
pcmk__write_series_sequence(PE_STATE_DIR, series[series_id].name,
++seq, series_wrap);
}
pcmk__set_result(&request->result, CRM_EX_OK, PCMK_EXEC_DONE, NULL);
done:
free_xml(converted);
pe_free_working_set(scheduler);
return reply;
}
static xmlNode *
handle_unknown_request(pcmk__request_t *request)
{
pcmk__ipc_send_ack(request->ipc_client, request->ipc_id, request->ipc_flags,
PCMK__XE_ACK, NULL, CRM_EX_INVALID_PARAM);
pcmk__format_result(&request->result, CRM_EX_PROTOCOL, PCMK_EXEC_INVALID,
"Unknown IPC request type '%s' (bug?)",
pcmk__client_name(request->ipc_client));
return NULL;
}
static xmlNode *
handle_hello_request(pcmk__request_t *request)
{
pcmk__ipc_send_ack(request->ipc_client, request->ipc_id, request->ipc_flags,
PCMK__XE_ACK, NULL, CRM_EX_INDETERMINATE);
crm_trace("Received IPC hello from %s", pcmk__client_name(request->ipc_client));
pcmk__set_result(&request->result, CRM_EX_OK, PCMK_EXEC_DONE, NULL);
return NULL;
}
static void
schedulerd_register_handlers(void)
{
pcmk__server_command_t handlers[] = {
{ CRM_OP_HELLO, handle_hello_request },
{ CRM_OP_PECALC, handle_pecalc_request },
{ NULL, handle_unknown_request },
};
schedulerd_handlers = pcmk__register_handlers(handlers);
}
static int32_t
pe_ipc_accept(qb_ipcs_connection_t * c, uid_t uid, gid_t gid)
{
crm_trace("Connection %p", c);
if (pcmk__new_client(c, uid, gid) == NULL) {
return -ENOMEM;
}
return 0;
}
static int32_t
pe_ipc_dispatch(qb_ipcs_connection_t * qbc, void *data, size_t size)
{
uint32_t id = 0;
uint32_t flags = 0;
xmlNode *msg = NULL;
pcmk__client_t *c = pcmk__find_client(qbc);
const char *sys_to = NULL;
CRM_CHECK(c != NULL, return 0);
if (schedulerd_handlers == NULL) {
schedulerd_register_handlers();
}
msg = pcmk__client_data2xml(c, data, &id, &flags);
if (msg == NULL) {
pcmk__ipc_send_ack(c, id, flags, PCMK__XE_ACK, NULL, CRM_EX_PROTOCOL);
return 0;
}
sys_to = crm_element_value(msg, PCMK__XA_CRM_SYS_TO);
if (pcmk__str_eq(crm_element_value(msg, PCMK__XA_SUBT),
PCMK__VALUE_RESPONSE, pcmk__str_none)) {
pcmk__ipc_send_ack(c, id, flags, PCMK__XE_ACK, NULL,
CRM_EX_INDETERMINATE);
crm_info("Ignoring IPC reply from %s", pcmk__client_name(c));
} else if (!pcmk__str_eq(sys_to, CRM_SYSTEM_PENGINE, pcmk__str_none)) {
pcmk__ipc_send_ack(c, id, flags, PCMK__XE_ACK, NULL,
CRM_EX_INDETERMINATE);
crm_info("Ignoring invalid IPC message: to '%s' not "
CRM_SYSTEM_PENGINE, pcmk__s(sys_to, ""));
} else {
char *log_msg = NULL;
const char *reason = NULL;
xmlNode *reply = NULL;
pcmk__request_t request = {
.ipc_client = c,
.ipc_id = id,
.ipc_flags = flags,
.peer = NULL,
.xml = msg,
.call_options = 0,
.result = PCMK__UNKNOWN_RESULT,
};
request.op = crm_element_value_copy(request.xml, PCMK__XA_CRM_TASK);
CRM_CHECK(request.op != NULL, return 0);
reply = pcmk__process_request(&request, schedulerd_handlers);
if (reply != NULL) {
pcmk__ipc_send_xml(c, id, reply, crm_ipc_server_event);
free_xml(reply);
}
reason = request.result.exit_reason;
log_msg = crm_strdup_printf("Processed %s request from %s %s: %s%s%s%s",
request.op, pcmk__request_origin_type(&request),
pcmk__request_origin(&request),
pcmk_exec_status_str(request.result.execution_status),
(reason == NULL)? "" : " (",
(reason == NULL)? "" : reason,
(reason == NULL)? "" : ")");
if (!pcmk__result_ok(&request.result)) {
crm_warn("%s", log_msg);
} else {
crm_debug("%s", log_msg);
}
free(log_msg);
pcmk__reset_request(&request);
}
free_xml(msg);
return 0;
}
/* Error code means? */
static int32_t
pe_ipc_closed(qb_ipcs_connection_t * c)
{
pcmk__client_t *client = pcmk__find_client(c);
if (client == NULL) {
return 0;
}
crm_trace("Connection %p", c);
pcmk__free_client(client);
return 0;
}
static void
pe_ipc_destroy(qb_ipcs_connection_t * c)
{
crm_trace("Connection %p", c);
pe_ipc_closed(c);
}
struct qb_ipcs_service_handlers ipc_callbacks = {
.connection_accept = pe_ipc_accept,
.connection_created = NULL,
.msg_process = pe_ipc_dispatch,
.connection_closed = pe_ipc_closed,
.connection_destroyed = pe_ipc_destroy
};
diff --git a/include/crm/common/io_internal.h b/include/crm/common/io_internal.h
index 6b6ef33361..42840fd5f2 100644
--- a/include/crm/common/io_internal.h
+++ b/include/crm/common/io_internal.h
@@ -1,57 +1,57 @@
/*
* Copyright 2022-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_IO_INTERNAL__H
#define PCMK__CRM_COMMON_IO_INTERNAL__H
#include <fcntl.h> // open()
#include <stdbool.h> // bool
#include <unistd.h> // uid_t, gid_t
int pcmk__real_path(const char *path, char **resolved_path);
char *pcmk__series_filename(const char *directory, const char *series,
- int sequence, bool bzip);
+ unsigned 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);
}
#endif // PCMK__CRM_COMMON_IO_INTERNAL__H
diff --git a/lib/cib/cib_file.c b/lib/cib/cib_file.c
index 9bfb433533..e573895031 100644
--- a/lib/cib/cib_file.c
+++ b/lib/cib/cib_file.c
@@ -1,1188 +1,1188 @@
/*
* Original copyright 2004 International Business Machines
* Later changes copyright 2008-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 <unistd.h>
#include <limits.h>
#include <stdlib.h>
#include <stdint.h>
#include <stdio.h>
#include <stdarg.h>
#include <string.h>
#include <pwd.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <glib.h>
#include <crm/crm.h>
#include <crm/cib/internal.h>
#include <crm/common/ipc.h>
#include <crm/common/xml.h>
#include <crm/common/xml_internal.h>
#define CIB_SERIES "cib"
#define CIB_SERIES_MAX 100
#define CIB_SERIES_BZIP FALSE /* Must be false because archived copies are
created with hard links
*/
#define CIB_LIVE_NAME CIB_SERIES ".xml"
// key: client ID (const char *) -> value: client (cib_t *)
static GHashTable *client_table = NULL;
enum cib_file_flags {
cib_file_flag_dirty = (1 << 0),
cib_file_flag_live = (1 << 1),
};
typedef struct cib_file_opaque_s {
char *id;
char *filename;
uint32_t flags; // Group of enum cib_file_flags
xmlNode *cib_xml;
} cib_file_opaque_t;
static int cib_file_process_commit_transaction(const char *op, int options,
const char *section,
xmlNode *req, xmlNode *input,
xmlNode *existing_cib,
xmlNode **result_cib,
xmlNode **answer);
/*!
* \internal
* \brief Add a CIB file client to client table
*
* \param[in] cib CIB client
*/
static void
register_client(const cib_t *cib)
{
cib_file_opaque_t *private = cib->variant_opaque;
if (client_table == NULL) {
client_table = pcmk__strkey_table(NULL, NULL);
}
g_hash_table_insert(client_table, private->id, (gpointer) cib);
}
/*!
* \internal
* \brief Remove a CIB file client from client table
*
* \param[in] cib CIB client
*/
static void
unregister_client(const cib_t *cib)
{
cib_file_opaque_t *private = cib->variant_opaque;
if (client_table == NULL) {
return;
}
g_hash_table_remove(client_table, private->id);
/* @COMPAT: Add to crm_exit() when libcib and libcrmcommon are merged,
* instead of destroying the client table when there are no more clients.
*/
if (g_hash_table_size(client_table) == 0) {
g_hash_table_destroy(client_table);
client_table = NULL;
}
}
/*!
* \internal
* \brief Look up a CIB file client by its ID
*
* \param[in] client_id CIB client ID
*
* \return CIB client with matching ID if found, or \p NULL otherwise
*/
static cib_t *
get_client(const char *client_id)
{
if (client_table == NULL) {
return NULL;
}
return g_hash_table_lookup(client_table, (gpointer) client_id);
}
static const cib__op_fn_t cib_op_functions[] = {
[cib__op_apply_patch] = cib_process_diff,
[cib__op_bump] = cib_process_bump,
[cib__op_commit_transact] = cib_file_process_commit_transaction,
[cib__op_create] = cib_process_create,
[cib__op_delete] = cib_process_delete,
[cib__op_erase] = cib_process_erase,
[cib__op_modify] = cib_process_modify,
[cib__op_query] = cib_process_query,
[cib__op_replace] = cib_process_replace,
[cib__op_upgrade] = cib_process_upgrade,
};
/* cib_file_backup() and cib_file_write_with_digest() need to chown the
* written files only in limited circumstances, so these variables allow
* that to be indicated without affecting external callers
*/
static uid_t cib_file_owner = 0;
static uid_t cib_file_group = 0;
static gboolean cib_do_chown = FALSE;
#define cib_set_file_flags(cibfile, flags_to_set) do { \
(cibfile)->flags = pcmk__set_flags_as(__func__, __LINE__, \
LOG_TRACE, "CIB file", \
cibfile->filename, \
(cibfile)->flags, \
(flags_to_set), \
#flags_to_set); \
} while (0)
#define cib_clear_file_flags(cibfile, flags_to_clear) do { \
(cibfile)->flags = pcmk__clear_flags_as(__func__, __LINE__, \
LOG_TRACE, "CIB file", \
cibfile->filename, \
(cibfile)->flags, \
(flags_to_clear), \
#flags_to_clear); \
} while (0)
/*!
* \internal
* \brief Get the function that performs a given CIB file operation
*
* \param[in] operation Operation whose function to look up
*
* \return Function that performs \p operation for a CIB file client
*/
static cib__op_fn_t
file_get_op_function(const cib__operation_t *operation)
{
enum cib__op_type type = operation->type;
pcmk__assert(type >= 0);
if (type >= PCMK__NELEM(cib_op_functions)) {
return NULL;
}
return cib_op_functions[type];
}
/*!
* \internal
* \brief Check whether a file is the live CIB
*
* \param[in] filename Name of file to check
*
* \return TRUE if file exists and its real path is same as live CIB's
*/
static gboolean
cib_file_is_live(const char *filename)
{
gboolean same = FALSE;
if (filename != NULL) {
// Canonicalize file names for true comparison
char *real_filename = NULL;
if (pcmk__real_path(filename, &real_filename) == pcmk_rc_ok) {
char *real_livename = NULL;
if (pcmk__real_path(CRM_CONFIG_DIR "/" CIB_LIVE_NAME,
&real_livename) == pcmk_rc_ok) {
same = !strcmp(real_filename, real_livename);
free(real_livename);
}
free(real_filename);
}
}
return same;
}
static int
cib_file_process_request(cib_t *cib, xmlNode *request, xmlNode **output)
{
int rc = pcmk_ok;
const cib__operation_t *operation = NULL;
cib__op_fn_t op_function = NULL;
int call_id = 0;
int call_options = cib_none;
const char *op = crm_element_value(request, PCMK__XA_CIB_OP);
const char *section = crm_element_value(request, PCMK__XA_CIB_SECTION);
xmlNode *wrapper = pcmk__xe_first_child(request, PCMK__XE_CIB_CALLDATA,
NULL, NULL);
xmlNode *data = pcmk__xe_first_child(wrapper, NULL, NULL, NULL);
bool changed = false;
bool read_only = false;
xmlNode *result_cib = NULL;
xmlNode *cib_diff = NULL;
cib_file_opaque_t *private = cib->variant_opaque;
// We error checked these in callers
cib__get_operation(op, &operation);
op_function = file_get_op_function(operation);
crm_element_value_int(request, PCMK__XA_CIB_CALLID, &call_id);
crm_element_value_int(request, PCMK__XA_CIB_CALLOPT, &call_options);
read_only = !pcmk_is_set(operation->flags, cib__op_attr_modifies);
// Mirror the logic in prepare_input() in pacemaker-based
if ((section != NULL) && pcmk__xe_is(data, PCMK_XE_CIB)) {
data = pcmk_find_cib_element(data, section);
}
rc = cib_perform_op(cib, op, call_options, op_function, read_only, section,
request, data, true, &changed, &private->cib_xml,
&result_cib, &cib_diff, output);
if (pcmk_is_set(call_options, cib_transaction)) {
/* The rest of the logic applies only to the transaction as a whole, not
* to individual requests.
*/
goto done;
}
if (rc == -pcmk_err_schema_validation) {
// Show validation errors to stderr
pcmk__validate_xml(result_cib, NULL, NULL, NULL);
} else if ((rc == pcmk_ok) && !read_only) {
pcmk__log_xml_patchset(LOG_DEBUG, cib_diff);
if (result_cib != private->cib_xml) {
free_xml(private->cib_xml);
private->cib_xml = result_cib;
}
cib_set_file_flags(private, cib_file_flag_dirty);
}
// Global operation callback (deprecated)
if (cib->op_callback != NULL) {
cib->op_callback(NULL, call_id, rc, *output);
}
done:
if ((result_cib != private->cib_xml) && (result_cib != *output)) {
free_xml(result_cib);
}
free_xml(cib_diff);
return rc;
}
static int
cib_file_perform_op_delegate(cib_t *cib, const char *op, const char *host,
const char *section, xmlNode *data,
xmlNode **output_data, int call_options,
const char *user_name)
{
int rc = pcmk_ok;
xmlNode *request = NULL;
xmlNode *output = NULL;
cib_file_opaque_t *private = cib->variant_opaque;
const cib__operation_t *operation = NULL;
crm_info("Handling %s operation for %s as %s",
pcmk__s(op, "invalid"), pcmk__s(section, "entire CIB"),
pcmk__s(user_name, "default user"));
if (output_data != NULL) {
*output_data = NULL;
}
if (cib->state == cib_disconnected) {
return -ENOTCONN;
}
rc = cib__get_operation(op, &operation);
rc = pcmk_rc2legacy(rc);
if (rc != pcmk_ok) {
// @COMPAT: At compatibility break, use rc directly
return -EPROTONOSUPPORT;
}
if (file_get_op_function(operation) == NULL) {
// @COMPAT: At compatibility break, use EOPNOTSUPP
crm_err("Operation %s is not supported by CIB file clients", op);
return -EPROTONOSUPPORT;
}
cib__set_call_options(call_options, "file operation", cib_no_mtime);
rc = cib__create_op(cib, op, host, section, data, call_options, user_name,
NULL, &request);
if (rc != pcmk_ok) {
return rc;
}
crm_xml_add(request, PCMK_XE_ACL_TARGET, user_name);
crm_xml_add(request, PCMK__XA_CIB_CLIENTID, private->id);
if (pcmk_is_set(call_options, cib_transaction)) {
rc = cib__extend_transaction(cib, request);
goto done;
}
rc = cib_file_process_request(cib, request, &output);
if ((output_data != NULL) && (output != NULL)) {
if (output->doc == private->cib_xml->doc) {
*output_data = pcmk__xml_copy(NULL, output);
} else {
*output_data = output;
}
}
done:
if ((output != NULL)
&& (output->doc != private->cib_xml->doc)
&& ((output_data == NULL) || (output != *output_data))) {
free_xml(output);
}
free_xml(request);
return rc;
}
/*!
* \internal
* \brief Read CIB from disk and validate it against XML schema
*
* \param[in] filename Name of file to read CIB from
* \param[out] output Where to store the read CIB XML
*
* \return pcmk_ok on success,
* -ENXIO if file does not exist (or stat() otherwise fails), or
* -pcmk_err_schema_validation if XML doesn't parse or validate
* \note If filename is the live CIB, this will *not* verify its digest,
* though that functionality would be trivial to add here.
* Also, this will *not* verify that the file is writable,
* because some callers might not need to write.
*/
static int
load_file_cib(const char *filename, xmlNode **output)
{
struct stat buf;
xmlNode *root = NULL;
/* Ensure file is readable */
if (strcmp(filename, "-") && (stat(filename, &buf) < 0)) {
return -ENXIO;
}
/* Parse XML from file */
root = pcmk__xml_read(filename);
if (root == NULL) {
return -pcmk_err_schema_validation;
}
/* Add a status section if not already present */
if (pcmk__xe_first_child(root, PCMK_XE_STATUS, NULL, NULL) == NULL) {
pcmk__xe_create(root, PCMK_XE_STATUS);
}
/* Validate XML against its specified schema */
if (!pcmk__configured_schema_validates(root)) {
const char *schema = crm_element_value(root, PCMK_XA_VALIDATE_WITH);
crm_err("CIB does not validate against %s, or that schema is unknown", schema);
free_xml(root);
return -pcmk_err_schema_validation;
}
/* Remember the parsed XML for later use */
*output = root;
return pcmk_ok;
}
static int
cib_file_signon(cib_t *cib, const char *name, enum cib_conn_type type)
{
int rc = pcmk_ok;
cib_file_opaque_t *private = cib->variant_opaque;
if (private->filename == NULL) {
rc = -EINVAL;
} else {
rc = load_file_cib(private->filename, &private->cib_xml);
}
if (rc == pcmk_ok) {
crm_debug("Opened connection to local file '%s' for %s",
private->filename, name);
cib->state = cib_connected_command;
cib->type = cib_command;
register_client(cib);
} else {
crm_info("Connection to local file '%s' for %s (client %s) failed: %s",
private->filename, name, private->id, pcmk_strerror(rc));
}
return rc;
}
/*!
* \internal
* \brief Write out the in-memory CIB to a live CIB file
*
* \param[in] cib_root Root of XML tree to write
* \param[in,out] path Full path to file to write
*
* \return 0 on success, -1 on failure
*/
static int
cib_file_write_live(xmlNode *cib_root, char *path)
{
uid_t uid = geteuid();
struct passwd *daemon_pwent;
char *sep = strrchr(path, '/');
const char *cib_dirname, *cib_filename;
int rc = 0;
/* Get the desired uid/gid */
errno = 0;
daemon_pwent = getpwnam(CRM_DAEMON_USER);
if (daemon_pwent == NULL) {
crm_perror(LOG_ERR, "Could not find %s user", CRM_DAEMON_USER);
return -1;
}
/* If we're root, we can change the ownership;
* if we're daemon, anything we create will be OK;
* otherwise, block access so we don't create wrong owner
*/
if ((uid != 0) && (uid != daemon_pwent->pw_uid)) {
crm_perror(LOG_ERR, "Must be root or %s to modify live CIB",
CRM_DAEMON_USER);
return 0;
}
/* fancy footwork to separate dirname from filename
* (we know the canonical name maps to the live CIB,
* but the given name might be relative, or symlinked)
*/
if (sep == NULL) { /* no directory component specified */
cib_dirname = "./";
cib_filename = path;
} else if (sep == path) { /* given name is in / */
cib_dirname = "/";
cib_filename = path + 1;
} else { /* typical case; split given name into parts */
*sep = '\0';
cib_dirname = path;
cib_filename = sep + 1;
}
/* if we're root, we want to update the file ownership */
if (uid == 0) {
cib_file_owner = daemon_pwent->pw_uid;
cib_file_group = daemon_pwent->pw_gid;
cib_do_chown = TRUE;
}
/* write the file */
if (cib_file_write_with_digest(cib_root, cib_dirname,
cib_filename) != pcmk_ok) {
rc = -1;
}
/* turn off file ownership changes, for other callers */
if (uid == 0) {
cib_do_chown = FALSE;
}
/* undo fancy stuff */
if ((sep != NULL) && (*sep == '\0')) {
*sep = '/';
}
return rc;
}
/*!
* \internal
* \brief Sign-off method for CIB file variants
*
* This will write the file to disk if needed, and free the in-memory CIB. If
* the file is the live CIB, it will compute and write a signature as well.
*
* \param[in,out] cib CIB object to sign off
*
* \return pcmk_ok on success, pcmk_err_generic on failure
* \todo This method should refuse to write the live CIB if the CIB manager is
* running.
*/
static int
cib_file_signoff(cib_t *cib)
{
int rc = pcmk_ok;
cib_file_opaque_t *private = cib->variant_opaque;
crm_debug("Disconnecting from the CIB manager");
cib->state = cib_disconnected;
cib->type = cib_no_connection;
unregister_client(cib);
cib->cmds->end_transaction(cib, false, cib_none);
/* If the in-memory CIB has been changed, write it to disk */
if (pcmk_is_set(private->flags, cib_file_flag_dirty)) {
/* If this is the live CIB, write it out with a digest */
if (pcmk_is_set(private->flags, cib_file_flag_live)) {
if (cib_file_write_live(private->cib_xml, private->filename) < 0) {
rc = pcmk_err_generic;
}
/* Otherwise, it's a simple write */
} else {
bool compress = pcmk__ends_with_ext(private->filename, ".bz2");
if (pcmk__xml_write_file(private->cib_xml, private->filename,
compress, NULL) != pcmk_rc_ok) {
rc = pcmk_err_generic;
}
}
if (rc == pcmk_ok) {
crm_info("Wrote CIB to %s", private->filename);
cib_clear_file_flags(private, cib_file_flag_dirty);
} else {
crm_err("Could not write CIB to %s", private->filename);
}
}
/* Free the in-memory CIB */
free_xml(private->cib_xml);
private->cib_xml = NULL;
return rc;
}
static int
cib_file_free(cib_t *cib)
{
int rc = pcmk_ok;
if (cib->state != cib_disconnected) {
rc = cib_file_signoff(cib);
}
if (rc == pcmk_ok) {
cib_file_opaque_t *private = cib->variant_opaque;
free(private->id);
free(private->filename);
free(private);
free(cib->cmds);
free(cib->user);
free(cib);
} else {
fprintf(stderr, "Couldn't sign off: %d\n", rc);
}
return rc;
}
static int
cib_file_inputfd(cib_t *cib)
{
return -EPROTONOSUPPORT;
}
static int
cib_file_register_notification(cib_t *cib, const char *callback, int enabled)
{
return -EPROTONOSUPPORT;
}
static int
cib_file_set_connection_dnotify(cib_t *cib,
void (*dnotify) (gpointer user_data))
{
return -EPROTONOSUPPORT;
}
/*!
* \internal
* \brief Get the given CIB connection's unique client identifier
*
* \param[in] cib CIB connection
* \param[out] async_id If not \p NULL, where to store asynchronous client ID
* \param[out] sync_id If not \p NULL, where to store synchronous client ID
*
* \return Legacy Pacemaker return code
*
* \note This is the \p cib_file variant implementation of
* \p cib_api_operations_t:client_id().
*/
static int
cib_file_client_id(const cib_t *cib, const char **async_id,
const char **sync_id)
{
cib_file_opaque_t *private = cib->variant_opaque;
if (async_id != NULL) {
*async_id = private->id;
}
if (sync_id != NULL) {
*sync_id = private->id;
}
return pcmk_ok;
}
cib_t *
cib_file_new(const char *cib_location)
{
cib_t *cib = NULL;
cib_file_opaque_t *private = NULL;
char *filename = NULL;
if (cib_location == NULL) {
cib_location = getenv("CIB_file");
if (cib_location == NULL) {
return NULL; // Shouldn't be possible if we were called internally
}
}
cib = cib_new_variant();
if (cib == NULL) {
return NULL;
}
filename = strdup(cib_location);
if (filename == NULL) {
free(cib);
return NULL;
}
private = calloc(1, sizeof(cib_file_opaque_t));
if (private == NULL) {
free(cib);
free(filename);
return NULL;
}
private->id = crm_generate_uuid();
private->filename = filename;
cib->variant = cib_file;
cib->variant_opaque = private;
private->flags = 0;
if (cib_file_is_live(cib_location)) {
cib_set_file_flags(private, cib_file_flag_live);
crm_trace("File %s detected as live CIB", cib_location);
}
/* assign variant specific ops */
cib->delegate_fn = cib_file_perform_op_delegate;
cib->cmds->signon = cib_file_signon;
cib->cmds->signoff = cib_file_signoff;
cib->cmds->free = cib_file_free;
cib->cmds->inputfd = cib_file_inputfd; // Deprecated method
cib->cmds->register_notification = cib_file_register_notification;
cib->cmds->set_connection_dnotify = cib_file_set_connection_dnotify;
cib->cmds->client_id = cib_file_client_id;
return cib;
}
/*!
* \internal
* \brief Compare the calculated digest of an XML tree against a signature file
*
* \param[in] root Root of XML tree to compare
* \param[in] sigfile Name of signature file containing digest to compare
*
* \return TRUE if digests match or signature file does not exist, else FALSE
*/
static gboolean
cib_file_verify_digest(xmlNode *root, const char *sigfile)
{
gboolean passed = FALSE;
char *expected;
int rc = pcmk__file_contents(sigfile, &expected);
switch (rc) {
case pcmk_rc_ok:
if (expected == NULL) {
crm_err("On-disk digest at %s is empty", sigfile);
return FALSE;
}
break;
case ENOENT:
crm_warn("No on-disk digest present at %s", sigfile);
return TRUE;
default:
crm_err("Could not read on-disk digest from %s: %s",
sigfile, pcmk_rc_str(rc));
return FALSE;
}
passed = pcmk__verify_digest(root, expected);
free(expected);
return passed;
}
/*!
* \internal
* \brief Read an XML tree from a file and verify its digest
*
* \param[in] filename Name of XML file to read
* \param[in] sigfile Name of signature file containing digest to compare
* \param[out] root If non-NULL, will be set to pointer to parsed XML tree
*
* \return 0 if file was successfully read, parsed and verified, otherwise:
* -errno on stat() failure,
* -pcmk_err_cib_corrupt if file size is 0 or XML is not parseable, or
* -pcmk_err_cib_modified if digests do not match
* \note If root is non-NULL, it is the caller's responsibility to free *root on
* successful return.
*/
int
cib_file_read_and_verify(const char *filename, const char *sigfile, xmlNode **root)
{
int s_res;
struct stat buf;
char *local_sigfile = NULL;
xmlNode *local_root = NULL;
pcmk__assert(filename != NULL);
if (root) {
*root = NULL;
}
/* Verify that file exists and its size is nonzero */
s_res = stat(filename, &buf);
if (s_res < 0) {
crm_perror(LOG_WARNING, "Could not verify cluster configuration file %s", filename);
return -errno;
} else if (buf.st_size == 0) {
crm_warn("Cluster configuration file %s is corrupt (size is zero)", filename);
return -pcmk_err_cib_corrupt;
}
/* Parse XML */
local_root = pcmk__xml_read(filename);
if (local_root == NULL) {
crm_warn("Cluster configuration file %s is corrupt (unparseable as XML)", filename);
return -pcmk_err_cib_corrupt;
}
/* If sigfile is not specified, use original file name plus .sig */
if (sigfile == NULL) {
sigfile = local_sigfile = crm_strdup_printf("%s.sig", filename);
}
/* Verify that digests match */
if (cib_file_verify_digest(local_root, sigfile) == FALSE) {
free(local_sigfile);
free_xml(local_root);
return -pcmk_err_cib_modified;
}
free(local_sigfile);
if (root) {
*root = local_root;
} else {
free_xml(local_root);
}
return pcmk_ok;
}
/*!
* \internal
* \brief Back up a CIB
*
* \param[in] cib_dirname Directory containing CIB file and backups
* \param[in] cib_filename Name (relative to cib_dirname) of CIB file to back up
*
* \return 0 on success, -1 on error
*/
static int
cib_file_backup(const char *cib_dirname, const char *cib_filename)
{
int rc = 0;
- unsigned int seq;
+ unsigned int seq = 0U;
char *cib_path = crm_strdup_printf("%s/%s", cib_dirname, cib_filename);
char *cib_digest = crm_strdup_printf("%s.sig", cib_path);
char *backup_path;
char *backup_digest;
// Determine backup and digest file names
if (pcmk__read_series_sequence(cib_dirname, CIB_SERIES,
&seq) != pcmk_rc_ok) {
// @TODO maybe handle errors better ...
- seq = 0;
+ seq = 0U;
}
backup_path = pcmk__series_filename(cib_dirname, CIB_SERIES, seq,
CIB_SERIES_BZIP);
backup_digest = crm_strdup_printf("%s.sig", backup_path);
/* Remove the old backups if they exist */
unlink(backup_path);
unlink(backup_digest);
/* Back up the CIB, by hard-linking it to the backup name */
if ((link(cib_path, backup_path) < 0) && (errno != ENOENT)) {
crm_perror(LOG_ERR, "Could not archive %s by linking to %s",
cib_path, backup_path);
rc = -1;
/* Back up the CIB signature similarly */
} else if ((link(cib_digest, backup_digest) < 0) && (errno != ENOENT)) {
crm_perror(LOG_ERR, "Could not archive %s by linking to %s",
cib_digest, backup_digest);
rc = -1;
/* Update the last counter and ensure everything is sync'd to media */
} else {
pcmk__write_series_sequence(cib_dirname, CIB_SERIES, ++seq,
CIB_SERIES_MAX);
if (cib_do_chown) {
int rc2;
if ((chown(backup_path, cib_file_owner, cib_file_group) < 0)
&& (errno != ENOENT)) {
crm_perror(LOG_ERR, "Could not set owner of %s", backup_path);
rc = -1;
}
if ((chown(backup_digest, cib_file_owner, cib_file_group) < 0)
&& (errno != ENOENT)) {
crm_perror(LOG_ERR, "Could not set owner of %s", backup_digest);
rc = -1;
}
rc2 = pcmk__chown_series_sequence(cib_dirname, CIB_SERIES,
cib_file_owner, cib_file_group);
if (rc2 != pcmk_rc_ok) {
crm_err("Could not set owner of sequence file in %s: %s",
cib_dirname, pcmk_rc_str(rc2));
rc = -1;
}
}
pcmk__sync_directory(cib_dirname);
crm_info("Archived previous version as %s", backup_path);
}
free(cib_path);
free(cib_digest);
free(backup_path);
free(backup_digest);
return rc;
}
/*!
* \internal
* \brief Prepare CIB XML to be written to disk
*
* Set \c PCMK_XA_NUM_UPDATES to 0, set \c PCMK_XA_CIB_LAST_WRITTEN to the
* current timestamp, and strip out the status section.
*
* \param[in,out] root Root of CIB XML tree
*
* \return void
*/
static void
cib_file_prepare_xml(xmlNode *root)
{
xmlNode *cib_status_root = NULL;
/* Always write out with num_updates=0 and current last-written timestamp */
crm_xml_add(root, PCMK_XA_NUM_UPDATES, "0");
pcmk__xe_add_last_written(root);
/* Delete status section before writing to file, because
* we discard it on startup anyway, and users get confused by it */
cib_status_root = pcmk__xe_first_child(root, PCMK_XE_STATUS, NULL, NULL);
CRM_CHECK(cib_status_root != NULL, return);
free_xml(cib_status_root);
}
/*!
* \internal
* \brief Write CIB to disk, along with a signature file containing its digest
*
* \param[in,out] cib_root Root of XML tree to write
* \param[in] cib_dirname Directory containing CIB and signature files
* \param[in] cib_filename Name (relative to cib_dirname) of file to write
*
* \return pcmk_ok on success,
* pcmk_err_cib_modified if existing cib_filename doesn't match digest,
* pcmk_err_cib_backup if existing cib_filename couldn't be backed up,
* or pcmk_err_cib_save if new cib_filename couldn't be saved
*/
int
cib_file_write_with_digest(xmlNode *cib_root, const char *cib_dirname,
const char *cib_filename)
{
int exit_rc = pcmk_ok;
int rc, fd;
char *digest = NULL;
/* Detect CIB version for diagnostic purposes */
const char *epoch = crm_element_value(cib_root, PCMK_XA_EPOCH);
const char *admin_epoch = crm_element_value(cib_root, PCMK_XA_ADMIN_EPOCH);
/* Determine full CIB and signature pathnames */
char *cib_path = crm_strdup_printf("%s/%s", cib_dirname, cib_filename);
char *digest_path = crm_strdup_printf("%s.sig", cib_path);
/* Create temporary file name patterns for writing out CIB and signature */
char *tmp_cib = crm_strdup_printf("%s/cib.XXXXXX", cib_dirname);
char *tmp_digest = crm_strdup_printf("%s/cib.XXXXXX", cib_dirname);
/* Ensure the admin didn't modify the existing CIB underneath us */
crm_trace("Reading cluster configuration file %s", cib_path);
rc = cib_file_read_and_verify(cib_path, NULL, NULL);
if ((rc != pcmk_ok) && (rc != -ENOENT)) {
crm_err("%s was manually modified while the cluster was active!",
cib_path);
exit_rc = pcmk_err_cib_modified;
goto cleanup;
}
/* Back up the existing CIB */
if (cib_file_backup(cib_dirname, cib_filename) < 0) {
exit_rc = pcmk_err_cib_backup;
goto cleanup;
}
crm_debug("Writing CIB to disk");
umask(S_IWGRP | S_IWOTH | S_IROTH);
cib_file_prepare_xml(cib_root);
/* Write the CIB to a temporary file, so we can deploy (near) atomically */
fd = mkstemp(tmp_cib);
if (fd < 0) {
crm_perror(LOG_ERR, "Couldn't open temporary file %s for writing CIB",
tmp_cib);
exit_rc = pcmk_err_cib_save;
goto cleanup;
}
/* Protect the temporary file */
if (fchmod(fd, S_IRUSR | S_IWUSR) < 0) {
crm_perror(LOG_ERR, "Couldn't protect temporary file %s for writing CIB",
tmp_cib);
exit_rc = pcmk_err_cib_save;
goto cleanup;
}
if (cib_do_chown && (fchown(fd, cib_file_owner, cib_file_group) < 0)) {
crm_perror(LOG_ERR, "Couldn't protect temporary file %s for writing CIB",
tmp_cib);
exit_rc = pcmk_err_cib_save;
goto cleanup;
}
/* Write out the CIB */
if (pcmk__xml_write_fd(cib_root, tmp_cib, fd, false, NULL) != pcmk_rc_ok) {
crm_err("Changes couldn't be written to %s", tmp_cib);
exit_rc = pcmk_err_cib_save;
goto cleanup;
}
/* Calculate CIB digest */
digest = calculate_on_disk_digest(cib_root);
pcmk__assert(digest != NULL);
crm_info("Wrote version %s.%s.0 of the CIB to disk (digest: %s)",
(admin_epoch ? admin_epoch : "0"), (epoch ? epoch : "0"), digest);
/* Write the CIB digest to a temporary file */
fd = mkstemp(tmp_digest);
if (fd < 0) {
crm_perror(LOG_ERR, "Could not create temporary file for CIB digest");
exit_rc = pcmk_err_cib_save;
goto cleanup;
}
if (cib_do_chown && (fchown(fd, cib_file_owner, cib_file_group) < 0)) {
crm_perror(LOG_ERR, "Couldn't protect temporary file %s for writing CIB",
tmp_cib);
exit_rc = pcmk_err_cib_save;
close(fd);
goto cleanup;
}
rc = pcmk__write_sync(fd, digest);
if (rc != pcmk_rc_ok) {
crm_err("Could not write digest to %s: %s",
tmp_digest, pcmk_rc_str(rc));
exit_rc = pcmk_err_cib_save;
close(fd);
goto cleanup;
}
close(fd);
crm_debug("Wrote digest %s to disk", digest);
/* Verify that what we wrote is sane */
crm_info("Reading cluster configuration file %s (digest: %s)",
tmp_cib, tmp_digest);
rc = cib_file_read_and_verify(tmp_cib, tmp_digest, NULL);
pcmk__assert(rc == 0);
/* Rename temporary files to live, and sync directory changes to media */
crm_debug("Activating %s", tmp_cib);
if (rename(tmp_cib, cib_path) < 0) {
crm_perror(LOG_ERR, "Couldn't rename %s as %s", tmp_cib, cib_path);
exit_rc = pcmk_err_cib_save;
}
if (rename(tmp_digest, digest_path) < 0) {
crm_perror(LOG_ERR, "Couldn't rename %s as %s", tmp_digest,
digest_path);
exit_rc = pcmk_err_cib_save;
}
pcmk__sync_directory(cib_dirname);
cleanup:
free(cib_path);
free(digest_path);
free(digest);
free(tmp_digest);
free(tmp_cib);
return exit_rc;
}
/*!
* \internal
* \brief Process requests in a CIB transaction
*
* Stop when a request fails or when all requests have been processed.
*
* \param[in,out] cib CIB client
* \param[in,out] transaction CIB transaction
*
* \return Standard Pacemaker return code
*/
static int
cib_file_process_transaction_requests(cib_t *cib, xmlNode *transaction)
{
cib_file_opaque_t *private = cib->variant_opaque;
for (xmlNode *request = pcmk__xe_first_child(transaction,
PCMK__XE_CIB_COMMAND, NULL,
NULL);
request != NULL; request = pcmk__xe_next_same(request)) {
xmlNode *output = NULL;
const char *op = crm_element_value(request, PCMK__XA_CIB_OP);
int rc = cib_file_process_request(cib, request, &output);
rc = pcmk_legacy2rc(rc);
if (rc != pcmk_rc_ok) {
crm_err("Aborting transaction for CIB file client (%s) on file "
"'%s' due to failed %s request: %s",
private->id, private->filename, op, pcmk_rc_str(rc));
crm_log_xml_info(request, "Failed request");
return rc;
}
crm_trace("Applied %s request to transaction working CIB for CIB file "
"client (%s) on file '%s'",
op, private->id, private->filename);
crm_log_xml_trace(request, "Successful request");
}
return pcmk_rc_ok;
}
/*!
* \internal
* \brief Commit a given CIB file client's transaction to a working CIB copy
*
* \param[in,out] cib CIB file client
* \param[in] transaction CIB transaction
* \param[in,out] result_cib Where to store result CIB
*
* \return Standard Pacemaker return code
*
* \note The caller is responsible for replacing the \p cib argument's
* \p private->cib_xml with \p result_cib on success, and for freeing
* \p result_cib using \p free_xml() on failure.
*/
static int
cib_file_commit_transaction(cib_t *cib, xmlNode *transaction,
xmlNode **result_cib)
{
int rc = pcmk_rc_ok;
cib_file_opaque_t *private = cib->variant_opaque;
xmlNode *saved_cib = private->cib_xml;
CRM_CHECK(pcmk__xe_is(transaction, PCMK__XE_CIB_TRANSACTION),
return pcmk_rc_no_transaction);
/* *result_cib should be a copy of private->cib_xml (created by
* cib_perform_op()). If not, make a copy now. Change tracking isn't
* strictly required here because:
* * Each request in the transaction will have changes tracked and ACLs
* checked if appropriate.
* * cib_perform_op() will infer changes for the commit request at the end.
*/
CRM_CHECK((*result_cib != NULL) && (*result_cib != private->cib_xml),
*result_cib = pcmk__xml_copy(NULL, private->cib_xml));
crm_trace("Committing transaction for CIB file client (%s) on file '%s' to "
"working CIB",
private->id, private->filename);
// Apply all changes to a working copy of the CIB
private->cib_xml = *result_cib;
rc = cib_file_process_transaction_requests(cib, transaction);
crm_trace("Transaction commit %s for CIB file client (%s) on file '%s'",
((rc == pcmk_rc_ok)? "succeeded" : "failed"),
private->id, private->filename);
/* Some request types (for example, erase) may have freed private->cib_xml
* (the working copy) and pointed it at a new XML object. In that case, it
* follows that *result_cib (the working copy) was freed.
*
* Point *result_cib at the updated working copy stored in private->cib_xml.
*/
*result_cib = private->cib_xml;
// Point private->cib_xml back to the unchanged original copy
private->cib_xml = saved_cib;
return rc;
}
static int
cib_file_process_commit_transaction(const char *op, int options,
const char *section, xmlNode *req,
xmlNode *input, xmlNode *existing_cib,
xmlNode **result_cib, xmlNode **answer)
{
int rc = pcmk_rc_ok;
const char *client_id = crm_element_value(req, PCMK__XA_CIB_CLIENTID);
cib_t *cib = NULL;
CRM_CHECK(client_id != NULL, return -EINVAL);
cib = get_client(client_id);
CRM_CHECK(cib != NULL, return -EINVAL);
rc = cib_file_commit_transaction(cib, input, result_cib);
if (rc != pcmk_rc_ok) {
cib_file_opaque_t *private = cib->variant_opaque;
crm_err("Could not commit transaction for CIB file client (%s) on "
"file '%s': %s",
private->id, private->filename, pcmk_rc_str(rc));
}
return pcmk_rc2legacy(rc);
}
diff --git a/lib/common/io.c b/lib/common/io.c
index 19d5de6207..6951b1fb40 100644
--- a/lib/common/io.c
+++ b/lib/common/io.c
@@ -1,662 +1,662 @@
/*
* 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>
#ifndef _GNU_SOURCE
# define _GNU_SOURCE
#endif
#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, int sequence,
- bool bzip)
+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-%d.%s", directory, series, sequence,
+ 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 " CRM_XS " uid %d != %d",
target_name, CRM_DAEMON_USER, sys_user->pw_uid,
target_stat->st_uid);
return FALSE;
}
if ((target_stat->st_mode & (S_IRUSR | S_IWUSR)) == 0) {
crm_notice("%s is not readable and writable by user %s "
CRM_XS " st_mode=0%lo",
target_name, CRM_DAEMON_USER,
(unsigned long) target_stat->st_mode);
return FALSE;
}
return TRUE;
}
static bool
pcmk__daemon_group_can_write(const char *target_name, struct stat *target_stat)
{
struct group *sys_grp = NULL;
errno = 0;
sys_grp = getgrnam(CRM_DAEMON_GROUP);
if (sys_grp == NULL) {
crm_notice("Could not find group %s: %s",
CRM_DAEMON_GROUP, pcmk_rc_str(errno));
return FALSE;
}
if (target_stat->st_gid != sys_grp->gr_gid) {
crm_notice("%s is not owned by group %s " CRM_XS " uid %d != %d",
target_name, CRM_DAEMON_GROUP,
sys_grp->gr_gid, target_stat->st_gid);
return FALSE;
}
if ((target_stat->st_mode & (S_IRGRP | S_IWGRP)) == 0) {
crm_notice("%s is not readable and writable by group %s "
CRM_XS " st_mode=0%lo",
target_name, CRM_DAEMON_GROUP,
(unsigned long) target_stat->st_mode);
return FALSE;
}
return TRUE;
}
/*!
* \internal
* \brief 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 " CRM_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 " CRM_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 "
CRM_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.
*/
#if HAVE_LINUX_PROCFS
dir = opendir("/proc/self/fd");
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);
}
// Deprecated functions kept only for backward API compatibility
// LCOV_EXCL_START
#include <crm/common/util_compat.h>
void
crm_build_path(const char *path_c, mode_t mode)
{
int rc = pcmk__build_path(path_c, mode);
if (rc != pcmk_rc_ok) {
crm_err("Could not create directory '%s': %s",
path_c, pcmk_rc_str(rc));
}
}
// LCOV_EXCL_STOP
// End deprecated API

File Metadata

Mime Type
text/x-diff
Expires
Tue, Jul 8, 6:02 PM (1 d, 1 h)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
2002442
Default Alt Text
(69 KB)

Event Timeline