Page MenuHomeClusterLabs Projects

No OneTemporary

diff --git a/include/crm/common/util.h b/include/crm/common/util.h
index 9c96f144ba..16fcaa9c4f 100644
--- a/include/crm/common/util.h
+++ b/include/crm/common/util.h
@@ -1,402 +1,404 @@
/*
* Copyright (C) 2004 Andrew Beekhof <andrew@beekhof.net>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
#ifndef CRM_COMMON_UTIL__H
# define CRM_COMMON_UTIL__H
# include <clplumbing/lsb_exitcodes.h>
# include <sys/types.h>
# include <stdlib.h>
# include <limits.h>
# include <signal.h>
# include <crm/lrmd.h>
# if SUPPORT_HEARTBEAT
# include <heartbeat.h>
# else
# define NORMALNODE "normal"
# define ACTIVESTATUS "active"/* fully functional, and all links are up */
# define DEADSTATUS "dead"
/* Status of non-working link or machine */
# define PINGSTATUS "ping"
/* Status of a working ping node */
# define JOINSTATUS "join"
/* Status when an api client joins */
# define LEAVESTATUS "leave"
/* Status when an api client leaves */
# define ONLINESTATUS "online"/* Status of an online client */
# define OFFLINESTATUS "offline"
/* Status of an offline client */
# endif
extern unsigned int crm_log_level;
extern gboolean crm_config_error;
extern gboolean crm_config_warning;
# ifdef HAVE_GETOPT_H
# include <getopt.h>
# else
# define no_argument 0
# define required_argument 1
# endif
# define pcmk_option_default 0x00000
# define pcmk_option_hidden 0x00001
# define pcmk_option_paragraph 0x00002
# define pcmk_option_example 0x00004
struct crm_option {
/* Fields from 'struct option' in getopt.h */
/* name of long option */
const char *name;
/*
* one of no_argument, required_argument, and optional_argument:
* whether option takes an argument
*/
int has_arg;
/* if not NULL, set *flag to val when option found */
int *flag;
/* if flag not NULL, value to set *flag to; else return value */
int val;
/* Custom fields */
const char *desc;
long flags;
};
# define crm_config_err(fmt...) { crm_config_error = TRUE; crm_err(fmt); }
# define crm_config_warn(fmt...) { crm_config_warning = TRUE; crm_warn(fmt); }
void crm_log_deinit(void);
gboolean daemon_option_enabled(const char *daemon, const char *option);
gboolean crm_log_cli_init(const char *entity);
gboolean crm_log_init(const char *entity, int level, gboolean daemon,
gboolean to_stderr, int argc, char **argv, gboolean quiet);
void crm_log_args(int argc, char **argv);
int crm_should_log(int level);
void crm_bump_log_level(void);
void crm_enable_stderr(int enable);
/* returns the old value */
unsigned int set_crm_log_level(unsigned int level);
unsigned int get_crm_log_level(void);
char *crm_itoa(int an_int);
char *crm_strdup_fn(const char *a, const char *file, const char *fn, int line);
char *generate_hash_key(const char *crm_msg_reference, const char *sys);
char *generate_hash_value(const char *src_node, const char *src_subsys);
gboolean decodeNVpair(const char *srcstring, char separator, char **name, char **value);
int compare_version(const char *version1, const char *version2);
char *generateReference(const char *custom1, const char *custom2);
void g_hash_destroy_str(gpointer data);
gboolean crm_is_true(const char *s);
int crm_str_to_boolean(const char *s, int *ret);
long long crm_get_msec(const char *input);
unsigned long long crm_get_interval(const char *input);
char *generate_op_key(const char *rsc_id, const char *op_type, int interval);
gboolean parse_op_key(const char *key, char **rsc_id, char **op_type, int *interval);
char *generate_notify_key(const char *rsc_id, const char *notify_type, const char *op_type);
char *generate_transition_magic_v202(const char *transition_key, int op_status);
char *generate_transition_magic(const char *transition_key, int op_status, int op_rc);
gboolean decode_transition_magic(const char *magic, char **uuid,
int *transition_id, int *action_id, int *op_status,
int *op_rc, int *target_rc);
char *generate_transition_key(int action, int transition_id, int target_rc,
const char *node);
gboolean decode_transition_key(const char *key, char **uuid, int *action, int *transition_id,
int *target_rc);
char *crm_concat(const char *prefix, const char *suffix, char join);
gboolean decode_op_key(const char *key, char **rsc_id, char **op_type, int *interval);
void filter_action_parameters(xmlNode * param_set, const char *version);
void filter_reload_parameters(xmlNode * param_set, const char *restart_string);
static inline int
crm_strlen_zero(const char *s)
{
return !s || *s == '\0';
}
# define safe_str_eq(a, b) crm_str_eq(a, b, FALSE)
gboolean crm_str_eq(const char *a, const char *b, gboolean use_case);
gboolean safe_str_neq(const char *a, const char *b);
int crm_parse_int(const char *text, const char *default_text);
long long crm_int_helper(const char *text, char **end_text);
# define crm_atoi(text, default_text) crm_parse_int(text, default_text)
void crm_abort(const char *file, const char *function, int line,
const char *condition, gboolean do_core, gboolean do_fork);
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_pid_active(long pid);
int crm_read_pidfile(const char *filename);
int crm_lock_pidfile(const char *filename);
void crm_make_daemon(const char *name, gboolean daemonize, const char *pidfile);
typedef struct pe_cluster_option_s {
const char *name;
const char *alt_name;
const char *type;
const char *values;
const char *default_value;
gboolean(*is_valid) (const char *);
const char *description_short;
const char *description_long;
} pe_cluster_option;
const char *cluster_option(GHashTable * options, gboolean(*validate) (const char *),
const char *name, const char *old_name, const char *def_value);
const char *get_cluster_pref(GHashTable * options, pe_cluster_option * option_list, int len,
const char *name);
void config_metadata(const char *name, const char *version, const char *desc_short,
const char *desc_long, pe_cluster_option * option_list, int len);
void verify_all_options(GHashTable * options, pe_cluster_option * option_list, int len);
gboolean check_time(const char *value);
gboolean check_timer(const char *value);
gboolean check_boolean(const char *value);
gboolean check_number(const char *value);
int char2score(const char *score);
char *score2char(int score);
gboolean crm_is_writable(const char *dir, const char *file,
const char *user, const char *group, gboolean need_both);
# define set_bit(word, bit) word = crm_set_bit(__PRETTY_FUNCTION__, NULL, word, bit)
# define clear_bit(word, bit) word = crm_clear_bit(__PRETTY_FUNCTION__, NULL, word, bit)
# define set_bit_inplace set_bit
# define clear_bit_inplace clear_bit
static inline long long
crm_clear_bit(const char *function, const char *target, long long word, long long bit)
{
long long rc = (word & ~bit);
if(rc == word) {
/* Unchanged */
} else if (target) {
crm_trace("Bit 0x%.8llx for %s cleared by %s", bit, target, function);
} else {
crm_trace("Bit 0x%.8llx cleared by %s", bit, function);
}
return rc;
}
static inline long long
crm_set_bit(const char *function, const char *target, long long word, long long bit)
{
long long rc = (word|bit);
if(rc == word) {
/* Unchanged */
} else if (target) {
crm_trace("Bit 0x%.8llx for %s set by %s", bit, target, function);
} else {
crm_trace("Bit 0x%.8llx set by %s", bit, function);
}
return rc;
}
static inline gboolean
is_not_set(long long word, long long bit)
{
return ((word & bit) == 0);
}
static inline gboolean
is_set(long long word, long long bit)
{
return ((word & bit) == bit);
}
static inline gboolean
is_set_any(long long word, long long bit)
{
return ((word & bit) != 0);
}
xmlNode *cib_recv_remote_msg(void *session, gboolean encrypted);
void cib_send_remote_msg(void *session, xmlNode * msg, gboolean encrypted);
char *crm_meta_name(const char *field);
const char *crm_meta_value(GHashTable * hash, const char *field);
void crm_set_options(const char *short_options, const char *usage,
struct crm_option *long_options, const char *app_desc);
int crm_get_option(int argc, char **argv, int *index);
void crm_help(char cmd, int exit_code);
int rsc_op_expected_rc(lrmd_event_data_t *event);
gboolean did_rsc_op_fail(lrmd_event_data_t *event, int target_rc);
extern int node_score_red;
extern int node_score_green;
extern int node_score_yellow;
extern int node_score_infinity;
xmlNode *create_operation_update(xmlNode * parent, lrmd_event_data_t *event, const char *caller_version,
int target_rc, const char *origin, int level);
# if USE_GHASH_COMPAT
typedef struct fake_ghi {
GHashTable *hash;
int nth; /* current index over the iteration */
int lpc; /* internal loop counter inside g_hash_table_find */
gpointer key;
gpointer value;
} GHashTableIter;
static inline void
g_hash_prepend_value(gpointer key, gpointer value, gpointer user_data)
{
GList **values = (GList **) user_data;
*values = g_list_prepend(*values, value);
}
static inline GList *
g_hash_table_get_values(GHashTable * hash_table)
{
GList *values = NULL;
g_hash_table_foreach(hash_table, g_hash_prepend_value, &values);
return values;
}
static inline gboolean
g_hash_table_nth_data(gpointer key, gpointer value, gpointer user_data)
{
GHashTableIter *iter = (GHashTableIter *) user_data;
if (iter->lpc++ == iter->nth) {
iter->key = key;
iter->value = value;
return TRUE;
}
return FALSE;
}
static inline void
g_hash_table_iter_init(GHashTableIter * iter, GHashTable * hash_table)
{
iter->hash = hash_table;
iter->nth = 0;
iter->lpc = 0;
iter->key = NULL;
iter->value = NULL;
}
static inline gboolean
g_hash_table_iter_next(GHashTableIter * iter, gpointer * key, gpointer * value)
{
gboolean found = FALSE;
iter->lpc = 0;
iter->key = NULL;
iter->value = NULL;
if (iter->nth < g_hash_table_size(iter->hash)) {
found = ! !g_hash_table_find(iter->hash, g_hash_table_nth_data, iter);
iter->nth++;
}
if (key)
*key = iter->key;
if (value)
*value = iter->value;
return found;
}
# endif /* USE_GHASH_COMPAT */
# if ENABLE_ACL
static inline gboolean
is_privileged(const char *user)
{
if (user == NULL) {
return FALSE;
} else if (strcmp(user, CRM_DAEMON_USER) == 0) {
return TRUE;
} else if (strcmp(user, "root") == 0) {
return TRUE;
}
return FALSE;
}
void determine_request_user(char **user, IPC_Channel * channel, xmlNode * request,
const char *field);
# endif
void *find_library_function(void **handle, const char *lib, const char *fn);
void *convert_const_pointer(const void *ptr);
char *crm_generate_uuid(void);
char *crm_md5sum(const char *buffer);
void crm_enable_blackbox(int nsig);
void crm_enable_blackbox_tracing(int nsig);
void crm_write_blackbox(int nsig);
+void crm_update_callsites(void);
+
#endif
diff --git a/include/crm/crm.h b/include/crm/crm.h
index dc77bac99e..8261113ea7 100644
--- a/include/crm/crm.h
+++ b/include/crm/crm.h
@@ -1,387 +1,387 @@
/*
* Copyright (C) 2004 Andrew Beekhof <andrew@beekhof.net>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
#ifndef CRM__H
# define CRM__H
# include <crm_config.h>
# include <stdlib.h>
# include <glib.h>
# include <stdbool.h>
# include <assert.h>
# undef MIN
# undef MAX
# include <string.h>
# include <qb/qblog.h>
# include <libxml/tree.h>
int log_data_element(int log_level, const char *file, const char *function, int line,
const char *prefix, xmlNode * data, int depth, gboolean formatted);
# define CRM_FEATURE_SET "3.0.6"
# define MINIMUM_SCHEMA_VERSION "pacemaker-1.0"
# define LATEST_SCHEMA_VERSION "pacemaker-"CRM_DTD_VERSION
# define EOS '\0'
# define DIMOF(a) ((int) (sizeof(a)/sizeof(a[0])) )
# ifndef __GNUC__
# define __builtin_expect(expr, result) (expr)
# endif
/* Some handy macros used by the Linux kernel */
# define __likely(expr) __builtin_expect(expr, 1)
# define __unlikely(expr) __builtin_expect(expr, 0)
# define CRM_DEPRECATED_SINCE_2_0_1 0
# define CRM_DEPRECATED_SINCE_2_0_2 0
# define CRM_DEPRECATED_SINCE_2_0_3 0
# define CRM_DEPRECATED_SINCE_2_0_4 0
# define CRM_DEPRECATED_SINCE_2_0_5 0
# define CRM_DEPRECATED_SINCE_2_0_6 1
# define CRM_DEPRECATED_SINCE_2_0_7 1
# define CRM_DEPRECATED_SINCE_2_0_8 1
# define CRM_DEPRECATED_SINCE_2_1_0 1
# define CRM_META "CRM_meta"
# define CRM_ASSERT(expr) do { \
if(__unlikely((expr) == FALSE)) { \
crm_abort(__FILE__, __PRETTY_FUNCTION__, __LINE__, #expr, TRUE, FALSE); \
} \
} while(0)
extern const char *crm_system_name;
/* *INDENT-OFF* */
/* Clean these up at some point, some probably should be runtime options */
# define SOCKET_LEN 1024
# define APPNAME_LEN 256
# define MAX_IPC_FAIL 5
# define MAX_IPC_DELAY 120
# define MSG_LOG 1
# define DOT_FSA_ACTIONS 1
# define DOT_ALL_FSA_INPUTS 1
/* #define FSA_TRACE 1 */
# define INFINITY_S "INFINITY"
# define MINUS_INFINITY_S "-INFINITY"
# define INFINITY 1000000
/* Sub-systems */
# define CRM_SYSTEM_DC "dc"
# define CRM_SYSTEM_DCIB "dcib"
/* The master CIB */
# define CRM_SYSTEM_CIB "cib"
# define CRM_SYSTEM_CRMD "crmd"
# define CRM_SYSTEM_LRMD "lrmd"
# define CRM_SYSTEM_PENGINE "pengine"
# define CRM_SYSTEM_TENGINE "tengine"
# define CRM_SYSTEM_STONITHD "stonithd"
# define CRM_SYSTEM_MCP "pacemakerd"
/* Valid operations */
# define CRM_OP_NOOP "noop"
# define CRM_OP_JOIN_ANNOUNCE "join_announce"
# define CRM_OP_JOIN_OFFER "join_offer"
# define CRM_OP_JOIN_REQUEST "join_request"
# define CRM_OP_JOIN_ACKNAK "join_ack_nack"
# define CRM_OP_JOIN_CONFIRM "join_confirm"
# define CRM_OP_DIE "die_no_respawn"
# define CRM_OP_RETRIVE_CIB "retrieve_cib"
# define CRM_OP_PING "ping"
# define CRM_OP_VOTE "vote"
# define CRM_OP_NOVOTE "no-vote"
# define CRM_OP_HELLO "hello"
# define CRM_OP_HBEAT "dc_beat"
# define CRM_OP_PECALC "pe_calc"
# define CRM_OP_ABORT "abort"
# define CRM_OP_QUIT "quit"
# define CRM_OP_LOCAL_SHUTDOWN "start_shutdown"
# define CRM_OP_SHUTDOWN_REQ "req_shutdown"
# define CRM_OP_SHUTDOWN "do_shutdown"
# define CRM_OP_FENCE "stonith"
# define CRM_OP_EVENTCC "event_cc"
# define CRM_OP_TEABORT "te_abort"
# define CRM_OP_TEABORTED "te_abort_confirmed" /* we asked */
# define CRM_OP_TE_HALT "te_halt"
# define CRM_OP_TECOMPLETE "te_complete"
# define CRM_OP_TETIMEOUT "te_timeout"
# define CRM_OP_TRANSITION "transition"
# define CRM_OP_REGISTER "register"
# define CRM_OP_DEBUG_UP "debug_inc"
# define CRM_OP_DEBUG_DOWN "debug_dec"
# define CRM_OP_INVOKE_LRM "lrm_invoke"
# define CRM_OP_LRM_REFRESH "lrm_refresh"
# define CRM_OP_LRM_QUERY "lrm_query"
# define CRM_OP_LRM_DELETE "lrm_delete"
# define CRM_OP_LRM_FAIL "lrm_fail"
# define CRM_OP_PROBED "probe_complete"
# define CRM_OP_REPROBE "probe_again"
# define CRM_OP_CLEAR_FAILCOUNT "clear_failcount"
# define CRM_OP_RELAXED_SET "one-or-more"
# define CRMD_STATE_ACTIVE "member"
# define CRMD_STATE_INACTIVE "down"
# define CRMD_JOINSTATE_DOWN CRMD_STATE_INACTIVE
# define CRMD_JOINSTATE_PENDING "pending"
# define CRMD_JOINSTATE_MEMBER CRMD_STATE_ACTIVE
# define CRMD_JOINSTATE_NACK "banned"
# define CRMD_ACTION_DELETE "delete"
# define CRMD_ACTION_CANCEL "cancel"
# define CRMD_ACTION_MIGRATE "migrate_to"
# define CRMD_ACTION_MIGRATED "migrate_from"
# define CRMD_ACTION_START "start"
# define CRMD_ACTION_STARTED "running"
# define CRMD_ACTION_STOP "stop"
# define CRMD_ACTION_STOPPED "stopped"
# define CRMD_ACTION_PROMOTE "promote"
# define CRMD_ACTION_PROMOTED "promoted"
# define CRMD_ACTION_DEMOTE "demote"
# define CRMD_ACTION_DEMOTED "demoted"
# define CRMD_ACTION_NOTIFY "notify"
# define CRMD_ACTION_NOTIFIED "notified"
# define CRMD_ACTION_STATUS "monitor"
/* short names */
# define RSC_DELETE CRMD_ACTION_DELETE
# define RSC_CANCEL CRMD_ACTION_CANCEL
# define RSC_MIGRATE CRMD_ACTION_MIGRATE
# define RSC_MIGRATED CRMD_ACTION_MIGRATED
# define RSC_START CRMD_ACTION_START
# define RSC_STARTED CRMD_ACTION_STARTED
# define RSC_STOP CRMD_ACTION_STOP
# define RSC_STOPPED CRMD_ACTION_STOPPED
# define RSC_PROMOTE CRMD_ACTION_PROMOTE
# define RSC_PROMOTED CRMD_ACTION_PROMOTED
# define RSC_DEMOTE CRMD_ACTION_DEMOTE
# define RSC_DEMOTED CRMD_ACTION_DEMOTED
# define RSC_NOTIFY CRMD_ACTION_NOTIFY
# define RSC_NOTIFIED CRMD_ACTION_NOTIFIED
# define RSC_STATUS CRMD_ACTION_STATUS
/* *INDENT-ON* */
typedef GList *GListPtr;
# ifndef LOG_TRACE
# define LOG_TRACE LOG_DEBUG+1
# endif
# define LOG_DEBUG_2 LOG_TRACE
# define LOG_DEBUG_3 LOG_TRACE
# define LOG_DEBUG_4 LOG_TRACE
# define LOG_DEBUG_5 LOG_TRACE
# define LOG_DEBUG_6 LOG_TRACE
# define LOG_MSG LOG_TRACE
/*
* Throughout the macros below, note the leading, pre-comma, space in the
* various ' , ##args' occurences to aid portability across versions of 'gcc'.
* http://gcc.gnu.org/onlinedocs/cpp/Variadic-Macros.html#Variadic-Macros
*/
# define CRM_TRACE_INIT_DATA(name) QB_LOG_INIT_DATA(name)
# define do_crm_log(level, fmt, args...) do { \
qb_log_from_external_source( __func__, __FILE__, fmt, level, __LINE__, 0, ##args); \
if((level) < LOG_WARNING) { \
crm_write_blackbox(0); \
} \
} while(0)
/* level /MUST/ be a constant or compilation will fail */
# define do_crm_log_unlikely(level, fmt, args...) do { \
static struct qb_log_callsite *trace_cs = NULL; \
if(trace_cs == NULL) { \
trace_cs = qb_log_callsite_get(__func__, __FILE__, fmt, level, __LINE__, 0); \
} \
if (trace_cs && trace_cs->targets) { \
qb_log_from_external_source( \
__func__, __FILE__, fmt, level, __LINE__, 0, ##args); \
} \
} while(0)
# define CRM_LOG_ASSERT(expr) do { \
if(__unlikely((expr) == FALSE)) { \
static struct qb_log_callsite *core_cs = NULL; \
if(core_cs == NULL) { \
core_cs = qb_log_callsite_get(__func__, __FILE__, "log-assert", LOG_TRACE, __LINE__, 0); \
} \
crm_abort(__FILE__, __PRETTY_FUNCTION__, __LINE__, #expr, \
core_cs?core_cs->targets:FALSE, TRUE); \
} \
} while(0)
# define CRM_CHECK(expr, failure_action) do { \
if(__unlikely((expr) == FALSE)) { \
static struct qb_log_callsite *core_cs = NULL; \
if(core_cs == NULL) { \
core_cs = qb_log_callsite_get(__func__, __FILE__, "check-assert", LOG_TRACE, __LINE__, 0); \
} \
crm_abort(__FILE__, __PRETTY_FUNCTION__, __LINE__, #expr, \
core_cs?core_cs->targets:FALSE, TRUE); \
failure_action; \
} \
} while(0)
# define do_crm_log_xml(level, text, xml) do { \
static struct qb_log_callsite *xml_cs = NULL; \
if(xml_cs == NULL) { \
xml_cs = qb_log_callsite_get(__func__, __FILE__, "xml-blog", level, __LINE__, 0); \
} \
if (xml_cs && xml_cs->targets) { \
log_data_element(level, __FILE__, __PRETTY_FUNCTION__, __LINE__, text, xml, 0, TRUE); \
} \
if((level) < LOG_WARNING) { \
crm_write_blackbox(0); \
} \
} while(0)
# define do_crm_log_alias(level, file, function, line, fmt, args...) do { \
qb_log_from_external_source(function, file, fmt, level, line, 0, ##args); \
} while(0)
# define do_crm_log_always(level, fmt, args...) qb_log(level, "%s: " fmt, __PRETTY_FUNCTION__ , ##args)
# define crm_perror(level, fmt, args...) do { \
const char *err = strerror(errno); \
fprintf(stderr, fmt ": %s (%d)\n", ##args, err, errno); \
do_crm_log(level, fmt ": %s (%d)", ##args, err, errno); \
if((level) < LOG_WARNING) { \
crm_write_blackbox(0); \
} \
} while(0)
# define crm_log_tag(level, tag, fmt, args...) do { \
- qb_log_from_external_source( __func__, __FILE__, fmt, level, __LINE__, g_quark_from_string(tag), ##args); \
+ qb_log_from_external_source( __func__, __FILE__, fmt, level, __LINE__, g_quark_try_string(tag), ##args); \
} while(0)
# define crm_crit(fmt, args...) do { \
qb_logt(LOG_CRIT, 0, fmt , ##args); \
crm_write_blackbox(0); \
} while(0)
# define crm_err(fmt, args...) do { \
qb_logt(LOG_ERR, 0, fmt , ##args); \
crm_write_blackbox(0); \
} while(0)
# define crm_warn(fmt, args...) qb_logt(LOG_WARNING, 0, fmt , ##args)
# define crm_notice(fmt, args...) qb_logt(LOG_NOTICE, 0, fmt , ##args)
# define crm_info(fmt, args...) qb_logt(LOG_INFO, 0, fmt , ##args)
# define crm_debug(fmt, args...) do_crm_log_unlikely(LOG_DEBUG, fmt , ##args)
# define crm_trace(fmt, args...) do_crm_log_unlikely(LOG_TRACE, fmt , ##args)
# include <crm/common/util.h>
# define crm_log_xml_crit(xml, text) do_crm_log_xml(LOG_CRIT, text, xml)
# define crm_log_xml_err(xml, text) do_crm_log_xml(LOG_ERR, text, xml)
# define crm_log_xml_warn(xml, text) do_crm_log_xml(LOG_WARNING, text, xml)
# define crm_log_xml_notice(xml, text) do_crm_log_xml(LOG_NOTICE, text, xml)
# define crm_log_xml_info(xml, text) do_crm_log_xml(LOG_INFO, text, xml)
# define crm_log_xml_debug(xml, text) do_crm_log_xml(LOG_DEBUG, text, xml)
# define crm_log_xml_trace(xml, text) do_crm_log_xml(LOG_TRACE, text, xml)
# define crm_str(x) (const char*)(x?x:"<null>")
# define crm_malloc0(malloc_obj, length) do { \
malloc_obj = malloc(length); \
if(malloc_obj == NULL) { \
crm_err("Failed allocation of %lu bytes", (unsigned long)length); \
CRM_ASSERT(malloc_obj != NULL); \
} \
memset(malloc_obj, 0, length); \
} while(0)
# define crm_malloc(malloc_obj, length) do { \
malloc_obj = malloc(length); \
if(malloc_obj == NULL) { \
crm_err("Failed allocation of %lu bytes", (unsigned long)length); \
CRM_ASSERT(malloc_obj != NULL); \
} \
} while(0)
# define crm_realloc(realloc_obj, length) do { \
realloc_obj = realloc(realloc_obj, length); \
CRM_ASSERT(realloc_obj != NULL); \
} while(0)
# define crm_free(free_obj) do { free(free_obj); free_obj=NULL; } while(0)
# define crm_strdup(str) crm_strdup_fn(str, __FILE__, __PRETTY_FUNCTION__, __LINE__)
# define crm_str_hash g_str_hash_traditional
guint g_str_hash_traditional(gconstpointer v);
void update_all_trace_data(void);
static inline void
slist_basic_destroy(GListPtr list)
{
GListPtr gIter = NULL;
for (gIter = list; gIter != NULL; gIter = gIter->next) {
free(gIter->data);
}
g_list_free(list);
}
/* These two macros are no longer to be used
* They exist for compatability reasons and will be removed in a
* future release
* Use something like this instead:
GListPtr gIter = rsc->children;
for(; gIter != NULL; gIter = gIter->next) {
resource_t *child_rsc = (resource_t*)gIter->data;
...
}
*
*/
# define slist_destroy(child_type, child, parent, a) do { \
GListPtr __crm_iter_head = parent; \
child_type *child = NULL; \
while(__crm_iter_head != NULL) { \
child = (child_type *) __crm_iter_head->data; \
__crm_iter_head = __crm_iter_head->next; \
{ a; } \
} \
g_list_free(parent); \
} while(0)
#endif
diff --git a/lib/common/utils.c b/lib/common/utils.c
index b4c85aa9d2..ea52adbbfb 100644
--- a/lib/common/utils.c
+++ b/lib/common/utils.c
@@ -1,2821 +1,2836 @@
/*
* Copyright (C) 2004 Andrew Beekhof <andrew@beekhof.net>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
#include <crm_internal.h>
#include <dlfcn.h>
#ifndef _GNU_SOURCE
# define _GNU_SOURCE
#endif
#include <sys/param.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/stat.h>
#include <sys/utsname.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <limits.h>
#include <ctype.h>
#include <pwd.h>
#include <grp.h>
#include <time.h>
#include <libgen.h>
#include <signal.h>
#include <qb/qbdefs.h>
#include <crm/crm.h>
#include <crm/lrmd.h>
#include <crm/services.h>
#include <crm/msg_xml.h>
#include <crm/common/xml.h>
#include <crm/common/util.h>
#include <crm/common/ipc.h>
#include <crm/common/iso8601.h>
#include <crm/common/mainloop.h>
#include <crm/attrd.h>
#include <libxml2/libxml/relaxng.h>
#if HAVE_HB_CONFIG_H
# include <heartbeat/hb_config.h> /* for HA_COREDIR */
#endif
#if HAVE_GLUE_CONFIG_H
# include <glue_config.h> /* for HA_COREDIR */
#endif
#ifndef MAXLINE
# define MAXLINE 512
#endif
#ifdef HAVE_GETOPT_H
# include <getopt.h>
#endif
CRM_TRACE_INIT_DATA(common);
static uint ref_counter = 0;
gboolean crm_config_error = FALSE;
gboolean crm_config_warning = FALSE;
const char *crm_system_name = "unknown";
int node_score_red = 0;
int node_score_green = 0;
int node_score_yellow = 0;
int node_score_infinity = INFINITY;
unsigned int crm_log_level = LOG_INFO;
-void set_format_string(int method, const char *daemon, gboolean trace);
+static gboolean crm_tracing_enabled(void);
gboolean
check_time(const char *value)
{
if (crm_get_msec(value) < 5000) {
return FALSE;
}
return TRUE;
}
gboolean
check_timer(const char *value)
{
if (crm_get_msec(value) < 0) {
return FALSE;
}
return TRUE;
}
gboolean
check_boolean(const char *value)
{
int tmp = FALSE;
if (crm_str_to_boolean(value, &tmp) != 1) {
return FALSE;
}
return TRUE;
}
gboolean
check_number(const char *value)
{
errno = 0;
if (value == NULL) {
return FALSE;
} else if (safe_str_eq(value, MINUS_INFINITY_S)) {
} else if (safe_str_eq(value, INFINITY_S)) {
} else {
crm_int_helper(value, NULL);
}
if (errno != 0) {
return FALSE;
}
return TRUE;
}
int
char2score(const char *score)
{
int score_f = 0;
if (score == NULL) {
} else if (safe_str_eq(score, MINUS_INFINITY_S)) {
score_f = -node_score_infinity;
} else if (safe_str_eq(score, INFINITY_S)) {
score_f = node_score_infinity;
} else if (safe_str_eq(score, "+" INFINITY_S)) {
score_f = node_score_infinity;
} else if (safe_str_eq(score, "red")) {
score_f = node_score_red;
} else if (safe_str_eq(score, "yellow")) {
score_f = node_score_yellow;
} else if (safe_str_eq(score, "green")) {
score_f = node_score_green;
} else {
score_f = crm_parse_int(score, NULL);
if (score_f > 0 && score_f > node_score_infinity) {
score_f = node_score_infinity;
} else if (score_f < 0 && score_f < -node_score_infinity) {
score_f = -node_score_infinity;
}
}
return score_f;
}
char *
score2char(int score)
{
if (score >= node_score_infinity) {
return crm_strdup(INFINITY_S);
} else if (score <= -node_score_infinity) {
return crm_strdup("-" INFINITY_S);
}
return crm_itoa(score);
}
const char *
cluster_option(GHashTable * options, gboolean(*validate) (const char *),
const char *name, const char *old_name, const char *def_value)
{
const char *value = NULL;
CRM_ASSERT(name != NULL);
if (options != NULL) {
value = g_hash_table_lookup(options, name);
}
if (value == NULL && old_name && options != NULL) {
value = g_hash_table_lookup(options, old_name);
if (value != NULL) {
crm_config_warn("Using deprecated name '%s' for"
" cluster option '%s'", old_name, name);
g_hash_table_insert(options, crm_strdup(name), crm_strdup(value));
value = g_hash_table_lookup(options, old_name);
}
}
if (value == NULL) {
crm_trace("Using default value '%s' for cluster option '%s'", def_value, name);
if (options == NULL) {
return def_value;
}
g_hash_table_insert(options, crm_strdup(name), crm_strdup(def_value));
value = g_hash_table_lookup(options, name);
}
if (validate && validate(value) == FALSE) {
crm_config_err("Value '%s' for cluster option '%s' is invalid."
" Defaulting to %s", value, name, def_value);
g_hash_table_replace(options, crm_strdup(name), crm_strdup(def_value));
value = g_hash_table_lookup(options, name);
}
return value;
}
const char *
get_cluster_pref(GHashTable * options, pe_cluster_option * option_list, int len, const char *name)
{
int lpc = 0;
const char *value = NULL;
gboolean found = FALSE;
for (lpc = 0; lpc < len; lpc++) {
if (safe_str_eq(name, option_list[lpc].name)) {
found = TRUE;
value = cluster_option(options,
option_list[lpc].is_valid,
option_list[lpc].name,
option_list[lpc].alt_name, option_list[lpc].default_value);
}
}
CRM_CHECK(found, crm_err("No option named: %s", name));
CRM_ASSERT(value != NULL);
return value;
}
void
config_metadata(const char *name, const char *version, const char *desc_short,
const char *desc_long, pe_cluster_option * option_list, int len)
{
int lpc = 0;
fprintf(stdout, "<?xml version=\"1.0\"?>"
"<!DOCTYPE resource-agent SYSTEM \"ra-api-1.dtd\">\n"
"<resource-agent name=\"%s\">\n"
" <version>%s</version>\n"
" <longdesc lang=\"en\">%s</longdesc>\n"
" <shortdesc lang=\"en\">%s</shortdesc>\n"
" <parameters>\n", name, version, desc_long, desc_short);
for (lpc = 0; lpc < len; lpc++) {
if (option_list[lpc].description_long == NULL && option_list[lpc].description_short == NULL) {
continue;
}
fprintf(stdout, " <parameter name=\"%s\" unique=\"0\">\n"
" <shortdesc lang=\"en\">%s</shortdesc>\n"
" <content type=\"%s\" default=\"%s\"/>\n"
" <longdesc lang=\"en\">%s%s%s</longdesc>\n"
" </parameter>\n",
option_list[lpc].name,
option_list[lpc].description_short,
option_list[lpc].type,
option_list[lpc].default_value,
option_list[lpc].
description_long ? option_list[lpc].description_long : option_list[lpc].
description_short, option_list[lpc].values ? " Allowed values: " : "",
option_list[lpc].values ? option_list[lpc].values : "");
}
fprintf(stdout, " </parameters>\n</resource-agent>\n");
}
void
verify_all_options(GHashTable * options, pe_cluster_option * option_list, int len)
{
int lpc = 0;
for (lpc = 0; lpc < len; lpc++) {
cluster_option(options,
option_list[lpc].is_valid,
option_list[lpc].name,
option_list[lpc].alt_name, option_list[lpc].default_value);
}
}
char *
generateReference(const char *custom1, const char *custom2)
{
const char *local_cust1 = custom1;
const char *local_cust2 = custom2;
int reference_len = 4;
char *since_epoch = NULL;
reference_len += 20; /* too big */
reference_len += 40; /* too big */
if (local_cust1 == NULL) {
local_cust1 = "_empty_";
}
reference_len += strlen(local_cust1);
if (local_cust2 == NULL) {
local_cust2 = "_empty_";
}
reference_len += strlen(local_cust2);
since_epoch = calloc(1, reference_len);
if (since_epoch != NULL) {
sprintf(since_epoch, "%s-%s-%ld-%u",
local_cust1, local_cust2, (unsigned long)time(NULL), ref_counter++);
}
return since_epoch;
}
gboolean
decodeNVpair(const char *srcstring, char separator, char **name, char **value)
{
int lpc = 0;
int len = 0;
const char *temp = NULL;
CRM_ASSERT(name != NULL && value != NULL);
*name = NULL;
*value = NULL;
crm_trace("Attempting to decode: [%s]", srcstring);
if (srcstring != NULL) {
len = strlen(srcstring);
while (lpc <= len) {
if (srcstring[lpc] == separator) {
*name = calloc(1, lpc + 1);
if (*name == NULL) {
break; /* and return FALSE */
}
memcpy(*name, srcstring, lpc);
(*name)[lpc] = '\0';
/* this sucks but as the strtok manpage says..
* it *is* a bug
*/
len = len - lpc;
len--;
if (len <= 0) {
*value = NULL;
} else {
*value = calloc(1, len + 1);
if (*value == NULL) {
free(*name);
break; /* and return FALSE */
}
temp = srcstring + lpc + 1;
memcpy(*value, temp, len);
(*value)[len] = '\0';
}
return TRUE;
}
lpc++;
}
}
if (*name != NULL) {
free(*name);
}
*name = NULL;
*value = NULL;
return FALSE;
}
char *
crm_concat(const char *prefix, const char *suffix, char join)
{
int len = 0;
char *new_str = NULL;
CRM_ASSERT(prefix != NULL);
CRM_ASSERT(suffix != NULL);
len = strlen(prefix) + strlen(suffix) + 2;
new_str = calloc(1, (len));
sprintf(new_str, "%s%c%s", prefix, join, suffix);
new_str[len - 1] = 0;
return new_str;
}
char *
generate_hash_key(const char *crm_msg_reference, const char *sys)
{
char *hash_key = crm_concat(sys ? sys : "none", crm_msg_reference, '_');
crm_trace("created hash key: (%s)", hash_key);
return hash_key;
}
char *
generate_hash_value(const char *src_node, const char *src_subsys)
{
char *hash_value = NULL;
if (src_node == NULL || src_subsys == NULL) {
return NULL;
}
if (strcasecmp(CRM_SYSTEM_DC, src_subsys) == 0) {
hash_value = crm_strdup(src_subsys);
CRM_ASSERT(hash_value);
return hash_value;
}
hash_value = crm_concat(src_node, src_subsys, '_');
crm_info("created hash value: (%s)", hash_value);
return hash_value;
}
char *
crm_itoa(int an_int)
{
int len = 32;
char *buffer = NULL;
buffer = calloc(1, (len + 1));
if (buffer != NULL) {
snprintf(buffer, len, "%d", an_int);
}
return buffer;
}
#ifdef HAVE_G_LOG_SET_DEFAULT_HANDLER
GLogFunc glib_log_default;
static void
crm_glib_handler(const gchar * log_domain, GLogLevelFlags flags, const gchar * message,
gpointer user_data)
{
int log_level = LOG_WARNING;
GLogLevelFlags msg_level = (flags & G_LOG_LEVEL_MASK);
switch (msg_level) {
case G_LOG_LEVEL_CRITICAL:
/* log and record how we got here */
crm_abort(__FILE__, __PRETTY_FUNCTION__, __LINE__, message, TRUE, TRUE);
return;
case G_LOG_LEVEL_ERROR:
log_level = LOG_ERR;
break;
case G_LOG_LEVEL_MESSAGE:
log_level = LOG_NOTICE;
break;
case G_LOG_LEVEL_INFO:
log_level = LOG_INFO;
break;
case G_LOG_LEVEL_DEBUG:
log_level = LOG_DEBUG;
break;
case G_LOG_LEVEL_WARNING:
case G_LOG_FLAG_RECURSION:
case G_LOG_FLAG_FATAL:
case G_LOG_LEVEL_MASK:
log_level = LOG_WARNING;
break;
}
do_crm_log(log_level, "%s: %s", log_domain, message);
}
#endif
void
crm_log_deinit(void)
{
#ifdef HAVE_G_LOG_SET_DEFAULT_HANDLER
g_log_set_default_handler(glib_log_default, NULL);
#endif
}
#define FMT_MAX 256
-void
-set_format_string(int method, const char *daemon, gboolean trace)
+static void
+set_format_string(int method, const char *daemon)
{
- static gboolean local_trace = FALSE;
int offset = 0;
char fmt[FMT_MAX];
if (method > QB_LOG_STDERR) {
/* When logging to a file */
struct utsname res;
if (uname(&res) == 0) {
offset +=
snprintf(fmt + offset, FMT_MAX - offset, "%%t [%d] %s %10s: ", getpid(),
res.nodename, daemon);
} else {
offset += snprintf(fmt + offset, FMT_MAX - offset, "%%t [%d] %10s: ", getpid(), daemon);
}
}
- local_trace |= trace;
- if (local_trace && method >= QB_LOG_STDERR) {
+ if (crm_tracing_enabled() && method >= QB_LOG_STDERR) {
offset += snprintf(fmt + offset, FMT_MAX - offset, "(%%-12f:%%5l %%g) %%-7p: %%n: ");
} else {
offset += snprintf(fmt + offset, FMT_MAX - offset, "%%g %%-7p: %%n: ");
}
if (method == QB_LOG_SYSLOG) {
offset += snprintf(fmt + offset, FMT_MAX - offset, "%%b");
} else {
offset += snprintf(fmt + offset, FMT_MAX - offset, "\t%%b");
}
qb_log_format_set(method, fmt);
}
static void
crm_add_logfile(const char *filename)
{
int fd = 0;
static gboolean have_logfile = FALSE;
if(filename == NULL && have_logfile == FALSE) {
filename = "/var/log/pacemaker.log";
}
if (filename == NULL) {
return; /* Nothing to do */
}
fd = qb_log_file_open(filename);
if(fd < 0) {
crm_perror(LOG_WARNING, "Couldn't send additional logging to %s", filename);
return;
}
crm_notice("Additional logging available in %s", filename);
- qb_log_filter_ctl(fd, QB_LOG_FILTER_ADD, QB_LOG_FILTER_FILE, "*", crm_log_level);
qb_log_ctl(fd, QB_LOG_CONF_ENABLED, QB_TRUE);
/* Set the default log format */
- set_format_string(fd, crm_system_name, FALSE);
- have_logfile = TRUE;
-}
-
-void
-update_all_trace_data(void)
-{
- int lpc = 0;
-
- if(getenv("PCMK_trace_files") || getenv("PCMK_trace_functions") || getenv("PCMK_trace_formats")) {
- if (qb_log_ctl(QB_LOG_STDERR, QB_LOG_CONF_STATE_GET, 0) != QB_LOG_STATE_ENABLED
- && qb_log_ctl(QB_LOG_BLACKBOX, QB_LOG_CONF_STATE_GET, 0) != QB_LOG_STATE_ENABLED) {
- /* Make sure tracing goes somewhere */
- crm_add_logfile(NULL);
- }
-
- } else {
- return;
- }
-
- /* No tracing to SYSLOG */
- for (lpc = QB_LOG_STDERR; lpc < QB_LOG_TARGET_MAX; lpc++) {
- if (qb_log_ctl(lpc, QB_LOG_CONF_STATE_GET, 0) == QB_LOG_STATE_ENABLED) {
-
- const char *env_value = NULL;
-
- env_value = getenv("PCMK_trace_files");
- if (env_value) {
- char token[500];
- const char *offset = NULL;
- const char *next = env_value;
-
- do {
- offset = next;
- next = strchrnul(offset, ',');
- snprintf(token, 499, "%.*s", (int)(next - offset), offset);
- crm_info("Looking for %s from %s (%d)", token, env_value, lpc);
-
- qb_log_filter_ctl(lpc, QB_LOG_FILTER_ADD, QB_LOG_FILTER_FILE, token, LOG_TRACE);
-
- if (next[0] != 0) {
- next++;
- }
+ set_format_string(fd, crm_system_name);
- } while (next != NULL && next[0] != 0);
- }
-
- env_value = getenv("PCMK_trace_formats");
- if (env_value) {
- crm_info("Looking for format strings matching %s (%d)", env_value, lpc);
- qb_log_filter_ctl(lpc,
- QB_LOG_FILTER_ADD, QB_LOG_FILTER_FORMAT, env_value, LOG_TRACE);
- }
-
- env_value = getenv("PCMK_trace_functions");
- if (env_value) {
- crm_info("Looking for functions named %s (%d)", env_value, lpc);
- qb_log_filter_ctl(lpc,
- QB_LOG_FILTER_ADD, QB_LOG_FILTER_FUNCTION, env_value, LOG_TRACE);
- }
-
- set_format_string(lpc, crm_system_name, TRUE);
- }
- }
+ /* Enable callsites */
+ crm_update_callsites();
+ have_logfile = TRUE;
}
#ifndef NAME_MAX
# define NAME_MAX 256
#endif
gboolean
daemon_option_enabled(const char *daemon, const char *option)
{
char env_name[NAME_MAX];
const char *value = NULL;
snprintf(env_name, NAME_MAX, "PCMK_%s", option);
value = getenv(env_name);
if (value != NULL && crm_is_true(value)) {
return TRUE;
} else if (value != NULL && strstr(value, daemon)) {
return TRUE;
}
snprintf(env_name, NAME_MAX, "HA_%s", option);
value = getenv(env_name);
if (value != NULL && crm_is_true(value)) {
return TRUE;
}
return FALSE;
}
static char *blackbox_file_prefix = NULL;
+static gboolean blackbox_tracing_enabled = FALSE;
void
crm_enable_blackbox_tracing(int nsig)
{
- static gboolean enabled = FALSE;
- if(enabled) {
- enabled = FALSE;
- qb_log_filter_ctl(QB_LOG_BLACKBOX, QB_LOG_FILTER_REMOVE, QB_LOG_FILTER_FILE, "*", LOG_TRACE);
+ if(blackbox_tracing_enabled) {
+ blackbox_tracing_enabled = FALSE;
} else {
- enabled = TRUE;
- qb_log_filter_ctl(QB_LOG_BLACKBOX, QB_LOG_FILTER_ADD, QB_LOG_FILTER_FILE, "*", LOG_TRACE);
+ blackbox_tracing_enabled = TRUE;
}
- crm_debug("Blackbox tracing is %s", enabled?"on":"off");
+ crm_update_callsites();
+ crm_debug("Blackbox tracing is %s", blackbox_tracing_enabled?"on":"off");
}
void
crm_enable_blackbox(int nsig)
{
if(blackbox_file_prefix == NULL) {
pid_t pid = getpid();
blackbox_file_prefix = malloc(NAME_MAX);
snprintf(blackbox_file_prefix, NAME_MAX, "%s/blackbox-%s-%d", CRM_BLACKBOX_DIR, crm_system_name, pid);
}
if (qb_log_ctl(QB_LOG_BLACKBOX, QB_LOG_CONF_STATE_GET, 0) != QB_LOG_STATE_ENABLED) {
qb_log_ctl(QB_LOG_BLACKBOX, QB_LOG_CONF_SIZE, 1024*1024); /* Any size change drops existing entries */
qb_log_ctl(QB_LOG_BLACKBOX, QB_LOG_CONF_ENABLED, QB_TRUE); /* Setting the size seems to disable it */
- qb_log_filter_ctl(QB_LOG_BLACKBOX, QB_LOG_FILTER_ADD, QB_LOG_FILTER_FILE, "*", LOG_DEBUG);
crm_notice("Initiated blackbox recorder: %s", blackbox_file_prefix);
crm_signal(SIGSEGV, crm_write_blackbox);
+ crm_update_callsites();
/* Original meanings from signal(7)
*
* Signal Value Action Comment
* SIGPROF 27,27,29 Term Profiling timer expired
* SIGTRAP 5 Core Trace/breakpoint trap
*
* Our usage is as similar as possible
*/
mainloop_add_signal(SIGPROF, crm_enable_blackbox_tracing);
mainloop_add_signal(SIGTRAP, crm_write_blackbox);
}
}
void
crm_write_blackbox(int nsig)
{
static int counter = 1;
static time_t last = 0;
char buffer[NAME_MAX];
time_t now = time(NULL);
if(blackbox_file_prefix == NULL) {
return;
}
switch(nsig) {
case 0:
case SIGTRAP:
/* The graceful case - such as assertion failure or user request */
snprintf(buffer, NAME_MAX, "%s.%d", blackbox_file_prefix, counter++);
if(nsig == 0 && (now - last) < 2) {
/* Prevent over-dumping */
return;
} else if(nsig == SIGTRAP) {
crm_notice("Blackbox dump requested, please see %s for contents", buffer);
} else {
crm_notice("Problem detected, please see %s for additional details", buffer);
}
last = now;
qb_log_blackbox_write_to_file(buffer);
/* Flush the existing contents
* A size change would also work
*/
qb_log_ctl(QB_LOG_BLACKBOX, QB_LOG_CONF_ENABLED, QB_FALSE);
qb_log_ctl(QB_LOG_BLACKBOX, QB_LOG_CONF_ENABLED, QB_TRUE);
break;
default:
/* Do as little as possible, just try to get what we have out
* We logged the filename when the blackbox was enabled
*/
crm_signal(nsig, SIG_DFL);
qb_log_blackbox_write_to_file(blackbox_file_prefix);
qb_log_fini();
raise(nsig);
break;
}
}
gboolean
crm_log_cli_init(const char *entity)
{
return crm_log_init(entity, LOG_ERR, FALSE, FALSE, 0, NULL, TRUE);
}
static const char *crm_quark_to_string(uint32_t tag)
{
const char *text = g_quark_to_string(tag);
if(text) {
return text;
}
return "";
}
+static void
+crm_log_filter_source(int source, const char *trace_files, const char *trace_fns, const char *trace_fmts, const char *trace_tags, struct qb_log_callsite *cs)
+{
+ if (qb_log_ctl(source, QB_LOG_CONF_STATE_GET, 0) != QB_LOG_STATE_ENABLED) {
+ return;
+ } else if (source == QB_LOG_SYSLOG) { /* No tracing to syslog */
+ if(cs->priority <= LOG_INFO && cs->priority <= crm_log_level) {
+ qb_bit_set(cs->targets, source);
+ }
+ /* Tracing options... */
+ } else if(source == QB_LOG_BLACKBOX && blackbox_tracing_enabled) {
+ qb_bit_set(cs->targets, source);
+ } else if (cs->priority <= crm_log_level) {
+ qb_bit_set(cs->targets, source);
+ } else if(trace_files && strstr(trace_files, cs->filename) != NULL) {
+ qb_bit_set(cs->targets, source);
+ } else if(trace_fns && strstr(trace_fns, cs->function) != NULL) {
+ qb_bit_set(cs->targets, source);
+ } else if(trace_fmts && strstr(trace_fmts, cs->format) != NULL) {
+ qb_bit_set(cs->targets, source);
+ } else if(trace_tags && cs->tags != 0 && g_quark_to_string(cs->tags) != NULL) {
+ qb_bit_set(cs->targets, source);
+ }
+}
+
+static void
+crm_log_filter(struct qb_log_callsite *cs)
+{
+ int lpc = 0;
+ static int need_init = 1;
+ static const char *trace_fns = NULL;
+ static const char *trace_tags = NULL;
+ static const char *trace_fmts = NULL;
+ static const char *trace_files = NULL;
+
+ if(need_init) {
+ need_init = 0;
+ trace_fns = getenv("PCMK_trace_functions");
+ trace_fmts = getenv("PCMK_trace_formats");
+ trace_tags = getenv("PCMK_trace_tags");
+ trace_files = getenv("PCMK_trace_files");
+
+ if (trace_tags != NULL) {
+ uint32_t tag;
+ char token[500];
+ const char *offset = NULL;
+ const char *next = trace_tags;
+
+ do {
+ offset = next;
+ next = strchrnul(offset, ',');
+ snprintf(token, 499, "%.*s", (int)(next - offset), offset);
+
+ tag = g_quark_from_string(token);
+ crm_info("Created GQuark %u from token '%s' in '%s'", tag, token, trace_tags);
+
+ if (next[0] != 0) {
+ next++;
+ }
+
+ } while (next != NULL && next[0] != 0);
+ }
+ }
+
+ cs->targets = 0; /* Reset then find targets to enable */
+ for (lpc = QB_LOG_SYSLOG; lpc < QB_LOG_TARGET_MAX; lpc++) {
+ crm_log_filter_source(lpc, trace_files, trace_fns, trace_fmts, trace_tags, cs);
+ }
+}
+
+void
+crm_update_callsites(void)
+{
+ crm_notice("Enabling callsites based on priority=%d, files=%s, functions=%s, formats=%s, tags=%s",
+ crm_log_level,
+ getenv("PCMK_trace_files"),
+ getenv("PCMK_trace_functions"),
+ getenv("PCMK_trace_formats"),
+ getenv("PCMK_trace_tags"));
+ qb_log_filter_fn_set(crm_log_filter);
+}
+
+static gboolean
+crm_tracing_enabled(void)
+{
+ if(crm_log_level >= LOG_TRACE) {
+ return TRUE;
+ } else if(getenv("PCMK_trace_files") || getenv("PCMK_trace_functions") || getenv("PCMK_trace_formats") || getenv("PCMK_trace_tags")) {
+ return TRUE;
+ }
+ return FALSE;
+}
+
gboolean
crm_log_init(const char *entity, int level, gboolean daemon, gboolean to_stderr,
int argc, char **argv, gboolean quiet)
{
int lpc = 0;
const char *logfile = getenv("HA_debugfile");
const char *facility = getenv("HA_logfacility");
/* Redirect messages from glib functions to our handler */
#ifdef HAVE_G_LOG_SET_DEFAULT_HANDLER
glib_log_default = g_log_set_default_handler(crm_glib_handler, NULL);
#endif
/* and for good measure... - this enum is a bit field (!) */
g_log_set_always_fatal((GLogLevelFlags) 0); /*value out of range */
if (facility == NULL) {
/* Set a default */
facility = "daemon";
}
if (entity) {
crm_system_name = entity;
} else if (argc > 0 && argv != NULL) {
char *mutable = crm_strdup(argv[0]);
crm_system_name = basename(mutable);
if (strstr(crm_system_name, "lt-") == crm_system_name) {
crm_system_name += 3;
}
} else if (crm_system_name == NULL) {
crm_system_name = "Unknown";
}
setenv("PCMK_service", crm_system_name, 1);
if (daemon_option_enabled(crm_system_name, "debug")) {
/* Override the default setting */
level = LOG_DEBUG;
}
if (daemon_option_enabled(crm_system_name, "stderr")) {
/* Override the default setting */
to_stderr = TRUE;
}
+
crm_log_level = level;
qb_log_init(crm_system_name, qb_log_facility2int(facility), level);
qb_log_tags_stringify_fn_set(crm_quark_to_string);
+ if(daemon
+ && crm_tracing_enabled()
+ && qb_log_ctl(QB_LOG_STDERR, QB_LOG_CONF_STATE_GET, 0) != QB_LOG_STATE_ENABLED
+ && qb_log_ctl(QB_LOG_BLACKBOX, QB_LOG_CONF_STATE_GET, 0) != QB_LOG_STATE_ENABLED) {
+ /* Make sure tracing goes somewhere */
+ crm_add_logfile(NULL);
+ }
+
+ crm_update_callsites();
+
if (quiet) {
/* Nuke any syslog activity */
unsetenv("HA_logfacility");
qb_log_ctl(QB_LOG_SYSLOG, QB_LOG_CONF_ENABLED, QB_FALSE);
} else {
crm_log_args(argc, argv);
if(daemon) {
setenv("HA_logfacility", facility, TRUE);
}
}
if (daemon_option_enabled(crm_system_name, "blackbox")) {
crm_enable_blackbox(0);
}
/* Set default format strings */
for (lpc = QB_LOG_SYSLOG; lpc < QB_LOG_TARGET_MAX; lpc++) {
- set_format_string(lpc, crm_system_name, FALSE);
+ set_format_string(lpc, crm_system_name);
}
crm_enable_stderr(to_stderr);
if(logfile) {
crm_add_logfile(logfile);
}
/* Ok, now we can start logging... */
if (daemon) {
const char *user = getenv("USER");
if (user != NULL && safe_str_neq(user, "root") && safe_str_neq(user, CRM_DAEMON_USER)) {
crm_trace("Not switching to corefile directory for %s", user);
daemon = FALSE;
}
}
if (daemon) {
int user = getuid();
const char *base = HA_COREDIR;
struct passwd *pwent = getpwuid(user);
if (pwent == NULL) {
crm_perror(LOG_ERR, "Cannot get name for uid: %d", user);
} else if (safe_str_neq(pwent->pw_name, "root")
&& safe_str_neq(pwent->pw_name, "nobody")
&& safe_str_neq(pwent->pw_name, CRM_DAEMON_USER)) {
crm_debug("Don't change active directory for regular user: %s", pwent->pw_name);
} else if (chdir(base) < 0) {
crm_perror(LOG_ERR, "Cannot change active directory to %s", base);
} else if (chdir(pwent->pw_name) < 0) {
crm_perror(LOG_ERR, "Cannot change active directory to %s/%s", base, pwent->pw_name);
} else {
crm_info("Changed active directory to %s/%s", base, pwent->pw_name);
#if 0
{
char path[512];
snprintf(path, 512, "%s-%d", crm_system_name, getpid());
mkdir(path, 0750);
chdir(path);
crm_info("Changed active directory to %s/%s/%s", base, pwent->pw_name, path);
}
#endif
}
mainloop_add_signal(SIGUSR1, crm_enable_blackbox);
}
- update_all_trace_data();
-
return TRUE;
}
/* returns the old value */
unsigned int
set_crm_log_level(unsigned int level)
{
unsigned int old = crm_log_level;
-
- int lpc = 0;
-
- for (lpc = QB_LOG_SYSLOG; lpc < QB_LOG_TARGET_MAX; lpc++) {
- if (qb_log_ctl(lpc, QB_LOG_CONF_STATE_GET, 0) == QB_LOG_STATE_ENABLED) {
-
- if (old > level) {
- qb_log_filter_ctl(lpc, QB_LOG_FILTER_REMOVE, QB_LOG_FILTER_FILE, "*", old);
- }
-
- if (old != level) {
- int rc = qb_log_filter_ctl(lpc, QB_LOG_FILTER_ADD, QB_LOG_FILTER_FILE, "*", level);
-
- crm_info("New log level: %d %d", level, rc);
- /* qb_log_ctl(QB_LOG_SYSLOG, QB_LOG_CONF_PRIORITY_BUMP, LOG_INFO - LOG_DEBUG);
- * Needed? Yes, if we want trace logging sent to syslog but the semantics suck
- */
- }
-
- set_format_string(lpc, crm_system_name, level > LOG_DEBUG);
- }
- }
crm_log_level = level;
+ crm_update_callsites();
+ crm_info("New log level: %d", level);
return old;
}
void
crm_enable_stderr(int enable)
{
if (enable && qb_log_ctl(QB_LOG_STDERR, QB_LOG_CONF_STATE_GET, 0) != QB_LOG_STATE_ENABLED) {
- qb_log_filter_ctl(QB_LOG_STDERR, QB_LOG_FILTER_ADD, QB_LOG_FILTER_FILE, "*", crm_log_level);
- set_format_string(QB_LOG_STDERR, crm_system_name, crm_log_level > LOG_DEBUG);
qb_log_ctl(QB_LOG_STDERR, QB_LOG_CONF_ENABLED, QB_TRUE);
-
- /* Need to reprocess now that a new target is active */
- update_all_trace_data();
+ crm_update_callsites();
} else if (enable == FALSE) {
qb_log_ctl(QB_LOG_STDERR, QB_LOG_CONF_ENABLED, QB_FALSE);
}
}
void
crm_bump_log_level(void)
{
if (qb_log_ctl(QB_LOG_STDERR, QB_LOG_CONF_STATE_GET, 0) == QB_LOG_STATE_ENABLED) {
set_crm_log_level(crm_log_level + 1);
}
crm_enable_stderr(TRUE);
}
int
crm_should_log(int level)
{
if (level <= crm_log_level) {
return 1;
}
return 0;
}
unsigned int
get_crm_log_level(void)
{
return crm_log_level;
}
static int
crm_version_helper(const char *text, char **end_text)
{
int atoi_result = -1;
CRM_ASSERT(end_text != NULL);
errno = 0;
if (text != NULL && text[0] != 0) {
atoi_result = (int)strtol(text, end_text, 10);
if (errno == EINVAL) {
crm_err("Conversion of '%s' %c failed", text, text[0]);
atoi_result = -1;
}
}
return atoi_result;
}
void
crm_log_args(int argc, char **argv)
{
int lpc = 0;
int len = 0;
int existing_len = 0;
char *arg_string = NULL;
if (argc == 0 || argv == NULL) {
return;
}
for (; lpc < argc; lpc++) {
if (argv[lpc] == NULL) {
break;
}
len = 2 + strlen(argv[lpc]); /* +1 space, +1 EOS */
arg_string = realloc(arg_string, len + existing_len);
existing_len += sprintf(arg_string + existing_len, "%s ", argv[lpc]);
}
do_crm_log_always(LOG_INFO, "Invoked: %s", arg_string);
free(arg_string);
}
/*
* version1 < version2 : -1
* version1 = version2 : 0
* version1 > version2 : 1
*/
int
compare_version(const char *version1, const char *version2)
{
int rc = 0;
int lpc = 0;
char *ver1_copy = NULL, *ver2_copy = NULL;
char *rest1 = NULL, *rest2 = NULL;
if (version1 == NULL && version2 == NULL) {
return 0;
} else if (version1 == NULL) {
return -1;
} else if (version2 == NULL) {
return 1;
}
ver1_copy = crm_strdup(version1);
ver2_copy = crm_strdup(version2);
rest1 = ver1_copy;
rest2 = ver2_copy;
while (1) {
int digit1 = 0;
int digit2 = 0;
lpc++;
if (rest1 == rest2) {
break;
}
if (rest1 != NULL) {
digit1 = crm_version_helper(rest1, &rest1);
}
if (rest2 != NULL) {
digit2 = crm_version_helper(rest2, &rest2);
}
if (digit1 < digit2) {
rc = -1;
crm_trace("%d < %d", digit1, digit2);
break;
} else if (digit1 > digit2) {
rc = 1;
crm_trace("%d > %d", digit1, digit2);
break;
}
if (rest1 != NULL && rest1[0] == '.') {
rest1++;
}
if (rest1 != NULL && rest1[0] == 0) {
rest1 = NULL;
}
if (rest2 != NULL && rest2[0] == '.') {
rest2++;
}
if (rest2 != NULL && rest2[0] == 0) {
rest2 = NULL;
}
}
free(ver1_copy);
free(ver2_copy);
if (rc == 0) {
crm_trace("%s == %s (%d)", version1, version2, lpc);
} else if (rc < 0) {
crm_trace("%s < %s (%d)", version1, version2, lpc);
} else if (rc > 0) {
crm_trace("%s > %s (%d)", version1, version2, lpc);
}
return rc;
}
gboolean do_stderr = FALSE;
void
g_hash_destroy_str(gpointer data)
{
free(data);
}
#include <sys/types.h>
/* #include <stdlib.h> */
/* #include <limits.h> */
long long
crm_int_helper(const char *text, char **end_text)
{
long long result = -1;
char *local_end_text = NULL;
int saved_errno = 0;
errno = 0;
if (text != NULL) {
#ifdef ANSI_ONLY
if (end_text != NULL) {
result = strtol(text, end_text, 10);
} else {
result = strtol(text, &local_end_text, 10);
}
#else
if (end_text != NULL) {
result = strtoll(text, end_text, 10);
} else {
result = strtoll(text, &local_end_text, 10);
}
#endif
saved_errno = errno;
/* CRM_CHECK(errno != EINVAL); */
if (errno == EINVAL) {
crm_err("Conversion of %s failed", text);
result = -1;
} else if (errno == ERANGE) {
crm_err("Conversion of %s was clipped: %lld", text, result);
} else if (errno != 0) {
crm_perror(LOG_ERR, "Conversion of %s failed:", text);
}
if (local_end_text != NULL && local_end_text[0] != '\0') {
crm_err("Characters left over after parsing '%s': '%s'", text, local_end_text);
}
errno = saved_errno;
}
return result;
}
int
crm_parse_int(const char *text, const char *default_text)
{
int atoi_result = -1;
if (text != NULL) {
atoi_result = crm_int_helper(text, NULL);
if (errno == 0) {
return atoi_result;
}
}
if (default_text != NULL) {
atoi_result = crm_int_helper(default_text, NULL);
if (errno == 0) {
return atoi_result;
}
} else {
crm_err("No default conversion value supplied");
}
return -1;
}
gboolean
safe_str_neq(const char *a, const char *b)
{
if (a == b) {
return FALSE;
} else if (a == NULL || b == NULL) {
return TRUE;
} else if (strcasecmp(a, b) == 0) {
return FALSE;
}
return TRUE;
}
char *
crm_strdup_fn(const char *src, const char *file, const char *fn, int line)
{
char *dup = NULL;
CRM_CHECK(src != NULL, crm_err("Could not perform copy at %s:%d (%s)", file, line, fn);
return NULL);
dup = calloc(1, strlen(src) + 1);
return strcpy(dup, src);
}
gboolean
crm_is_true(const char *s)
{
gboolean ret = FALSE;
if (s != NULL) {
crm_str_to_boolean(s, &ret);
}
return ret;
}
int
crm_str_to_boolean(const char *s, int *ret)
{
if (s == NULL) {
return -1;
} else if (strcasecmp(s, "true") == 0
|| strcasecmp(s, "on") == 0
|| strcasecmp(s, "yes") == 0 || strcasecmp(s, "y") == 0 || strcasecmp(s, "1") == 0) {
*ret = TRUE;
return 1;
} else if (strcasecmp(s, "false") == 0
|| strcasecmp(s, "off") == 0
|| strcasecmp(s, "no") == 0 || strcasecmp(s, "n") == 0 || strcasecmp(s, "0") == 0) {
*ret = FALSE;
return 1;
}
return -1;
}
#ifndef NUMCHARS
# define NUMCHARS "0123456789."
#endif
#ifndef WHITESPACE
# define WHITESPACE " \t\n\r\f"
#endif
unsigned long long
crm_get_interval(const char *input)
{
ha_time_t *interval = NULL;
char *input_copy = crm_strdup(input);
char *input_copy_mutable = input_copy;
unsigned long long msec = 0;
if (input == NULL) {
return 0;
} else if (input[0] != 'P') {
free(input_copy);
return crm_get_msec(input);
}
interval = parse_time_duration(&input_copy_mutable);
msec = date_in_seconds(interval);
free_ha_date(interval);
free(input_copy);
return msec * 1000;
}
long long
crm_get_msec(const char *input)
{
const char *cp = input;
const char *units;
long long multiplier = 1000;
long long divisor = 1;
long long msec = -1;
char *end_text = NULL;
/* double dret; */
if (input == NULL) {
return msec;
}
cp += strspn(cp, WHITESPACE);
units = cp + strspn(cp, NUMCHARS);
units += strspn(units, WHITESPACE);
if (strchr(NUMCHARS, *cp) == NULL) {
return msec;
}
if (strncasecmp(units, "ms", 2) == 0 || strncasecmp(units, "msec", 4) == 0) {
multiplier = 1;
divisor = 1;
} else if (strncasecmp(units, "us", 2) == 0 || strncasecmp(units, "usec", 4) == 0) {
multiplier = 1;
divisor = 1000;
} else if (strncasecmp(units, "s", 1) == 0 || strncasecmp(units, "sec", 3) == 0) {
multiplier = 1000;
divisor = 1;
} else if (strncasecmp(units, "m", 1) == 0 || strncasecmp(units, "min", 3) == 0) {
multiplier = 60 * 1000;
divisor = 1;
} else if (strncasecmp(units, "h", 1) == 0 || strncasecmp(units, "hr", 2) == 0) {
multiplier = 60 * 60 * 1000;
divisor = 1;
} else if (*units != EOS && *units != '\n' && *units != '\r') {
return msec;
}
msec = crm_int_helper(cp, &end_text);
msec *= multiplier;
msec /= divisor;
/* dret += 0.5; */
/* msec = (long long)dret; */
return msec;
}
char *
generate_op_key(const char *rsc_id, const char *op_type, int interval)
{
int len = 35;
char *op_id = NULL;
CRM_CHECK(rsc_id != NULL, return NULL);
CRM_CHECK(op_type != NULL, return NULL);
len += strlen(op_type);
len += strlen(rsc_id);
op_id = calloc(1, len);
CRM_CHECK(op_id != NULL, return NULL);
sprintf(op_id, "%s_%s_%d", rsc_id, op_type, interval);
return op_id;
}
gboolean
parse_op_key(const char *key, char **rsc_id, char **op_type, int *interval)
{
char *notify = NULL;
char *mutable_key = NULL;
char *mutable_key_ptr = NULL;
int len = 0, offset = 0, ch = 0;
CRM_CHECK(key != NULL, return FALSE);
*interval = 0;
len = strlen(key);
offset = len - 1;
crm_trace("Source: %s", key);
while (offset > 0 && isdigit(key[offset])) {
int digits = len - offset;
ch = key[offset] - '0';
CRM_CHECK(ch < 10, return FALSE);
CRM_CHECK(ch >= 0, return FALSE);
while (digits > 1) {
digits--;
ch = ch * 10;
}
*interval += ch;
offset--;
}
crm_trace(" Interval: %d", *interval);
CRM_CHECK(key[offset] == '_', return FALSE);
mutable_key = crm_strdup(key);
mutable_key_ptr = mutable_key_ptr;
mutable_key[offset] = 0;
offset--;
while (offset > 0 && key[offset] != '_') {
offset--;
}
CRM_CHECK(key[offset] == '_', free(mutable_key); return FALSE);
mutable_key_ptr = mutable_key + offset + 1;
crm_trace(" Action: %s", mutable_key_ptr);
*op_type = crm_strdup(mutable_key_ptr);
mutable_key[offset] = 0;
offset--;
CRM_CHECK(mutable_key != mutable_key_ptr, free(mutable_key); return FALSE);
notify = strstr(mutable_key, "_post_notify");
if (safe_str_eq(notify, "_post_notify")) {
notify[0] = 0;
}
notify = strstr(mutable_key, "_pre_notify");
if (safe_str_eq(notify, "_pre_notify")) {
notify[0] = 0;
}
crm_trace(" Resource: %s", mutable_key);
*rsc_id = mutable_key;
return TRUE;
}
char *
generate_notify_key(const char *rsc_id, const char *notify_type, const char *op_type)
{
int len = 12;
char *op_id = NULL;
CRM_CHECK(rsc_id != NULL, return NULL);
CRM_CHECK(op_type != NULL, return NULL);
CRM_CHECK(notify_type != NULL, return NULL);
len += strlen(op_type);
len += strlen(rsc_id);
len += strlen(notify_type);
op_id = calloc(1, len);
if (op_id != NULL) {
sprintf(op_id, "%s_%s_notify_%s_0", rsc_id, notify_type, op_type);
}
return op_id;
}
char *
generate_transition_magic_v202(const char *transition_key, int op_status)
{
int len = 80;
char *fail_state = NULL;
CRM_CHECK(transition_key != NULL, return NULL);
len += strlen(transition_key);
fail_state = calloc(1, len);
if (fail_state != NULL) {
snprintf(fail_state, len, "%d:%s", op_status, transition_key);
}
return fail_state;
}
char *
generate_transition_magic(const char *transition_key, int op_status, int op_rc)
{
int len = 80;
char *fail_state = NULL;
CRM_CHECK(transition_key != NULL, return NULL);
len += strlen(transition_key);
fail_state = calloc(1, len);
if (fail_state != NULL) {
snprintf(fail_state, len, "%d:%d;%s", op_status, op_rc, transition_key);
}
return fail_state;
}
gboolean
decode_transition_magic(const char *magic, char **uuid, int *transition_id, int *action_id,
int *op_status, int *op_rc, int *target_rc)
{
int res = 0;
char *key = NULL;
gboolean result = TRUE;
CRM_CHECK(magic != NULL, return FALSE);
CRM_CHECK(op_rc != NULL, return FALSE);
CRM_CHECK(op_status != NULL, return FALSE);
key = calloc(1, strlen(magic) + 1);
res = sscanf(magic, "%d:%d;%s", op_status, op_rc, key);
if (res != 3) {
crm_crit("Only found %d items in: %s", res, magic);
result = FALSE;
goto bail;
}
CRM_CHECK(decode_transition_key(key, uuid, transition_id, action_id, target_rc), result = FALSE;
goto bail;
);
bail:
free(key);
return result;
}
char *
generate_transition_key(int transition_id, int action_id, int target_rc, const char *node)
{
int len = 40;
char *fail_state = NULL;
CRM_CHECK(node != NULL, return NULL);
len += strlen(node);
fail_state = calloc(1, len);
if (fail_state != NULL) {
snprintf(fail_state, len, "%d:%d:%d:%s", action_id, transition_id, target_rc, node);
}
return fail_state;
}
gboolean
decode_transition_key(const char *key, char **uuid, int *transition_id, int *action_id,
int *target_rc)
{
int res = 0;
gboolean done = FALSE;
CRM_CHECK(uuid != NULL, return FALSE);
CRM_CHECK(target_rc != NULL, return FALSE);
CRM_CHECK(action_id != NULL, return FALSE);
CRM_CHECK(transition_id != NULL, return FALSE);
*uuid = calloc(1, strlen(key) + 1);
res = sscanf(key, "%d:%d:%d:%s", action_id, transition_id, target_rc, *uuid);
switch (res) {
case 4:
/* Post Pacemaker 0.6 */
done = TRUE;
break;
case 3:
case 2:
/* this can be tricky - the UUID might start with an integer */
/* Until Pacemaker 0.6 */
done = TRUE;
*target_rc = -1;
res = sscanf(key, "%d:%d:%s", action_id, transition_id, *uuid);
if (res == 2) {
*action_id = -1;
res = sscanf(key, "%d:%s", transition_id, *uuid);
CRM_CHECK(res == 2, done = FALSE);
} else if (res != 3) {
CRM_CHECK(res == 3, done = FALSE);
}
break;
case 1:
/* Prior to Heartbeat 2.0.8 */
done = TRUE;
*action_id = -1;
*target_rc = -1;
res = sscanf(key, "%d:%s", transition_id, *uuid);
CRM_CHECK(res == 2, done = FALSE);
break;
default:
crm_crit("Unhandled sscanf result (%d) for %s", res, key);
}
if (strlen(*uuid) != 36) {
crm_warn("Bad UUID (%s) in sscanf result (%d) for %s", *uuid, res, key);
}
if (done == FALSE) {
crm_err("Cannot decode '%s' rc=%d", key, res);
free(*uuid);
*uuid = NULL;
*target_rc = -1;
*action_id = -1;
*transition_id = -1;
}
return done;
}
void
filter_action_parameters(xmlNode * param_set, const char *version)
{
char *key = NULL;
char *timeout = NULL;
char *interval = NULL;
const char *attr_filter[] = {
XML_ATTR_ID,
XML_ATTR_CRM_VERSION,
XML_LRM_ATTR_OP_DIGEST,
};
gboolean do_delete = FALSE;
int lpc = 0;
static int meta_len = 0;
if (meta_len == 0) {
meta_len = strlen(CRM_META);
}
if (param_set == NULL) {
return;
}
for (lpc = 0; lpc < DIMOF(attr_filter); lpc++) {
xml_remove_prop(param_set, attr_filter[lpc]);
}
key = crm_meta_name(XML_LRM_ATTR_INTERVAL);
interval = crm_element_value_copy(param_set, key);
free(key);
key = crm_meta_name(XML_ATTR_TIMEOUT);
timeout = crm_element_value_copy(param_set, key);
if (param_set) {
xmlAttrPtr xIter = param_set->properties;
while (xIter) {
const char *prop_name = (const char *)xIter->name;
xIter = xIter->next;
do_delete = FALSE;
if (strncasecmp(prop_name, CRM_META, meta_len) == 0) {
do_delete = TRUE;
}
if (do_delete) {
xml_remove_prop(param_set, prop_name);
}
}
}
if (crm_get_msec(interval) > 0 && compare_version(version, "1.0.8") > 0) {
/* Re-instate the operation's timeout value */
if (timeout != NULL) {
crm_xml_add(param_set, key, timeout);
}
}
free(interval);
free(timeout);
free(key);
}
void
filter_reload_parameters(xmlNode * param_set, const char *restart_string)
{
int len = 0;
char *name = NULL;
char *match = NULL;
if (param_set == NULL) {
return;
}
if (param_set) {
xmlAttrPtr xIter = param_set->properties;
while (xIter) {
const char *prop_name = (const char *)xIter->name;
xIter = xIter->next;
name = NULL;
len = strlen(prop_name) + 3;
name = calloc(1, len);
sprintf(name, " %s ", prop_name);
name[len - 1] = 0;
match = strstr(restart_string, name);
if (match == NULL) {
crm_trace("%s not found in %s", prop_name, restart_string);
xml_remove_prop(param_set, prop_name);
}
free(name);
}
}
}
void
crm_abort(const char *file, const char *function, int line,
const char *assert_condition, gboolean do_core, gboolean do_fork)
{
int rc = 0;
int pid = 0;
int status = 0;
/* Implied by the parent's error logging below */
/* crm_write_blackbox(0); */
if (do_core == FALSE) {
crm_err("%s: Triggered assert at %s:%d : %s", function, file, line, assert_condition);
return;
} else if (do_fork) {
pid = fork();
} else {
crm_err("%s: Triggered fatal assert at %s:%d : %s", function, file, line, assert_condition);
}
switch (pid) {
case -1:
crm_crit("%s: Cannot create core for non-fatal assert at %s:%d : %s",
function, file, line, assert_condition);
return;
case 0: /* Child */
abort();
break;
default: /* Parent */
crm_err("%s: Forked child %d to record non-fatal assert at %s:%d : %s",
function, pid, file, line, assert_condition);
do {
rc = waitpid(pid, &status, 0);
if (rc < 0 && errno != EINTR) {
crm_perror(LOG_ERR, "%s: Cannot wait on forked child %d", function, pid);
}
} while (rc < 0 && errno == EINTR);
return;
}
}
char *
generate_series_filename(const char *directory, const char *series, int sequence, gboolean bzip)
{
int len = 40;
char *filename = NULL;
const char *ext = "raw";
CRM_CHECK(directory != NULL, return NULL);
CRM_CHECK(series != NULL, return NULL);
len += strlen(directory);
len += strlen(series);
filename = calloc(1, len);
CRM_CHECK(filename != NULL, return NULL);
if (bzip) {
ext = "bz2";
}
sprintf(filename, "%s/%s-%d.%s", directory, series, sequence, ext);
return filename;
}
int
get_last_sequence(const char *directory, const char *series)
{
FILE *file_strm = NULL;
int start = 0, length = 0, read_len = 0;
char *series_file = NULL;
char *buffer = NULL;
int seq = 0;
int len = 36;
CRM_CHECK(directory != NULL, return 0);
CRM_CHECK(series != NULL, return 0);
len += strlen(directory);
len += strlen(series);
series_file = calloc(1, len);
CRM_CHECK(series_file != NULL, return 0);
sprintf(series_file, "%s/%s.last", directory, series);
file_strm = fopen(series_file, "r");
if (file_strm == NULL) {
crm_debug("Series file %s does not exist", series_file);
free(series_file);
return 0;
}
/* see how big the file is */
start = ftell(file_strm);
fseek(file_strm, 0L, SEEK_END);
length = ftell(file_strm);
fseek(file_strm, 0L, start);
CRM_ASSERT(length >= 0);
CRM_ASSERT(start == ftell(file_strm));
if (length <= 0) {
crm_info("%s was not valid", series_file);
free(buffer);
buffer = NULL;
} else {
crm_trace("Reading %d bytes from file", length);
buffer = calloc(1, (length + 1));
read_len = fread(buffer, 1, length, file_strm);
if (read_len != length) {
crm_err("Calculated and read bytes differ: %d vs. %d", length, read_len);
free(buffer);
buffer = NULL;
}
}
free(series_file);
seq = crm_parse_int(buffer, "0");
free(buffer);
fclose(file_strm);
return seq;
}
void
write_last_sequence(const char *directory, const char *series, int sequence, int max)
{
int rc = 0;
int len = 36;
FILE *file_strm = NULL;
char *series_file = NULL;
CRM_CHECK(directory != NULL, return);
CRM_CHECK(series != NULL, return);
if (max == 0) {
return;
}
if (sequence >= max) {
sequence = 0;
}
len += strlen(directory);
len += strlen(series);
series_file = calloc(1, len);
sprintf(series_file, "%s/%s.last", directory, series);
file_strm = fopen(series_file, "w");
if (file_strm == NULL) {
crm_err("Cannout open series file %s for writing", series_file);
goto bail;
}
rc = fprintf(file_strm, "%d", sequence);
if (rc < 0) {
crm_perror(LOG_ERR, "Cannot write to series file %s", series_file);
}
bail:
if (file_strm != NULL) {
fflush(file_strm);
fclose(file_strm);
}
free(series_file);
}
#define LOCKSTRLEN 11
int
crm_pid_active(long pid)
{
if (pid <= 0) {
return -1;
} else if (kill(pid, 0) < 0 && errno == ESRCH) {
return 0;
}
#ifndef HAVE_PROC_PID
return 1;
#else
{
int rc = 0;
int running = 0;
char proc_path[PATH_MAX], exe_path[PATH_MAX], myexe_path[PATH_MAX];
/* check to make sure pid hasn't been reused by another process */
snprintf(proc_path, sizeof(proc_path), "/proc/%lu/exe", pid);
rc = readlink(proc_path, exe_path, PATH_MAX - 1);
if (rc < 0) {
crm_perror(LOG_ERR, "Could not read from %s", proc_path);
goto bail;
}
exe_path[rc] = 0;
snprintf(proc_path, sizeof(proc_path), "/proc/%lu/exe", (long unsigned int)getpid());
rc = readlink(proc_path, myexe_path, PATH_MAX - 1);
if (rc < 0) {
crm_perror(LOG_ERR, "Could not read from %s", proc_path);
goto bail;
}
myexe_path[rc] = 0;
if (strcmp(exe_path, myexe_path) == 0) {
running = 1;
}
}
bail:
return running;
#endif
}
int
crm_read_pidfile(const char *filename)
{
int fd;
long pid = -1;
char buf[LOCKSTRLEN + 1];
if ((fd = open(filename, O_RDONLY)) < 0) {
goto bail;
}
if (read(fd, buf, sizeof(buf)) < 1) {
goto bail;
}
if (sscanf(buf, "%lu", &pid) > 0) {
if (pid <= 0) {
pid = -LSB_STATUS_STOPPED;
}
}
bail:
if (fd >= 0) {
close(fd);
}
return pid;
}
int
crm_lock_pidfile(const char *filename)
{
struct stat sbuf;
int fd = 0, rc = 0;
long pid = 0, mypid = 0;
char lf_name[256], tf_name[256], buf[LOCKSTRLEN + 1];
mypid = (unsigned long)getpid();
snprintf(lf_name, sizeof(lf_name), "%s", filename);
snprintf(tf_name, sizeof(tf_name), "%s.%lu", filename, mypid);
if ((fd = open(lf_name, O_RDONLY)) >= 0) {
if (fstat(fd, &sbuf) >= 0 && sbuf.st_size < LOCKSTRLEN) {
sleep(1); /* if someone was about to create one,
* give'm a sec to do so
* Though if they follow our protocol,
* this won't happen. They should really
* put the pid in, then link, not the
* other way around.
*/
}
if (read(fd, buf, sizeof(buf)) > 0) {
if (sscanf(buf, "%lu", &pid) > 0) {
if (pid > 1 && pid != getpid() && crm_pid_active(pid)) {
/* locked by existing process - give up */
close(fd);
return -1;
}
}
}
unlink(lf_name);
close(fd);
}
if ((fd = open(tf_name, O_CREAT | O_WRONLY | O_EXCL, 0644)) < 0) {
/* Hmmh, why did we fail? Anyway, nothing we can do about it */
return -3;
}
/* Slight overkill with the %*d format ;-) */
snprintf(buf, sizeof(buf), "%*lu\n", LOCKSTRLEN - 1, mypid);
if (write(fd, buf, LOCKSTRLEN) != LOCKSTRLEN) {
/* Again, nothing we can do about this */
rc = -3;
close(fd);
goto out;
}
close(fd);
switch (link(tf_name, lf_name)) {
case 0:
if (stat(tf_name, &sbuf) < 0) {
/* something weird happened */
rc = -3;
} else if (sbuf.st_nlink < 2) {
/* somehow, it didn't get through - NFS trouble? */
rc = -2;
} else {
rc = 0;
}
break;
case EEXIST:
rc = -1;
break;
default:
rc = -3;
}
out:
unlink(tf_name);
return rc;
}
void
crm_make_daemon(const char *name, gboolean daemonize, const char *pidfile)
{
long pid;
const char *devnull = "/dev/null";
if (daemonize == FALSE) {
return;
}
pid = fork();
if (pid < 0) {
fprintf(stderr, "%s: could not start daemon\n", name);
crm_perror(LOG_ERR, "fork");
exit(LSB_EXIT_GENERIC);
} else if (pid > 0) {
exit(LSB_EXIT_OK);
}
if (crm_lock_pidfile(pidfile) < 0) {
pid = crm_read_pidfile(pidfile);
if (crm_pid_active(pid) > 0) {
crm_warn("%s: already running [pid %ld] (%s).\n", name, pid, pidfile);
exit(LSB_EXIT_OK);
}
}
umask(022);
close(STDIN_FILENO);
(void)open(devnull, O_RDONLY); /* Stdin: fd 0 */
close(STDOUT_FILENO);
(void)open(devnull, O_WRONLY); /* Stdout: fd 1 */
close(STDERR_FILENO);
(void)open(devnull, O_WRONLY); /* Stderr: fd 2 */
}
gboolean
crm_is_writable(const char *dir, const char *file,
const char *user, const char *group, gboolean need_both)
{
int s_res = -1;
struct stat buf;
char *full_file = NULL;
const char *target = NULL;
gboolean pass = TRUE;
gboolean readwritable = FALSE;
CRM_ASSERT(dir != NULL);
if (file != NULL) {
full_file = crm_concat(dir, file, '/');
target = full_file;
s_res = stat(full_file, &buf);
if (s_res == 0 && S_ISREG(buf.st_mode) == FALSE) {
crm_err("%s must be a regular file", target);
pass = FALSE;
goto out;
}
}
if (s_res != 0) {
target = dir;
s_res = stat(dir, &buf);
if (s_res != 0) {
crm_err("%s must exist and be a directory", dir);
pass = FALSE;
goto out;
} else if (S_ISDIR(buf.st_mode) == FALSE) {
crm_err("%s must be a directory", dir);
pass = FALSE;
}
}
if (user) {
struct passwd *sys_user = NULL;
sys_user = getpwnam(user);
readwritable = (sys_user != NULL
&& buf.st_uid == sys_user->pw_uid && (buf.st_mode & (S_IRUSR | S_IWUSR)));
if (readwritable == FALSE) {
crm_err("%s must be owned and r/w by user %s", target, user);
if (need_both) {
pass = FALSE;
}
}
}
if (group) {
struct group *sys_grp = getgrnam(group);
readwritable = (sys_grp != NULL
&& buf.st_gid == sys_grp->gr_gid && (buf.st_mode & (S_IRGRP | S_IWGRP)));
if (readwritable == FALSE) {
if (need_both || user == NULL) {
pass = FALSE;
crm_err("%s must be owned and r/w by group %s", target, group);
} else {
crm_warn("%s should be owned and r/w by group %s", target, group);
}
}
}
out:
free(full_file);
return pass;
}
gboolean
crm_str_eq(const char *a, const char *b, gboolean use_case)
{
if (a == b) {
return TRUE;
} else if (a == NULL || b == NULL) {
/* shouldn't be comparing NULLs */
return FALSE;
} else if (use_case && a[0] != b[0]) {
return FALSE;
} else if (strcasecmp(a, b) == 0) {
return TRUE;
}
return FALSE;
}
char *
crm_meta_name(const char *field)
{
int lpc = 0;
int max = 0;
char *crm_name = NULL;
CRM_CHECK(field != NULL, return NULL);
crm_name = crm_concat(CRM_META, field, '_');
/* Massage the names so they can be used as shell variables */
max = strlen(crm_name);
for (; lpc < max; lpc++) {
switch (crm_name[lpc]) {
case '-':
crm_name[lpc] = '_';
break;
}
}
return crm_name;
}
const char *
crm_meta_value(GHashTable * hash, const char *field)
{
char *key = NULL;
const char *value = NULL;
key = crm_meta_name(field);
if (key) {
value = g_hash_table_lookup(hash, key);
free(key);
}
return value;
}
static struct crm_option *crm_long_options = NULL;
static const char *crm_app_description = NULL;
static const char *crm_short_options = NULL;
static const char *crm_app_usage = NULL;
static struct option *
crm_create_long_opts(struct crm_option *long_options)
{
struct option *long_opts = NULL;
#ifdef HAVE_GETOPT_H
int index = 0, lpc = 0;
/*
* A previous, possibly poor, choice of '?' as the short form of --help
* means that getopt_long() returns '?' for both --help and for "unknown option"
*
* This dummy entry allows us to differentiate between the two in crm_get_option()
* and exit with the correct error code
*/
crm_realloc(long_opts, (index + 1) * sizeof(struct option));
long_opts[index].name = "__dummmy__";
long_opts[index].has_arg = 0;
long_opts[index].flag = 0;
long_opts[index].val = '_';
index++;
for (lpc = 0; long_options[lpc].name != NULL; lpc++) {
if (long_options[lpc].name[0] == '-') {
continue;
}
crm_realloc(long_opts, (index + 1) * sizeof(struct option));
/*fprintf(stderr, "Creating %d %s = %c\n", index,
* long_options[lpc].name, long_options[lpc].val); */
long_opts[index].name = long_options[lpc].name;
long_opts[index].has_arg = long_options[lpc].has_arg;
long_opts[index].flag = long_options[lpc].flag;
long_opts[index].val = long_options[lpc].val;
index++;
}
/* Now create the list terminator */
crm_realloc(long_opts, (index + 1) * sizeof(struct option));
long_opts[index].name = NULL;
long_opts[index].has_arg = 0;
long_opts[index].flag = 0;
long_opts[index].val = 0;
#endif
return long_opts;
}
void
crm_set_options(const char *short_options, const char *app_usage, struct crm_option *long_options,
const char *app_desc)
{
if (short_options) {
crm_short_options = short_options;
} else if (long_options) {
int lpc = 0;
int opt_string_len = 0;
char *local_short_options = NULL;
for (lpc = 0; long_options[lpc].name != NULL; lpc++) {
if (long_options[lpc].val) {
crm_realloc(local_short_options, opt_string_len + 3);
local_short_options[opt_string_len++] = long_options[lpc].val;
if (long_options[lpc].has_arg == required_argument) {
local_short_options[opt_string_len++] = ':';
}
local_short_options[opt_string_len] = 0;
}
}
crm_short_options = local_short_options;
crm_trace("Generated short option string: '%s'", local_short_options);
}
if (long_options) {
crm_long_options = long_options;
}
if (app_desc) {
crm_app_description = app_desc;
}
if (app_usage) {
crm_app_usage = app_usage;
}
}
int
crm_get_option(int argc, char **argv, int *index)
{
#ifdef HAVE_GETOPT_H
static struct option *long_opts = NULL;
if (long_opts == NULL && crm_long_options) {
long_opts = crm_create_long_opts(crm_long_options);
}
if (long_opts) {
int flag = getopt_long(argc, argv, crm_short_options, long_opts, index);
switch (flag) {
case 0:
return long_opts[*index].val;
case -1: /* End of option processing */
break;
case ':':
crm_trace("Missing argument");
crm_help('?', 1);
break;
case '?':
crm_help('?', *index ? 0 : 1);
break;
}
return flag;
}
#endif
if (crm_short_options) {
return getopt(argc, argv, crm_short_options);
}
return -1;
}
void
crm_help(char cmd, int exit_code)
{
int i = 0;
FILE *stream = (exit_code ? stderr : stdout);
if (cmd == 'v' || cmd == '$') {
fprintf(stream, "Pacemaker %s\n", VERSION);
fprintf(stream, "Written by Andrew Beekhof\n");
goto out;
}
if (cmd == '!') {
fprintf(stream, "Pacemaker %s (Build: %s): %s\n", VERSION, BUILD_VERSION, CRM_FEATURES);
goto out;
}
fprintf(stream, "%s - %s\n", crm_system_name, crm_app_description);
if (crm_app_usage) {
fprintf(stream, "Usage: %s %s\n", crm_system_name, crm_app_usage);
}
if (crm_long_options) {
fprintf(stream, "Options:\n");
for (i = 0; crm_long_options[i].name != NULL; i++) {
if (crm_long_options[i].flags & pcmk_option_hidden) {
} else if (crm_long_options[i].flags & pcmk_option_paragraph) {
fprintf(stream, "%s\n\n", crm_long_options[i].desc);
} else if (crm_long_options[i].flags & pcmk_option_example) {
fprintf(stream, "\t#%s\n\n", crm_long_options[i].desc);
} else if (crm_long_options[i].val == '-' && crm_long_options[i].desc) {
fprintf(stream, "%s\n", crm_long_options[i].desc);
} else {
/* is val printable as char ? */
if (crm_long_options[i].val <= UCHAR_MAX) {
fprintf(stream, " -%c,", crm_long_options[i].val);
} else {
fputs(" ", stream);
}
fprintf(stream, " --%s%c%s\t%s\n", crm_long_options[i].name,
crm_long_options[i].has_arg ? '=' : ' ',
crm_long_options[i].has_arg ? "value" : "",
crm_long_options[i].desc ? crm_long_options[i].desc : "");
}
}
} else if (crm_short_options) {
fprintf(stream, "Usage: %s - %s\n", crm_system_name, crm_app_description);
for (i = 0; crm_short_options[i] != 0; i++) {
int has_arg = FALSE;
if (crm_short_options[i + 1] == ':') {
has_arg = TRUE;
}
fprintf(stream, " -%c %s\n", crm_short_options[i], has_arg ? "{value}" : "");
if (has_arg) {
i++;
}
}
}
fprintf(stream, "\nReport bugs to %s\n", PACKAGE_BUGREPORT);
out:
if (exit_code >= 0) {
exit(exit_code);
}
}
gboolean
attrd_update(crm_ipc_t *cluster, char command, const char *host, const char *name,
const char *value, const char *section, const char *set, const char *dampen)
{
return attrd_update_delegate(cluster, command, host, name, value, section, set, dampen, NULL);
}
gboolean
attrd_lazy_update(char command, const char *host, const char *name,
const char *value, const char *section, const char *set,
const char *dampen)
{
return attrd_update_delegate(NULL, command, host, name, value, section, set, dampen, NULL);
}
gboolean
attrd_update_no_mainloop(int *connection, char command, const char *host,
const char *name, const char *value, const char *section,
const char *set, const char *dampen)
{
return attrd_update_delegate(NULL, command, host, name, value, section, set, dampen, NULL);
}
gboolean
attrd_update_delegate(crm_ipc_t *ipc, char command, const char *host, const char *name,
const char *value, const char *section, const char *set, const char *dampen,
const char *user_name)
{
int rc = 0;
int max = 5;
xmlNode *update = create_xml_node(NULL, __FUNCTION__);
static gboolean connected = TRUE;
static crm_ipc_t *local_ipc = NULL;
if(ipc == NULL && local_ipc == NULL) {
local_ipc = crm_ipc_new(T_ATTRD, 0);
connected = FALSE;
}
if(ipc == NULL) {
ipc = local_ipc;
}
/* remap common aliases */
if (safe_str_eq(section, "reboot")) {
section = XML_CIB_TAG_STATUS;
} else if (safe_str_eq(section, "forever")) {
section = XML_CIB_TAG_NODES;
}
crm_xml_add(update, F_TYPE, T_ATTRD);
crm_xml_add(update, F_ORIG, crm_system_name);
if (name == NULL && command == 'U') {
command = 'R';
}
switch (command) {
case 'D':
case 'U':
case 'v':
crm_xml_add(update, F_ATTRD_TASK, "update");
crm_xml_add(update, F_ATTRD_ATTRIBUTE, name);
break;
case 'R':
crm_xml_add(update, F_ATTRD_TASK, "refresh");
break;
case 'q':
crm_xml_add(update, F_ATTRD_TASK, "query");
break;
}
crm_xml_add(update, F_ATTRD_VALUE, value);
crm_xml_add(update, F_ATTRD_DAMPEN, dampen);
crm_xml_add(update, F_ATTRD_SECTION, section);
crm_xml_add(update, F_ATTRD_HOST, host);
crm_xml_add(update, F_ATTRD_SET, set);
#if ENABLE_ACL
if (user_name) {
crm_xml_add(update, F_ATTRD_USER, user_name);
}
#endif
while (max > 0) {
if (connected == FALSE) {
crm_info("Connecting to cluster... %d retries remaining", max);
connected = crm_ipc_connect(ipc);
}
if(connected) {
rc = crm_ipc_send(ipc, update, NULL, 0);
}
if(ipc != local_ipc) {
break;
} else if (rc > 0) {
break;
} else {
crm_ipc_close(ipc);
connected = FALSE;
sleep(5-max);
max--;
}
}
free_xml(update);
if (rc > 0) {
crm_debug("Sent update: %s=%s for %s", name, value, host ? host : "localhost");
return TRUE;
}
crm_info("Could not send update: %s=%s for %s", name, value, host ? host : "localhost");
return FALSE;
}
#define FAKE_TE_ID "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
static void
append_digest(lrmd_event_data_t * op, xmlNode * update, const char *version, const char *magic, int level)
{
/* this will enable us to later determine that the
* resource's parameters have changed and we should force
* a restart
*/
char *digest = NULL;
xmlNode *args_xml = NULL;
if (op->params == NULL) {
return;
}
args_xml = create_xml_node(NULL, XML_TAG_PARAMS);
g_hash_table_foreach(op->params, hash2field, args_xml);
filter_action_parameters(args_xml, version);
digest = calculate_operation_digest(args_xml, version);
#if 0
if (level < get_crm_log_level()
&& op->interval == 0 && crm_str_eq(op->op_type, CRMD_ACTION_START, TRUE)) {
char *digest_source = dump_xml_unformatted(args_xml);
do_crm_log(level, "Calculated digest %s for %s (%s). Source: %s\n",
digest, ID(update), magic, digest_source);
free(digest_source);
}
#endif
crm_xml_add(update, XML_LRM_ATTR_OP_DIGEST, digest);
free_xml(args_xml);
free(digest);
}
int
rsc_op_expected_rc(lrmd_event_data_t * op)
{
int rc = 0;
if (op && op->user_data) {
int dummy = 0;
char *uuid = NULL;
decode_transition_key(op->user_data, &uuid, &dummy, &dummy, &rc);
free(uuid);
}
return rc;
}
gboolean
did_rsc_op_fail(lrmd_event_data_t * op, int target_rc)
{
switch (op->op_status) {
case PCMK_LRM_OP_CANCELLED:
case PCMK_LRM_OP_PENDING:
return FALSE;
break;
case PCMK_LRM_OP_NOTSUPPORTED:
case PCMK_LRM_OP_TIMEOUT:
case PCMK_LRM_OP_ERROR:
return TRUE;
break;
default:
if (target_rc != op->rc) {
return TRUE;
}
}
return FALSE;
}
xmlNode *
create_operation_update(xmlNode * parent, lrmd_event_data_t * op, const char *caller_version, int target_rc,
const char *origin, int level)
{
char *key = NULL;
char *magic = NULL;
char *op_id = NULL;
char *local_user_data = NULL;
xmlNode *xml_op = NULL;
const char *task = NULL;
gboolean dc_munges_migrate_ops = (compare_version(caller_version, "3.0.3") < 0);
gboolean dc_needs_unique_ops = (compare_version(caller_version, "3.0.6") < 0);
CRM_CHECK(op != NULL, return NULL);
do_crm_log(level, "%s: Updating resouce %s after %s %s op (interval=%d)",
origin, op->rsc_id, services_lrm_status_str(op->op_status), op->op_type, op->interval);
if (op->op_status == PCMK_LRM_OP_CANCELLED) {
crm_trace("Ignoring cancelled op");
return NULL;
}
crm_trace("DC version: %s", caller_version);
task = op->op_type;
/* remap the task name under various scenarios
* this makes life easier for the PE when its trying determin the current state
*/
if (crm_str_eq(task, "reload", TRUE)) {
if (op->op_status == PCMK_LRM_OP_DONE) {
task = CRMD_ACTION_START;
} else {
task = CRMD_ACTION_STATUS;
}
} else if (dc_munges_migrate_ops && crm_str_eq(task, CRMD_ACTION_MIGRATE, TRUE)) {
/* if the migrate_from fails it will have enough info to do the right thing */
if (op->op_status == PCMK_LRM_OP_DONE) {
task = CRMD_ACTION_STOP;
} else {
task = CRMD_ACTION_STATUS;
}
} else if (dc_munges_migrate_ops
&& op->op_status == PCMK_LRM_OP_DONE && crm_str_eq(task, CRMD_ACTION_MIGRATED, TRUE)) {
task = CRMD_ACTION_START;
}
key = generate_op_key(op->rsc_id, task, op->interval);
if (dc_needs_unique_ops && op->interval > 0) {
op_id = crm_strdup(key);
} else if (crm_str_eq(task, CRMD_ACTION_NOTIFY, TRUE)) {
const char *n_type = crm_meta_value(op->params, "notify_type");
const char *n_task = crm_meta_value(op->params, "notify_operation");
CRM_LOG_ASSERT(n_type != NULL);
CRM_LOG_ASSERT(n_task != NULL);
op_id = generate_notify_key(op->rsc_id, n_type, n_task);
/* these are not yet allowed to fail */
op->op_status = PCMK_LRM_OP_DONE;
op->rc = 0;
} else if (did_rsc_op_fail(op, target_rc)) {
op_id = generate_op_key(op->rsc_id, "last_failure", 0);
} else if (op->interval > 0) {
op_id = crm_strdup(key);
} else {
op_id = generate_op_key(op->rsc_id, "last", 0);
}
xml_op = find_entity(parent, XML_LRM_TAG_RSC_OP, op_id);
if (xml_op == NULL) {
xml_op = create_xml_node(parent, XML_LRM_TAG_RSC_OP);
}
if (op->user_data == NULL) {
crm_debug("Generating fake transition key for:"
" %s_%s_%d %d from %s",
op->rsc_id, op->op_type, op->interval, op->call_id);
local_user_data = generate_transition_key(-1, op->call_id, target_rc, FAKE_TE_ID);
op->user_data = local_user_data;
}
magic = generate_transition_magic(op->user_data, op->op_status, op->rc);
crm_xml_add(xml_op, XML_ATTR_ID, op_id);
crm_xml_add(xml_op, XML_LRM_ATTR_TASK_KEY, key);
crm_xml_add(xml_op, XML_LRM_ATTR_TASK, task);
crm_xml_add(xml_op, XML_ATTR_ORIGIN, origin);
crm_xml_add(xml_op, XML_ATTR_CRM_VERSION, caller_version);
crm_xml_add(xml_op, XML_ATTR_TRANSITION_KEY, op->user_data);
crm_xml_add(xml_op, XML_ATTR_TRANSITION_MAGIC, magic);
crm_xml_add_int(xml_op, XML_LRM_ATTR_CALLID, op->call_id);
crm_xml_add_int(xml_op, XML_LRM_ATTR_RC, op->rc);
crm_xml_add_int(xml_op, XML_LRM_ATTR_OPSTATUS, op->op_status);
crm_xml_add_int(xml_op, XML_LRM_ATTR_INTERVAL, op->interval);
if (compare_version("2.1", caller_version) <= 0) {
if (op->t_run || op->t_rcchange || op->exec_time || op->queue_time) {
crm_trace("Timing data (%s_%s_%d): last=%lu change=%lu exec=%lu queue=%lu",
op->rsc_id, op->op_type, op->interval,
op->t_run, op->t_rcchange, op->exec_time, op->queue_time);
if (op->interval == 0) {
crm_xml_add_int(xml_op, "last-run", op->t_run);
}
crm_xml_add_int(xml_op, "last-rc-change", op->t_rcchange);
crm_xml_add_int(xml_op, "exec-time", op->exec_time);
crm_xml_add_int(xml_op, "queue-time", op->queue_time);
}
}
if (crm_str_eq(op->op_type, CRMD_ACTION_MIGRATE, TRUE)
|| crm_str_eq(op->op_type, CRMD_ACTION_MIGRATED, TRUE)) {
/*
* Record migrate_source and migrate_target always for migrate ops.
*/
const char *name = XML_LRM_ATTR_MIGRATE_SOURCE;
crm_xml_add(xml_op, name, crm_meta_value(op->params, name));
name = XML_LRM_ATTR_MIGRATE_TARGET;
crm_xml_add(xml_op, name, crm_meta_value(op->params, name));
}
append_digest(op, xml_op, caller_version, magic, LOG_DEBUG);
if (local_user_data) {
free(local_user_data);
op->user_data = NULL;
}
free(magic);
free(op_id);
free(key);
return xml_op;
}
#if ENABLE_ACL
void
determine_request_user(char **user, IPC_Channel * channel, xmlNode * request, const char *field)
{
/* Get our internal validation out of the way first */
CRM_CHECK(user != NULL && channel != NULL && field != NULL, return);
if (*user == NULL) {
/* Figure out who our peer is and cache it... */
struct passwd *pwent = getpwuid(channel->farside_uid);
if (pwent == NULL) {
crm_perror(LOG_ERR, "Cannot get password entry of uid: %d", channel->farside_uid);
} else {
*user = crm_strdup(pwent->pw_name);
}
}
/* If our peer is a privileged user, we might be doing something on behalf of someone else */
if (is_privileged(*user) == FALSE) {
/* We're not a privileged user, set or overwrite any existing value for $field */
crm_xml_replace(request, field, *user);
} else if (crm_element_value(request, field) == NULL) {
/* Even if we're privileged, make sure there is always a value set */
crm_xml_replace(request, field, *user);
/* } else { Legal delegation */
}
crm_trace("Processing msg for user '%s'", crm_element_value(request, field));
}
#endif
/*
* This re-implements g_str_hash as it was prior to glib2-2.28:
*
* http://git.gnome.org/browse/glib/commit/?id=354d655ba8a54b754cb5a3efb42767327775696c
*
* Note that the new g_str_hash is presumably a *better* hash (it's actually
* a correct implementation of DJB's hash), but we need to preserve existing
* behaviour, because the hash key ultimately determines the "sort" order
* when iterating through GHashTables, which affects allocation of scores to
* clone instances when iterating through rsc->allowed_nodes. It (somehow)
* also appears to have some minor impact on the ordering of a few
* pseudo_event IDs in the transition graph.
*/
guint
g_str_hash_traditional(gconstpointer v)
{
const signed char *p;
guint32 h = 0;
for (p = v; *p != '\0'; p++)
h = (h << 5) - h + *p;
return h;
}
void *
find_library_function(void **handle, const char *lib, const char *fn)
{
char *error;
void *a_function;
if (*handle == NULL) {
*handle = dlopen(lib, RTLD_LAZY);
}
if (!(*handle)) {
crm_err("Could not open %s: %s", lib, dlerror());
exit(100);
}
a_function = dlsym(*handle, fn);
if ((error = dlerror()) != NULL) {
crm_err("Could not find %s in %s: %s", fn, lib, error);
exit(100);
}
return a_function;
}
void *
convert_const_pointer(const void *ptr)
{
/* Worst function ever */
return (void *)ptr;
}
#include <uuid/uuid.h>
char *crm_generate_uuid(void)
{
unsigned char uuid[16];
char *buffer = malloc(37); /* Including NUL byte */
uuid_generate(uuid);
uuid_unparse(uuid, buffer);
return buffer;
}
#include <md5.h>
char *
crm_md5sum(const char *buffer)
{
int lpc = 0;
char *digest = NULL;
unsigned char raw_digest[MD5_DIGEST_SIZE];
digest = malloc(2*MD5_DIGEST_SIZE + 1);
md5_buffer(buffer, strlen(buffer), raw_digest);
for(lpc = 0; lpc < MD5_DIGEST_SIZE; lpc++) {
sprintf(digest+(2*lpc), "%02x", raw_digest[lpc]);
}
digest[(2*MD5_DIGEST_SIZE)] = 0;
crm_trace("Digest %s\n", digest);
return digest;
}
diff --git a/mcp/pacemaker.sysconfig b/mcp/pacemaker.sysconfig
index fd40e2305b..046c6aaef7 100644
--- a/mcp/pacemaker.sysconfig
+++ b/mcp/pacemaker.sysconfig
@@ -1,60 +1,63 @@
# For non-systemd based systems, prefix export to each enabled line
#==#==# Variables that control logging
# Log all messages from a comma-separated list of functions
# PCMK_trace_functions=function1,function2,function3
# Log all messages from a comma-separated list of files (no path)
# Supports wildcards eg. PCMK_trace_files=prefix*.c
# PCMK_trace_files=file.c,other.h
# Log all messages matching comma-separated list of formats
# PCMK_trace_formats="Sent delete %d"
+# Log all messages from a comma-separated list of tags
+# PCMK_trace_tags=tag1,tag2
+
# Enable blackbox logging globally or per-subsystem
# Blackbox data is written after a crash, assertion failure and/or when SIGTRAP is recieved
# The blackbox recorder can also be enabled for Pacemaker daemons at runtime by sending SIGUSR1
#
# After the blackbox has been enabled, additional (very verbose) tracing can
# be turned on with SIGPROF
#
# Multiple subsystems may me listed separated by commas
# eg. PCMK_blackbox=crmd,pengine
# PCMK_blackbox=yes|no|crmd|pengine|cib|stonith-ng|attrd|pacemakerd
# Enable debug logging globally or per-subsystem
# Multiple subsystems may me listed separated by commas
# eg. PCMK_debug=crmd,pengine
# PCMK_debug=yes|no|crmd|pengine|cib|stonith-ng|attrd|pacemakerd
#==#==# Advanced use only
# Enable this for compatibility with older corosync (prior to 2.0)
# based clusters which used the nodes uname as its uuid also
# PCMK_uname_is_uuid=no
# Specify an alternate location for RNG schemas and XSL transforms
# Mostly only useful for developer testing
# PCMK_schema_directory=/some/path
#==#==# IPC
# Force use of a particular class of IPC connection
# PCMK_ipc_type=shared-mem|socket|posix|sysv
# Specify an IPC buffer size in bytes
# Useful when connecting to really big clusters that exceed the default 20k buffer
# PCMK_ipc_buffer=20480
#==#==# Profiling and memory leak testing
# Variables for running child daemons under valgrind and/or checking for memory problems
# G_SLICE=always-malloc
# MALLOC_PERTURB_=221 # or 0
# MALLOC_CHECK_=3 # or 0,1,2
# PCMK_valgrind_enabled=yes
# PCMK_valgrind_enabled=cib,crmd
# PCMK_callgrind_enabled=yes
# PCMK_callgrind_enabled=cib,crmd
# VALGRIND_OPTS="--leak-check=full --trace-children=no --num-callers=25 --log-file=/tmp/pacemaker-%p.valgrind"
diff --git a/pengine/ptest.c b/pengine/ptest.c
index 2572f7e296..e6d8bf354f 100644
--- a/pengine/ptest.c
+++ b/pengine/ptest.c
@@ -1,512 +1,510 @@
/*
* Copyright (C) 2004 Andrew Beekhof <andrew@beekhof.net>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
#include <crm_internal.h>
#include <crm/crm.h>
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdlib.h>
#include <errno.h>
#include <fcntl.h>
#include <crm/transition.h>
#include <crm/common/xml.h>
#include <crm/common/util.h>
#include <crm/msg_xml.h>
#include <crm/cib.h>
#include <glib.h>
#include <pengine.h>
#include <lib/pengine/utils.h>
#include <allocate.h>
#if HAVE_LIBXML2
# include <libxml/parser.h>
#endif
gboolean use_stdin = FALSE;
gboolean do_simulation = FALSE;
gboolean inhibit_exit = FALSE;
gboolean all_actions = FALSE;
extern xmlNode *do_calculations(pe_working_set_t * data_set, xmlNode * xml_input, ha_time_t * now);
extern void cleanup_calculations(pe_working_set_t * data_set);
char *use_date = NULL;
FILE *dot_strm = NULL;
#define DOT_PREFIX "PE_DOT: "
/* #define DOT_PREFIX "" */
#define dot_write(fmt...) if(dot_strm != NULL) { \
fprintf(dot_strm, fmt); \
fprintf(dot_strm, "\n"); \
} else { \
crm_debug(DOT_PREFIX""fmt); \
}
static void
init_dotfile(void)
{
dot_write(" digraph \"g\" {");
/* dot_write(" size = \"30,30\""); */
/* dot_write(" graph ["); */
/* dot_write(" fontsize = \"12\""); */
/* dot_write(" fontname = \"Times-Roman\""); */
/* dot_write(" fontcolor = \"black\""); */
/* dot_write(" bb = \"0,0,398.922306,478.927856\""); */
/* dot_write(" color = \"black\""); */
/* dot_write(" ]"); */
/* dot_write(" node ["); */
/* dot_write(" fontsize = \"12\""); */
/* dot_write(" fontname = \"Times-Roman\""); */
/* dot_write(" fontcolor = \"black\""); */
/* dot_write(" shape = \"ellipse\""); */
/* dot_write(" color = \"black\""); */
/* dot_write(" ]"); */
/* dot_write(" edge ["); */
/* dot_write(" fontsize = \"12\""); */
/* dot_write(" fontname = \"Times-Roman\""); */
/* dot_write(" fontcolor = \"black\""); */
/* dot_write(" color = \"black\""); */
/* dot_write(" ]"); */
}
static char *
create_action_name(action_t * action)
{
char *action_name = NULL;
const char *action_host = NULL;
if (action->node) {
action_host = action->node->details->uname;
action_name = crm_concat(action->uuid, action_host, ' ');
} else if (is_set(action->flags, pe_action_pseudo)) {
action_name = crm_strdup(action->uuid);
} else {
action_host = "<none>";
action_name = crm_concat(action->uuid, action_host, ' ');
}
if (safe_str_eq(action->task, RSC_CANCEL)) {
char *tmp_action_name = action_name;
action_name = crm_concat("Cancel", tmp_action_name, ' ');
free(tmp_action_name);
}
return action_name;
}
gboolean USE_LIVE_CIB = FALSE;
/* *INDENT-OFF* */
static struct crm_option long_options[] = {
/* Top-level Options */
{"help", 0, 0, '?', "This text"},
{"version", 0, 0, '$', "Version information" },
{"verbose", 0, 0, 'V', "Increase debug output\n"},
{"simulate", 0, 0, 'S', "Simulate the transition's execution to find invalid graphs\n"},
{"show-scores", 0, 0, 's', "Display resource allocation scores"},
{"show-utilization", 0, 0, 'U', "Display utilization information"},
{"all-actions", 0, 0, 'a', "Display all possible actions - even ones not part of the transition graph"},
{"live-check", 0, 0, 'L', "Connect to the CIB and use the current contents as input"},
{"xml-text", 1, 0, 'X', "Retrieve XML from the supplied string"},
{"xml-file", 1, 0, 'x', "Retrieve XML from the named file"},
/* {"xml-pipe", 0, 0, 'p', "Retrieve XML from stdin\n"}, */
{"save-input", 1, 0, 'I', "\tSave the input to the named file"},
{"save-graph", 1, 0, 'G', "\tSave the transition graph (XML format) to the named file"},
{"save-dotfile",1, 0, 'D', "Save the transition graph (DOT format) to the named file\n"},
{0, 0, 0, 0}
};
/* *INDENT-ON* */
int
main(int argc, char **argv)
{
GListPtr lpc = NULL;
gboolean process = TRUE;
gboolean all_good = TRUE;
enum transition_status graph_rc = -1;
crm_graph_t *transition = NULL;
ha_time_t *a_date = NULL;
cib_t *cib_conn = NULL;
xmlNode *cib_object = NULL;
int argerr = 0;
int flag;
char *msg_buffer = NULL;
gboolean optional = FALSE;
pe_working_set_t data_set;
const char *source = NULL;
const char *xml_file = NULL;
const char *dot_file = NULL;
const char *graph_file = NULL;
const char *input_file = NULL;
const char *input_xml = NULL;
/* disable glib's fancy allocators that can't be free'd */
GMemVTable vtable;
vtable.malloc = malloc;
vtable.realloc = realloc;
vtable.free = free;
vtable.calloc = calloc;
vtable.try_malloc = malloc;
vtable.try_realloc = realloc;
g_mem_set_vtable(&vtable);
crm_log_cli_init("ptest");
crm_set_options(NULL, "[-?Vv] -[Xxp] {other options}", long_options,
"Calculate the cluster's response to the supplied cluster state\n"
"\nSuperceeded by crm_simulate and likely to be removed in a future release\n\n");
while (1) {
int option_index = 0;
flag = crm_get_option(argc, argv, &option_index);
if (flag == -1)
break;
switch (flag) {
case 'S':
do_simulation = TRUE;
break;
case 'a':
all_actions = TRUE;
break;
case 'w':
inhibit_exit = TRUE;
break;
case 'X':
/*use_stdin = TRUE;*/
input_xml = optarg;
break;
case 's':
show_scores = TRUE;
break;
case 'U':
show_utilization = TRUE;
break;
case 'x':
xml_file = optarg;
break;
case 'd':
use_date = optarg;
break;
case 'D':
dot_file = optarg;
break;
case 'G':
graph_file = optarg;
break;
case 'I':
input_file = optarg;
break;
case 'V':
crm_bump_log_level();
break;
case 'L':
USE_LIVE_CIB = TRUE;
break;
case '$':
case '?':
crm_help(flag, 0);
break;
default:
fprintf(stderr, "Option -%c is not yet supported\n", flag);
++argerr;
break;
}
}
if (optind < argc) {
printf("non-option ARGV-elements: ");
while (optind < argc) {
printf("%s ", argv[optind++]);
}
printf("\n");
}
if (optind > argc) {
++argerr;
}
if (argerr) {
crm_err("%d errors in option parsing", argerr);
crm_help('?', 1);
}
- update_all_trace_data(); /* again, so we see which trace points got updated */
-
if (USE_LIVE_CIB) {
int rc = cib_ok;
source = "live cib";
cib_conn = cib_new();
rc = cib_conn->cmds->signon(cib_conn, "ptest", cib_command);
if (rc == cib_ok) {
crm_info("Reading XML from: live cluster");
cib_object = get_cib_copy(cib_conn);
} else {
fprintf(stderr, "Live CIB query failed: %s\n", cib_error2string(rc));
return 3;
}
if (cib_object == NULL) {
fprintf(stderr, "Live CIB query failed: empty result\n");
return 3;
}
} else if (xml_file != NULL) {
source = xml_file;
cib_object = filename2xml(xml_file);
} else if (use_stdin) {
source = "stdin";
cib_object = filename2xml(NULL);
} else if (input_xml) {
source = "input string";
cib_object = string2xml(input_xml);
}
if (cib_object == NULL && source) {
fprintf(stderr, "Could not parse configuration input from: %s\n", source);
return 4;
} else if (cib_object == NULL) {
fprintf(stderr, "No configuration specified\n");
crm_help('?', 1);
}
if (get_object_root(XML_CIB_TAG_STATUS, cib_object) == NULL) {
create_xml_node(cib_object, XML_CIB_TAG_STATUS);
}
if (cli_config_update(&cib_object, NULL, FALSE) == FALSE) {
free_xml(cib_object);
return cib_STALE;
}
if (validate_xml(cib_object, NULL, FALSE) != TRUE) {
free_xml(cib_object);
return cib_dtd_validation;
}
if (input_file != NULL) {
FILE *input_strm = fopen(input_file, "w");
if (input_strm == NULL) {
crm_perror(LOG_ERR, "Could not open %s for writing", input_file);
} else {
msg_buffer = dump_xml_formatted(cib_object);
if (fprintf(input_strm, "%s\n", msg_buffer) < 0) {
crm_perror(LOG_ERR, "Write to %s failed", input_file);
}
fflush(input_strm);
fclose(input_strm);
free(msg_buffer);
}
}
if (use_date != NULL) {
a_date = parse_date(&use_date);
log_date(LOG_WARNING, "Set fake 'now' to", a_date, ha_log_date | ha_log_time);
log_date(LOG_WARNING, "Set fake 'now' to (localtime)",
a_date, ha_log_date | ha_log_time | ha_log_local);
}
set_working_set_defaults(&data_set);
if (process) {
if (show_scores && show_utilization) {
fprintf(stdout, "Allocation scores and utilization information:\n");
} else if (show_scores) {
fprintf(stdout, "Allocation scores:\n");
} else if (show_utilization) {
fprintf(stdout, "Utilization information:\n");
}
do_calculations(&data_set, cib_object, a_date);
}
msg_buffer = dump_xml_formatted(data_set.graph);
if (safe_str_eq(graph_file, "-")) {
fprintf(stdout, "%s\n", msg_buffer);
fflush(stdout);
} else if (graph_file != NULL) {
FILE *graph_strm = fopen(graph_file, "w");
if (graph_strm == NULL) {
crm_perror(LOG_ERR, "Could not open %s for writing", graph_file);
} else {
if (fprintf(graph_strm, "%s\n\n", msg_buffer) < 0) {
crm_perror(LOG_ERR, "Write to %s failed", graph_file);
}
fflush(graph_strm);
fclose(graph_strm);
}
}
free(msg_buffer);
if (dot_file != NULL) {
dot_strm = fopen(dot_file, "w");
if (dot_strm == NULL) {
crm_perror(LOG_ERR, "Could not open %s for writing", dot_file);
}
}
if (dot_strm == NULL) {
goto simulate;
}
init_dotfile();
for (lpc = data_set.actions; lpc != NULL; lpc = lpc->next) {
action_t *action = (action_t *) lpc->data;
const char *style = "filled";
const char *font = "black";
const char *color = "black";
const char *fill = NULL;
char *action_name = create_action_name(action);
crm_trace("Action %d: %p", action->id, action);
if (is_set(action->flags, pe_action_pseudo)) {
font = "orange";
}
style = "dashed";
if (is_set(action->flags, pe_action_dumped)) {
style = "bold";
color = "green";
} else if (action->rsc != NULL && is_not_set(action->rsc->flags, pe_rsc_managed)) {
color = "purple";
if (all_actions == FALSE) {
goto dont_write;
}
} else if (is_set(action->flags, pe_action_optional)) {
color = "blue";
if (all_actions == FALSE) {
goto dont_write;
}
} else {
color = "red";
CRM_CHECK(is_set(action->flags, pe_action_runnable) == FALSE,;
);
}
set_bit_inplace(action->flags, pe_action_dumped);
dot_write("\"%s\" [ style=%s color=\"%s\" fontcolor=\"%s\" %s%s]",
action_name, style, color, font, fill ? "fillcolor=" : "", fill ? fill : "");
dont_write:
free(action_name);
}
for (lpc = data_set.actions; lpc != NULL; lpc = lpc->next) {
action_t *action = (action_t *) lpc->data;
GListPtr lpc2 = NULL;
for (lpc2 = action->actions_before; lpc2 != NULL; lpc2 = lpc2->next) {
action_wrapper_t *before = (action_wrapper_t *) lpc2->data;
char *before_name = NULL;
char *after_name = NULL;
const char *style = "dashed";
optional = TRUE;
if (before->state == pe_link_dumped) {
optional = FALSE;
style = "bold";
} else if (is_set(action->flags, pe_action_pseudo)
&& (before->type & pe_order_stonith_stop)) {
continue;
} else if (before->state == pe_link_dup) {
continue;
} else if (before->type == pe_order_none) {
continue;
} else if (is_set(before->action->flags, pe_action_dumped)
&& is_set(action->flags, pe_action_dumped)) {
optional = FALSE;
}
if (all_actions || optional == FALSE) {
before_name = create_action_name(before->action);
after_name = create_action_name(action);
dot_write("\"%s\" -> \"%s\" [ style = %s]", before_name, after_name, style);
free(before_name);
free(after_name);
}
}
}
dot_write("}");
if (dot_strm != NULL) {
fflush(dot_strm);
fclose(dot_strm);
}
simulate:
if (do_simulation == FALSE) {
goto cleanup;
}
transition = unpack_graph(data_set.graph, "ptest");
print_graph(LOG_DEBUG, transition);
do {
graph_rc = run_graph(transition);
} while (graph_rc == transition_active);
if (graph_rc != transition_complete) {
crm_crit("Transition failed: %s", transition_status(graph_rc));
print_graph(LOG_ERR, transition);
}
destroy_graph(transition);
CRM_CHECK(graph_rc == transition_complete, all_good = FALSE;
crm_err("An invalid transition was produced"));
cleanup:
cleanup_alloc_calculations(&data_set);
crm_log_deinit();
/* required for MallocDebug.app */
if (inhibit_exit) {
GMainLoop *mainloop = g_main_new(FALSE);
g_main_run(mainloop);
}
if (all_good) {
return 0;
}
return graph_rc;
}
diff --git a/tools/crm_inject.c b/tools/crm_inject.c
index fd9e4bd32d..3ba2b2c677 100644
--- a/tools/crm_inject.c
+++ b/tools/crm_inject.c
@@ -1,1490 +1,1488 @@
/*
* Copyright (C) 2009 Andrew Beekhof <andrew@beekhof.net>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
#include <crm_internal.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/param.h>
#include <sys/types.h>
#include <dirent.h>
#include <crm/crm.h>
#include <crm/cib.h>
#include <crm/common/util.h>
#include <crm/transition.h>
#include <crm/common/iso8601.h>
#include <crm/pengine/status.h>
#include <allocate.h>
cib_t *global_cib = NULL;
GListPtr op_fail = NULL;
gboolean quiet = FALSE;
#define new_node_template "//"XML_CIB_TAG_NODE"[@uname='%s']"
#define node_template "//"XML_CIB_TAG_STATE"[@uname='%s']"
#define rsc_template "//"XML_CIB_TAG_STATE"[@uname='%s']//"XML_LRM_TAG_RESOURCE"[@id='%s']"
#define op_template "//"XML_CIB_TAG_STATE"[@uname='%s']//"XML_LRM_TAG_RESOURCE"[@id='%s']/"XML_LRM_TAG_RSC_OP"[@id='%s']"
/* #define op_template "//"XML_CIB_TAG_STATE"[@uname='%s']//"XML_LRM_TAG_RESOURCE"[@id='%s']/"XML_LRM_TAG_RSC_OP"[@id='%s' and @"XML_LRM_ATTR_CALLID"='%d']" */
#define FAKE_TE_ID "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
#define quiet_log(fmt, args...) do { \
if(quiet == FALSE) { \
printf(fmt , ##args); \
} \
} while(0)
extern void cleanup_alloc_calculations(pe_working_set_t * data_set);
extern xmlNode *do_calculations(pe_working_set_t * data_set, xmlNode * xml_input, ha_time_t * now);
char *use_date = NULL;
static ha_time_t *
get_date(void)
{
if (use_date) {
char *date_m = use_date;
return parse_date(&date_m);
}
return NULL;
}
static xmlNode *
find_resource(xmlNode * cib_node, const char *resource)
{
char *xpath = NULL;
xmlNode *match = NULL;
const char *node = crm_element_value(cib_node, XML_ATTR_UNAME);
int max = strlen(rsc_template) + strlen(resource) + strlen(node) + 1;
xpath = calloc(1, max);
snprintf(xpath, max, rsc_template, node, resource);
match = get_xpath_object(xpath, cib_node, LOG_DEBUG_2);
free(xpath);
return match;
}
static void
create_node_entry(cib_t * cib_conn, char *node)
{
int rc = cib_ok;
int max = strlen(new_node_template) + strlen(node) + 1;
char *xpath = NULL;
xpath = calloc(1, max);
snprintf(xpath, max, new_node_template, node);
rc = cib_conn->cmds->query(cib_conn, xpath, NULL, cib_xpath | cib_sync_call | cib_scope_local);
if (rc == cib_NOTEXISTS) {
xmlNode *cib_object = create_xml_node(NULL, XML_CIB_TAG_NODE);
/* Using node uname as uuid ala corosync/openais */
crm_xml_add(cib_object, XML_ATTR_ID, node);
crm_xml_add(cib_object, XML_ATTR_UNAME, node);
crm_xml_add(cib_object, XML_ATTR_TYPE, NORMALNODE);
cib_conn->cmds->create(cib_conn, XML_CIB_TAG_NODES, cib_object,
cib_sync_call | cib_scope_local);
/* Not bothering with subsequent query to see if it exists,
we'll bomb out later in the call to determine_host... */
free_xml(cib_object);
}
free(xpath);
}
static xmlNode *
inject_node_state(cib_t * cib_conn, char *node)
{
int rc = cib_ok;
int max = strlen(rsc_template) + strlen(node) + 1;
char *xpath = NULL;
xmlNode *cib_object = NULL;
xpath = calloc(1, max);
create_node_entry(cib_conn, node);
snprintf(xpath, max, node_template, node);
rc = cib_conn->cmds->query(cib_conn, xpath, &cib_object,
cib_xpath | cib_sync_call | cib_scope_local);
if(cib_object && ID(cib_object) == NULL) {
crm_err("Detected multiple node_state entries for xpath=%s, bailing", xpath);
crm_log_xml_warn(cib_object, "Duplicates");
exit(1);
}
if (rc == cib_NOTEXISTS) {
char *uuid = NULL;
cib_object = create_xml_node(NULL, XML_CIB_TAG_STATE);
determine_host(cib_conn, &node, &uuid);
crm_xml_add(cib_object, XML_ATTR_UUID, uuid);
crm_xml_add(cib_object, XML_ATTR_UNAME, node);
cib_conn->cmds->create(cib_conn, XML_CIB_TAG_STATUS, cib_object,
cib_sync_call | cib_scope_local);
free_xml(cib_object);
free(uuid);
rc = cib_conn->cmds->query(cib_conn, xpath, &cib_object,
cib_xpath | cib_sync_call | cib_scope_local);
}
free(xpath);
CRM_ASSERT(rc == cib_ok);
return cib_object;
}
static xmlNode *
modify_node(cib_t * cib_conn, char *node, gboolean up)
{
xmlNode *cib_node = inject_node_state(cib_conn, node);
if (up) {
crm_xml_add(cib_node, XML_CIB_ATTR_HASTATE, ACTIVESTATUS);
crm_xml_add(cib_node, XML_CIB_ATTR_INCCM, XML_BOOLEAN_YES);
crm_xml_add(cib_node, XML_CIB_ATTR_CRMDSTATE, ONLINESTATUS);
crm_xml_add(cib_node, XML_CIB_ATTR_JOINSTATE, CRMD_JOINSTATE_MEMBER);
crm_xml_add(cib_node, XML_CIB_ATTR_EXPSTATE, CRMD_JOINSTATE_MEMBER);
} else {
crm_xml_add(cib_node, XML_CIB_ATTR_HASTATE, DEADSTATUS);
crm_xml_add(cib_node, XML_CIB_ATTR_INCCM, XML_BOOLEAN_NO);
crm_xml_add(cib_node, XML_CIB_ATTR_CRMDSTATE, OFFLINESTATUS);
crm_xml_add(cib_node, XML_CIB_ATTR_JOINSTATE, CRMD_JOINSTATE_DOWN);
crm_xml_add(cib_node, XML_CIB_ATTR_EXPSTATE, CRMD_JOINSTATE_DOWN);
}
crm_xml_add(cib_node, XML_ATTR_ORIGIN, crm_system_name);
return cib_node;
}
static void
inject_transient_attr(xmlNode * cib_node, const char *name, const char *value)
{
xmlNode *attrs = NULL;
xmlNode *container = NULL;
xmlNode *nvp = NULL;
const char *node_uuid = ID(cib_node);
char *nvp_id = crm_concat(name, node_uuid, '-');
crm_info("Injecting attribute %s=%s into %s '%s'", name, value, xmlGetNodePath(cib_node),
ID(cib_node));
attrs = first_named_child(cib_node, XML_TAG_TRANSIENT_NODEATTRS);
if (attrs == NULL) {
attrs = create_xml_node(cib_node, XML_TAG_TRANSIENT_NODEATTRS);
crm_xml_add(attrs, XML_ATTR_ID, node_uuid);
}
container = first_named_child(attrs, XML_TAG_ATTR_SETS);
if (container == NULL) {
container = create_xml_node(attrs, XML_TAG_ATTR_SETS);
crm_xml_add(container, XML_ATTR_ID, node_uuid);
}
nvp = create_xml_node(container, XML_CIB_TAG_NVPAIR);
crm_xml_add(nvp, XML_ATTR_ID, nvp_id);
crm_xml_add(nvp, XML_NVPAIR_ATTR_NAME, name);
crm_xml_add(nvp, XML_NVPAIR_ATTR_VALUE, value);
free(nvp_id);
}
static xmlNode *
inject_resource(xmlNode * cib_node, const char *resource, const char *rclass, const char *rtype,
const char *rprovider)
{
xmlNode *lrm = NULL;
xmlNode *container = NULL;
xmlNode *cib_resource = NULL;
char *xpath = NULL;
cib_resource = find_resource(cib_node, resource);
if (cib_resource != NULL) {
return cib_resource;
}
/* One day, add query for class, provider, type */
if (rclass == NULL || rtype == NULL) {
fprintf(stderr, "Resource %s not found in the status section of %s."
" Please supply the class and type to continue\n", resource, ID(cib_node));
return NULL;
} else if (safe_str_neq(rclass, "ocf")
&& safe_str_neq(rclass, "stonith")
&& safe_str_neq(rclass, "heartbeat")
&& safe_str_neq(rclass, "lsb")) {
fprintf(stderr, "Invalid class for %s: %s\n", resource, rclass);
return NULL;
} else if (safe_str_eq(rclass, "ocf") && rprovider == NULL) {
fprintf(stderr, "Please specify the provider for resource %s\n", resource);
return NULL;
}
xpath = (char *)xmlGetNodePath(cib_node);
crm_info("Injecting new resource %s into %s '%s'", resource, xpath, ID(cib_node));
free(xpath);
lrm = first_named_child(cib_node, XML_CIB_TAG_LRM);
if (lrm == NULL) {
const char *node_uuid = ID(cib_node);
lrm = create_xml_node(cib_node, XML_CIB_TAG_LRM);
crm_xml_add(lrm, XML_ATTR_ID, node_uuid);
}
container = first_named_child(lrm, XML_LRM_TAG_RESOURCES);
if (container == NULL) {
container = create_xml_node(lrm, XML_LRM_TAG_RESOURCES);
}
cib_resource = create_xml_node(container, XML_LRM_TAG_RESOURCE);
crm_xml_add(cib_resource, XML_ATTR_ID, resource);
crm_xml_add(cib_resource, XML_AGENT_ATTR_CLASS, rclass);
crm_xml_add(cib_resource, XML_AGENT_ATTR_PROVIDER, rprovider);
crm_xml_add(cib_resource, XML_ATTR_TYPE, rtype);
return cib_resource;
}
static lrmd_event_data_t *
create_op(xmlNode * cib_resource, const char *task, int interval, int outcome)
{
lrmd_event_data_t *op = NULL;
xmlNode *xop = NULL;
op = calloc(1, sizeof(lrmd_event_data_t));
op->rsc_id = crm_strdup(ID(cib_resource));
op->interval = interval;
op->op_type = crm_strdup(task);
op->rc = outcome;
op->op_status = 0;
op->params = NULL; /* TODO: Fill me in */
op->call_id = 0;
for (xop = __xml_first_child(cib_resource); xop != NULL; xop = __xml_next(xop)) {
int tmp = 0;
crm_element_value_int(xop, XML_LRM_ATTR_CALLID, &tmp);
if (tmp > op->call_id) {
op->call_id = tmp;
}
}
op->call_id++;
return op;
}
static xmlNode *
inject_op(xmlNode * cib_resource, lrmd_event_data_t * op, int target_rc)
{
return create_operation_update(cib_resource, op, CRM_FEATURE_SET, target_rc, crm_system_name,
LOG_DEBUG_2);
}
static void
update_failcounts(xmlNode * cib_node, const char *resource, int interval, int rc)
{
if (rc == 0) {
return;
} else if (rc == 7 && interval == 0) {
return;
} else {
char *name = NULL;
char *now = crm_itoa(time(NULL));
name = crm_concat("fail-count", resource, '-');
inject_transient_attr(cib_node, name, "value++");
name = crm_concat("last-failure", resource, '-');
inject_transient_attr(cib_node, name, now);
free(name);
free(now);
}
}
static gboolean
exec_pseudo_action(crm_graph_t * graph, crm_action_t * action)
{
const char *node = crm_element_value(action->xml, XML_LRM_ATTR_TARGET);
const char *task = crm_element_value(action->xml, XML_LRM_ATTR_TASK_KEY);
action->confirmed = TRUE;
quiet_log(" * Pseudo action: %s%s%s\n", task, node?" on ":"", node?node:"");
update_graph(graph, action);
return TRUE;
}
static gboolean
exec_rsc_action(crm_graph_t * graph, crm_action_t * action)
{
int rc = 0;
GListPtr gIter = NULL;
lrmd_event_data_t *op = NULL;
int target_outcome = 0;
const char *rtype = NULL;
const char *rclass = NULL;
const char *resource = NULL;
const char *rprovider = NULL;
const char *target_rc_s = crm_meta_value(action->params, XML_ATTR_TE_TARGET_RC);
xmlNode *cib_node = NULL;
xmlNode *cib_resource = NULL;
xmlNode *action_rsc = first_named_child(action->xml, XML_CIB_TAG_RESOURCE);
char *node = crm_element_value_copy(action->xml, XML_LRM_ATTR_TARGET);
if (safe_str_eq(crm_element_value(action->xml, "operation"), "probe_complete")) {
crm_info("Skipping %s op for %s\n", crm_element_value(action->xml, "operation"), node);
goto done;
}
if (action_rsc == NULL) {
crm_log_xml_err(action->xml, "Bad");
free(node);
return FALSE;
}
resource = ID(action_rsc);
rclass = crm_element_value(action_rsc, XML_AGENT_ATTR_CLASS);
rtype = crm_element_value(action_rsc, XML_ATTR_TYPE);
rprovider = crm_element_value(action_rsc, XML_AGENT_ATTR_PROVIDER);
if (target_rc_s != NULL) {
target_outcome = crm_parse_int(target_rc_s, "0");
}
CRM_ASSERT(global_cib->cmds->query(global_cib, NULL, NULL, cib_sync_call | cib_scope_local) ==
cib_ok);
cib_node = inject_node_state(global_cib, node);
CRM_ASSERT(cib_node != NULL);
cib_resource = inject_resource(cib_node, resource, rclass, rtype, rprovider);
CRM_ASSERT(cib_resource != NULL);
op = convert_graph_action(cib_resource, action, 0, target_outcome);
if(op->interval) {
quiet_log(" * Resource action: %-15s %s=%d on %s\n", resource, op->op_type, op->interval, node);
} else {
quiet_log(" * Resource action: %-15s %s on %s\n", resource, op->op_type, node);
}
for (gIter = op_fail; gIter != NULL; gIter = gIter->next) {
char *spec = (char *)gIter->data;
char *key = NULL;
key = calloc(1, 1 + strlen(spec));
snprintf(key, strlen(spec), "%s_%s_%d@%s=", resource, op->op_type, op->interval, node);
if (strncasecmp(key, spec, strlen(key)) == 0) {
rc = sscanf(spec, "%*[^=]=%d", (int *) &op->rc);
action->failed = TRUE;
graph->abort_priority = INFINITY;
printf("\tPretending action %d failed with rc=%d\n", action->id, op->rc);
update_failcounts(cib_node, resource, op->interval, op->rc);
free(key);
break;
}
free(key);
}
inject_op(cib_resource, op, target_outcome);
lrmd_free_event(op);
rc = global_cib->cmds->modify(global_cib, XML_CIB_TAG_STATUS, cib_node,
cib_sync_call | cib_scope_local);
CRM_ASSERT(rc == cib_ok);
done:
free(node);
free_xml(cib_node);
action->confirmed = TRUE;
update_graph(graph, action);
return TRUE;
}
static gboolean
exec_crmd_action(crm_graph_t * graph, crm_action_t * action)
{
const char *node = crm_element_value(action->xml, XML_LRM_ATTR_TARGET);
const char *task = crm_element_value(action->xml, XML_LRM_ATTR_TASK);
action->confirmed = TRUE;
quiet_log(" * Cluster action: %s on %s\n", task, node);
update_graph(graph, action);
return TRUE;
}
#define STATUS_PATH_MAX 512
static gboolean
exec_stonith_action(crm_graph_t * graph, crm_action_t * action)
{
int rc = 0;
char xpath[STATUS_PATH_MAX];
char *target = crm_element_value_copy(action->xml, XML_LRM_ATTR_TARGET);
xmlNode *cib_node = modify_node(global_cib, target, FALSE);
crm_xml_add(cib_node, XML_ATTR_ORIGIN, __FUNCTION__);
CRM_ASSERT(cib_node != NULL);
quiet_log(" * Fencing %s\n", target);
rc = global_cib->cmds->replace(global_cib, XML_CIB_TAG_STATUS, cib_node,
cib_sync_call | cib_scope_local);
CRM_ASSERT(rc == cib_ok);
snprintf(xpath, STATUS_PATH_MAX, "//node_state[@uname='%s']/%s", target, XML_CIB_TAG_LRM);
rc = global_cib->cmds->delete(global_cib, xpath, NULL,
cib_xpath | cib_sync_call | cib_scope_local);
snprintf(xpath, STATUS_PATH_MAX, "//node_state[@uname='%s']/%s", target,
XML_TAG_TRANSIENT_NODEATTRS);
rc = global_cib->cmds->delete(global_cib, xpath, NULL,
cib_xpath | cib_sync_call | cib_scope_local);
action->confirmed = TRUE;
update_graph(graph, action);
free_xml(cib_node);
free(target);
return TRUE;
}
static char *
add_list_element(char *list, const char *value)
{
int len = 0;
int last = 0;
if (value == NULL) {
return list;
}
if (list) {
last = strlen(list);
}
len = last + 2; /* +1 space, +1 EOS */
len += strlen(value);
crm_realloc(list, len);
sprintf(list + last, " %s", value);
return list;
}
static void
print_cluster_status(pe_working_set_t * data_set)
{
char *online_nodes = NULL;
char *offline_nodes = NULL;
GListPtr gIter = NULL;
for (gIter = data_set->nodes; gIter != NULL; gIter = gIter->next) {
node_t *node = (node_t *) gIter->data;
const char *node_mode = NULL;
if (node->details->unclean) {
if (node->details->online && node->details->unclean) {
node_mode = "UNCLEAN (online)";
} else if (node->details->pending) {
node_mode = "UNCLEAN (pending)";
} else {
node_mode = "UNCLEAN (offline)";
}
} else if (node->details->pending) {
node_mode = "pending";
} else if (node->details->standby_onfail && node->details->online) {
node_mode = "standby (on-fail)";
} else if (node->details->standby) {
if (node->details->online) {
node_mode = "standby";
} else {
node_mode = "OFFLINE (standby)";
}
} else if (node->details->online) {
node_mode = "online";
online_nodes = add_list_element(online_nodes, node->details->uname);
continue;
} else {
node_mode = "OFFLINE";
offline_nodes = add_list_element(offline_nodes, node->details->uname);
continue;
}
if (safe_str_eq(node->details->uname, node->details->id)) {
printf("Node %s: %s\n", node->details->uname, node_mode);
} else {
printf("Node %s (%s): %s\n", node->details->uname, node->details->id, node_mode);
}
}
if (online_nodes) {
printf("Online: [%s ]\n", online_nodes);
free(online_nodes);
}
if (offline_nodes) {
printf("OFFLINE: [%s ]\n", offline_nodes);
free(offline_nodes);
}
fprintf(stdout, "\n");
for (gIter = data_set->resources; gIter != NULL; gIter = gIter->next) {
resource_t *rsc = (resource_t *) gIter->data;
if (is_set(rsc->flags, pe_rsc_orphan)
&& rsc->role == RSC_ROLE_STOPPED) {
continue;
}
rsc->fns->print(rsc, NULL, pe_print_printf, stdout);
}
fprintf(stdout, "\n");
}
static int
run_simulation(pe_working_set_t * data_set)
{
crm_graph_t *transition = NULL;
enum transition_status graph_rc = -1;
crm_graph_functions_t exec_fns = {
exec_pseudo_action,
exec_rsc_action,
exec_crmd_action,
exec_stonith_action,
};
set_graph_functions(&exec_fns);
quiet_log("\nExecuting cluster transition:\n");
transition = unpack_graph(data_set->graph, crm_system_name);
print_graph(LOG_DEBUG, transition);
do {
graph_rc = run_graph(transition);
} while (graph_rc == transition_active);
if (graph_rc != transition_complete) {
fprintf(stdout, "Transition failed: %s\n", transition_status(graph_rc));
print_graph(LOG_ERR, transition);
}
destroy_graph(transition);
if (graph_rc != transition_complete) {
fprintf(stdout, "An invalid transition was produced\n");
}
if (quiet == FALSE) {
xmlNode *cib_object = NULL;
int rc =
global_cib->cmds->query(global_cib, NULL, &cib_object, cib_sync_call | cib_scope_local);
CRM_ASSERT(rc == cib_ok);
quiet_log("\nRevised cluster status:\n");
cleanup_alloc_calculations(data_set);
data_set->input = cib_object;
data_set->now = get_date();
cluster_status(data_set);
print_cluster_status(data_set);
}
if (graph_rc != transition_complete) {
return graph_rc;
}
return 0;
}
static char *
create_action_name(action_t * action)
{
char *action_name = NULL;
const char *action_host = NULL;
if (action->node) {
action_host = action->node->details->uname;
action_name = crm_concat(action->uuid, action_host, ' ');
} else if (is_set(action->flags, pe_action_pseudo)) {
action_name = crm_strdup(action->uuid);
} else {
action_host = "<none>";
action_name = crm_concat(action->uuid, action_host, ' ');
}
if (safe_str_eq(action->task, RSC_CANCEL)) {
char *tmp_action_name = action_name;
action_name = crm_concat("Cancel", tmp_action_name, ' ');
free(tmp_action_name);
}
return action_name;
}
static void
create_dotfile(pe_working_set_t * data_set, const char *dot_file, gboolean all_actions)
{
GListPtr gIter = NULL;
FILE *dot_strm = fopen(dot_file, "w");
if (dot_strm == NULL) {
crm_perror(LOG_ERR, "Could not open %s for writing", dot_file);
return;
}
fprintf(dot_strm, " digraph \"g\" {\n");
for (gIter = data_set->actions; gIter != NULL; gIter = gIter->next) {
action_t *action = (action_t *) gIter->data;
const char *style = "dashed";
const char *font = "black";
const char *color = "black";
char *action_name = create_action_name(action);
crm_trace("Action %d: %p", action->id, action);
if (is_set(action->flags, pe_action_pseudo)) {
font = "orange";
}
if (is_set(action->flags, pe_action_dumped)) {
style = "bold";
color = "green";
} else if (action->rsc != NULL && is_not_set(action->rsc->flags, pe_rsc_managed)) {
color = "red";
font = "purple";
if (all_actions == FALSE) {
goto dont_write;
}
} else if (is_set(action->flags, pe_action_optional)) {
color = "blue";
if (all_actions == FALSE) {
goto dont_write;
}
} else {
color = "red";
CRM_CHECK(is_set(action->flags, pe_action_runnable) == FALSE,;
);
}
set_bit_inplace(action->flags, pe_action_dumped);
fprintf(dot_strm, "\"%s\" [ style=%s color=\"%s\" fontcolor=\"%s\"]\n",
action_name, style, color, font);
dont_write:
free(action_name);
}
for (gIter = data_set->actions; gIter != NULL; gIter = gIter->next) {
action_t *action = (action_t *) gIter->data;
GListPtr gIter2 = NULL;
for (gIter2 = action->actions_before; gIter2 != NULL; gIter2 = gIter2->next) {
action_wrapper_t *before = (action_wrapper_t *) gIter2->data;
char *before_name = NULL;
char *after_name = NULL;
const char *style = "dashed";
gboolean optional = TRUE;
if (before->state == pe_link_dumped) {
optional = FALSE;
style = "bold";
} else if (is_set(action->flags, pe_action_pseudo)
&& (before->type & pe_order_stonith_stop)) {
continue;
} else if (before->state == pe_link_dup) {
continue;
} else if (before->type == pe_order_none) {
continue;
} else if (is_set(before->action->flags, pe_action_dumped)
&& is_set(action->flags, pe_action_dumped)
&& before->type != pe_order_load) {
optional = FALSE;
}
if (all_actions || optional == FALSE) {
before_name = create_action_name(before->action);
after_name = create_action_name(action);
fprintf(dot_strm, "\"%s\" -> \"%s\" [ style = %s]\n",
before_name, after_name, style);
free(before_name);
free(after_name);
}
}
}
fprintf(dot_strm, "}\n");
if (dot_strm != NULL) {
fflush(dot_strm);
fclose(dot_strm);
}
}
static int
find_ticket_state(cib_t * the_cib, const char * ticket_id, xmlNode ** ticket_state_xml)
{
int offset = 0;
static int xpath_max = 1024;
enum cib_errors rc = cib_ok;
xmlNode *xml_search = NULL;
char *xpath_string = NULL;
CRM_ASSERT(ticket_state_xml != NULL);
*ticket_state_xml = NULL;
xpath_string = calloc(1, xpath_max);
offset +=
snprintf(xpath_string + offset, xpath_max - offset, "%s", "/cib/status/tickets");
if (ticket_id) {
offset += snprintf(xpath_string + offset, xpath_max - offset, "/%s[@id=\"%s\"]",
XML_CIB_TAG_TICKET_STATE, ticket_id);
}
rc = the_cib->cmds->query(the_cib, xpath_string, &xml_search,
cib_sync_call | cib_scope_local | cib_xpath);
if (rc != cib_ok) {
goto bail;
}
crm_log_xml_debug(xml_search, "Match");
if (xml_has_children(xml_search)) {
if (ticket_id) {
fprintf(stdout, "Multiple ticket_states match ticket_id=%s\n", ticket_id);
}
*ticket_state_xml = xml_search;
} else {
*ticket_state_xml = xml_search;
}
bail:
free(xpath_string);
return rc;
}
static int
set_ticket_state_attr(const char *ticket_id, const char *attr_name,
const char *attr_value, cib_t * cib, int cib_options)
{
enum cib_errors rc = cib_ok;
xmlNode *xml_top = NULL;
xmlNode *ticket_state_xml = NULL;
rc = find_ticket_state(cib, ticket_id, &ticket_state_xml);
if (rc == cib_ok) {
crm_debug("Found a match state for ticket: id=%s", ticket_id);
xml_top = ticket_state_xml;
} else if (rc != cib_NOTEXISTS) {
return rc;
} else {
xmlNode *xml_obj = NULL;
xml_top = create_xml_node(NULL, XML_CIB_TAG_STATUS);
xml_obj = create_xml_node(xml_top, XML_CIB_TAG_TICKETS);
ticket_state_xml = create_xml_node(xml_obj, XML_CIB_TAG_TICKET_STATE);
crm_xml_add(ticket_state_xml, XML_ATTR_ID, ticket_id);
}
crm_xml_add(ticket_state_xml, attr_name, attr_value);
crm_log_xml_debug(xml_top, "Update");
rc = cib->cmds->modify(cib, XML_CIB_TAG_STATUS, xml_top, cib_options);
free_xml(xml_top);
return rc;
}
static void
modify_configuration(pe_working_set_t * data_set,
const char *quorum, GListPtr node_up, GListPtr node_down, GListPtr node_fail,
GListPtr op_inject, GListPtr ticket_grant, GListPtr ticket_revoke,
GListPtr ticket_standby, GListPtr ticket_activate)
{
int rc = cib_ok;
GListPtr gIter = NULL;
xmlNode *cib_op = NULL;
xmlNode *cib_node = NULL;
xmlNode *cib_resource = NULL;
lrmd_event_data_t *op = NULL;
if (quorum) {
xmlNode *top = create_xml_node(NULL, XML_TAG_CIB);
quiet_log(" + Setting quorum: %s\n", quorum);
/* crm_xml_add(top, XML_ATTR_DC_UUID, dc_uuid); */
crm_xml_add(top, XML_ATTR_HAVE_QUORUM, quorum);
rc = global_cib->cmds->modify(global_cib, NULL, top, cib_sync_call | cib_scope_local);
CRM_ASSERT(rc == cib_ok);
}
for (gIter = node_up; gIter != NULL; gIter = gIter->next) {
char *node = (char *)gIter->data;
quiet_log(" + Bringing node %s online\n", node);
cib_node = modify_node(global_cib, node, TRUE);
CRM_ASSERT(cib_node != NULL);
rc = global_cib->cmds->modify(global_cib, XML_CIB_TAG_STATUS, cib_node,
cib_sync_call | cib_scope_local);
CRM_ASSERT(rc == cib_ok);
}
for (gIter = node_down; gIter != NULL; gIter = gIter->next) {
char *node = (char *)gIter->data;
quiet_log(" + Taking node %s offline\n", node);
cib_node = modify_node(global_cib, node, FALSE);
CRM_ASSERT(cib_node != NULL);
rc = global_cib->cmds->modify(global_cib, XML_CIB_TAG_STATUS, cib_node,
cib_sync_call | cib_scope_local);
CRM_ASSERT(rc == cib_ok);
}
for (gIter = node_fail; gIter != NULL; gIter = gIter->next) {
char *node = (char *)gIter->data;
quiet_log(" + Failing node %s\n", node);
cib_node = modify_node(global_cib, node, TRUE);
crm_xml_add(cib_node, XML_CIB_ATTR_INCCM, XML_BOOLEAN_NO);
CRM_ASSERT(cib_node != NULL);
rc = global_cib->cmds->modify(global_cib, XML_CIB_TAG_STATUS, cib_node,
cib_sync_call | cib_scope_local);
CRM_ASSERT(rc == cib_ok);
}
for (gIter = ticket_grant; gIter != NULL; gIter = gIter->next) {
char *ticket_id = (char *)gIter->data;
quiet_log(" + Granting ticket %s\n", ticket_id);
rc = set_ticket_state_attr(ticket_id, "granted", "true",
global_cib, cib_sync_call | cib_scope_local);
CRM_ASSERT(rc == cib_ok);
}
for (gIter = ticket_revoke; gIter != NULL; gIter = gIter->next) {
char *ticket_id = (char *)gIter->data;
quiet_log(" + Revoking ticket %s\n", ticket_id);
rc = set_ticket_state_attr(ticket_id, "granted", "false",
global_cib, cib_sync_call | cib_scope_local);
CRM_ASSERT(rc == cib_ok);
}
for (gIter = ticket_standby; gIter != NULL; gIter = gIter->next) {
char *ticket_id = (char *)gIter->data;
quiet_log(" + Making ticket %s standby\n", ticket_id);
rc = set_ticket_state_attr(ticket_id, "standby", "true",
global_cib, cib_sync_call | cib_scope_local);
CRM_ASSERT(rc == cib_ok);
}
for (gIter = ticket_activate; gIter != NULL; gIter = gIter->next) {
char *ticket_id = (char *)gIter->data;
quiet_log(" + Activating ticket %s\n", ticket_id);
rc = set_ticket_state_attr(ticket_id, "standby", "false",
global_cib, cib_sync_call | cib_scope_local);
CRM_ASSERT(rc == cib_ok);
}
for (gIter = op_inject; gIter != NULL; gIter = gIter->next) {
char *spec = (char *)gIter->data;
int rc = 0;
int outcome = 0;
int interval = 0;
char *key = NULL;
char *node = NULL;
char *task = NULL;
char *resource = NULL;
const char *rtype = NULL;
const char *rclass = NULL;
const char *rprovider = NULL;
resource_t *rsc = NULL;
quiet_log(" + Injecting %s into the configuration\n", spec);
key = calloc(1, strlen(spec) + 1);
node = calloc(1, strlen(spec) + 1);
rc = sscanf(spec, "%[^@]@%[^=]=%d", key, node, &outcome);
CRM_CHECK(rc == 3,
fprintf(stderr, "Invalid operation spec: %s. Only found %d fields\n", spec, rc);
continue);
parse_op_key(key, &resource, &task, &interval);
rsc = pe_find_resource(data_set->resources, resource);
if (rsc == NULL) {
fprintf(stderr, " - Invalid resource name: %s\n", resource);
} else {
rclass = crm_element_value(rsc->xml, XML_AGENT_ATTR_CLASS);
rtype = crm_element_value(rsc->xml, XML_ATTR_TYPE);
rprovider = crm_element_value(rsc->xml, XML_AGENT_ATTR_PROVIDER);
cib_node = inject_node_state(global_cib, node);
CRM_ASSERT(cib_node != NULL);
update_failcounts(cib_node, resource, interval, outcome);
cib_resource = inject_resource(cib_node, resource, rclass, rtype, rprovider);
CRM_ASSERT(cib_resource != NULL);
op = create_op(cib_resource, task, interval, outcome);
CRM_ASSERT(op != NULL);
cib_op = inject_op(cib_resource, op, 0);
CRM_ASSERT(cib_op != NULL);
lrmd_free_event(op);
rc = global_cib->cmds->modify(global_cib, XML_CIB_TAG_STATUS, cib_node,
cib_sync_call | cib_scope_local);
CRM_ASSERT(rc == cib_ok);
}
free(task);
free(node);
free(key);
}
}
static void
setup_input(const char *input, const char *output)
{
int rc = cib_ok;
cib_t *cib_conn = NULL;
xmlNode *cib_object = NULL;
char *local_output = NULL;
if (input == NULL) {
/* Use live CIB */
cib_conn = cib_new();
rc = cib_conn->cmds->signon(cib_conn, crm_system_name, cib_command);
if (rc == cib_ok) {
cib_object = get_cib_copy(cib_conn);
}
cib_conn->cmds->signoff(cib_conn);
cib_delete(cib_conn);
cib_conn = NULL;
if (cib_object == NULL) {
fprintf(stderr, "Live CIB query failed: empty result\n");
exit(3);
}
} else if (safe_str_eq(input, "-")) {
cib_object = filename2xml(NULL);
} else {
cib_object = filename2xml(input);
}
if (get_object_root(XML_CIB_TAG_STATUS, cib_object) == NULL) {
create_xml_node(cib_object, XML_CIB_TAG_STATUS);
}
if (cli_config_update(&cib_object, NULL, FALSE) == FALSE) {
free_xml(cib_object);
exit(cib_STALE);
}
if (validate_xml(cib_object, NULL, FALSE) != TRUE) {
free_xml(cib_object);
exit(cib_dtd_validation);
}
if (output == NULL) {
char *pid = crm_itoa(getpid());
local_output = get_shadow_file(pid);
output = local_output;
free(pid);
}
rc = write_xml_file(cib_object, output, FALSE);
free_xml(cib_object);
cib_object = NULL;
if (rc < 0) {
fprintf(stderr, "Could not create '%s': %s\n", output, strerror(errno));
exit(rc);
}
setenv("CIB_file", output, 1);
free(local_output);
}
/* *INDENT-OFF* */
static struct crm_option long_options[] = {
/* Top-level Options */
{"help", 0, 0, '?', "\tThis text"},
{"version", 0, 0, '$', "\tVersion information" },
{"quiet", 0, 0, 'Q', "\tDisplay only essentialoutput"},
{"verbose", 0, 0, 'V', "\tIncrease debug output"},
{"-spacer-", 0, 0, '-', "\nOperations:"},
{"run", 0, 0, 'R', "\tDetermine the cluster's response to the given configuration and status"},
{"simulate", 0, 0, 'S', "Simulate the transition's execution and display the resulting cluster status"},
{"in-place", 0, 0, 'X', "Simulate the transition's execution and store the result back to the input file"},
{"show-scores", 0, 0, 's', "Show allocation scores"},
{"show-utilization", 0, 0, 'U', "Show utilization information"},
{"profile", 1, 0, 'P', "Run all tests in the named directory to create profiling data"},
{"-spacer-", 0, 0, '-', "\nSynthetic Cluster Events:"},
{"node-up", 1, 0, 'u', "\tBring a node online"},
{"node-down", 1, 0, 'd', "\tTake a node offline"},
{"node-fail", 1, 0, 'f', "\tMark a node as failed"},
{"op-inject", 1, 0, 'i', "\t$rsc_$task_$interval@$node=$rc - Inject the specified task before running the simulation"},
{"op-fail", 1, 0, 'F', "\t$rsc_$task_$interval@$node=$rc - Fail the specified task while running the simulation"},
{"set-datetime", 1, 0, 't', "Set date/time"},
{"quorum", 1, 0, 'q', "\tSpecify a value for quorum"},
{"ticket-grant", 1, 0, 'g', "Grant a ticket"},
{"ticket-revoke", 1, 0, 'r', "Revoke a ticket"},
{"ticket-standby", 1, 0, 'b', "Make a ticket standby"},
{"ticket-activate", 1, 0, 'e', "Activate a ticket"},
{"-spacer-", 0, 0, '-', "\nOutput Options:"},
{"save-input", 1, 0, 'I', "\tSave the input configuration to the named file"},
{"save-output", 1, 0, 'O', "Save the output configuration to the named file"},
{"save-graph", 1, 0, 'G', "\tSave the transition graph (XML format) to the named file"},
{"save-dotfile", 1, 0, 'D', "Save the transition graph (DOT format) to the named file"},
{"all-actions", 0, 0, 'a', "\tDisplay all possible actions in the DOT graph - even ones not part of the transition"},
{"-spacer-", 0, 0, '-', "\nData Source:"},
{"live-check", 0, 0, 'L', "\tConnect to the CIB and use the current contents as input"},
{"xml-file", 1, 0, 'x', "\tRetrieve XML from the named file"},
{"xml-pipe", 0, 0, 'p', "\tRetrieve XML from stdin"},
{0, 0, 0, 0}
};
/* *INDENT-ON* */
static void
profile_one(const char *xml_file)
{
xmlNode *cib_object = NULL;
pe_working_set_t data_set;
printf("* Testing %s\n", xml_file);
cib_object = filename2xml(xml_file);
if (get_object_root(XML_CIB_TAG_STATUS, cib_object) == NULL) {
create_xml_node(cib_object, XML_CIB_TAG_STATUS);
}
if (cli_config_update(&cib_object, NULL, FALSE) == FALSE) {
free_xml(cib_object);
return;
}
if (validate_xml(cib_object, NULL, FALSE) != TRUE) {
free_xml(cib_object);
return;
}
set_working_set_defaults(&data_set);
data_set.input = cib_object;
data_set.now = get_date();
do_calculations(&data_set, cib_object, NULL);
cleanup_alloc_calculations(&data_set);
}
#ifndef FILENAME_MAX
# define FILENAME_MAX 512
#endif
static int
profile_all(const char *dir)
{
struct dirent **namelist;
int lpc = 0;
int file_num = scandir(dir, &namelist, 0, alphasort);
if (file_num > 0) {
struct stat prop;
char buffer[FILENAME_MAX + 1];
while (file_num--) {
if ('.' == namelist[file_num]->d_name[0]) {
free(namelist[file_num]);
continue;
} else if (strstr(namelist[file_num]->d_name, ".xml") == NULL) {
free(namelist[file_num]);
continue;
}
lpc++;
snprintf(buffer, FILENAME_MAX, "%s/%s", dir, namelist[file_num]->d_name);
if (stat(buffer, &prop) == 0 && S_ISREG(prop.st_mode)) {
profile_one(buffer);
}
free(namelist[file_num]);
}
free(namelist);
}
return lpc;
}
int
main(int argc, char **argv)
{
int rc = 0;
guint modified = 0;
gboolean store = FALSE;
gboolean process = FALSE;
gboolean verbose = FALSE;
gboolean simulate = FALSE;
gboolean all_actions = FALSE;
gboolean have_stdout = FALSE;
pe_working_set_t data_set;
const char *xml_file = "-";
const char *quorum = NULL;
const char *test_dir = NULL;
const char *dot_file = NULL;
const char *graph_file = NULL;
const char *input_file = NULL;
const char *output_file = NULL;
int flag = 0;
int index = 0;
int argerr = 0;
GListPtr node_up = NULL;
GListPtr node_down = NULL;
GListPtr node_fail = NULL;
GListPtr op_inject = NULL;
GListPtr ticket_grant = NULL;
GListPtr ticket_revoke = NULL;
GListPtr ticket_standby = NULL;
GListPtr ticket_activate = NULL;
xmlNode *input = NULL;
crm_log_cli_init("crm_simulate");
crm_set_options(NULL, "datasource operation [additional options]",
long_options, "Tool for simulating the cluster's response to events");
if (argc < 2) {
crm_help('?', LSB_EXIT_EINVAL);
}
while (1) {
flag = crm_get_option(argc, argv, &index);
if (flag == -1)
break;
switch (flag) {
case 'V':
verbose = TRUE;
if(have_stdout == FALSE) {
/* Redirect stderr to stdout so we can grep the output */
have_stdout = TRUE;
close(STDERR_FILENO);
dup2(STDOUT_FILENO, STDERR_FILENO);
}
crm_bump_log_level();
break;
case '?':
case '$':
crm_help(flag, LSB_EXIT_OK);
break;
case 'p':
xml_file = "-";
break;
case 'Q':
quiet = TRUE;
break;
case 'L':
xml_file = NULL;
break;
case 'x':
xml_file = optarg;
break;
case 'u':
modified++;
node_up = g_list_append(node_up, optarg);
break;
case 'd':
modified++;
node_down = g_list_append(node_down, optarg);
break;
case 'f':
modified++;
node_fail = g_list_append(node_fail, optarg);
break;
case 't':
use_date = crm_strdup(optarg);
break;
case 'i':
modified++;
op_inject = g_list_append(op_inject, optarg);
break;
case 'F':
process = TRUE;
simulate = TRUE;
op_fail = g_list_append(op_fail, optarg);
break;
case 'q':
modified++;
quorum = optarg;
break;
case 'g':
modified++;
ticket_grant = g_list_append(ticket_grant, optarg);
break;
case 'r':
modified++;
ticket_revoke = g_list_append(ticket_revoke, optarg);
break;
case 'b':
modified++;
ticket_standby = g_list_append(ticket_standby, optarg);
break;
case 'e':
modified++;
ticket_activate = g_list_append(ticket_activate, optarg);
break;
case 'a':
all_actions = TRUE;
break;
case 's':
process = TRUE;
show_scores = TRUE;
break;
case 'U':
process = TRUE;
show_utilization = TRUE;
break;
case 'S':
process = TRUE;
simulate = TRUE;
break;
case 'X':
store = TRUE;
process = TRUE;
simulate = TRUE;
break;
case 'R':
process = TRUE;
break;
case 'D':
process = TRUE;
dot_file = optarg;
break;
case 'G':
process = TRUE;
graph_file = optarg;
break;
case 'I':
input_file = optarg;
break;
case 'O':
output_file = optarg;
break;
case 'P':
test_dir = optarg;
break;
default:
++argerr;
break;
}
}
if (optind > argc) {
++argerr;
}
if (argerr) {
crm_help('?', LSB_EXIT_GENERIC);
}
- /* update_all_trace_data(); /\* again, so we see which trace points got updated *\/ */
-
if(test_dir != NULL) {
return profile_all(test_dir);
}
setup_input(xml_file, store ? xml_file : output_file);
global_cib = cib_new();
global_cib->cmds->signon(global_cib, crm_system_name, cib_command);
set_working_set_defaults(&data_set);
if (data_set.now != NULL) {
quiet_log(" + Setting effective cluster time: %s", use_date);
log_date(LOG_WARNING, "Set fake 'now' to", data_set.now, ha_log_date | ha_log_time);
}
rc = global_cib->cmds->query(global_cib, NULL, &input, cib_sync_call | cib_scope_local);
CRM_ASSERT(rc == cib_ok);
data_set.input = input;
data_set.now = get_date();
cluster_status(&data_set);
if (quiet == FALSE) {
quiet_log("\nCurrent cluster status:\n");
print_cluster_status(&data_set);
}
if (modified) {
quiet_log("Performing requested modifications\n");
modify_configuration(&data_set, quorum, node_up, node_down, node_fail, op_inject,
ticket_grant, ticket_revoke, ticket_standby, ticket_activate);
rc = global_cib->cmds->query(global_cib, NULL, &input, cib_sync_call);
if (rc != cib_ok) {
fprintf(stderr, "Could not connect to the CIB for input: %s\n", cib_error2string(rc));
goto done;
}
cleanup_alloc_calculations(&data_set);
data_set.now = get_date();
data_set.input = input;
}
if (input_file != NULL) {
rc = write_xml_file(input, input_file, FALSE);
if (rc < 0) {
fprintf(stderr, "Could not create '%s': %s\n", input_file, strerror(errno));
goto done;
}
}
rc = 0;
if (process || simulate) {
ha_time_t *local_date = NULL;
if (show_scores && show_utilization) {
printf("Allocation scores and utilization information:\n");
} else if (show_scores) {
fprintf(stdout, "Allocation scores:\n");
} else if (show_utilization) {
printf("Utilization information:\n");
}
do_calculations(&data_set, input, local_date);
input = NULL; /* Don't try and free it twice */
if (graph_file != NULL) {
char *msg_buffer = dump_xml_formatted(data_set.graph);
FILE *graph_strm = fopen(graph_file, "w");
if (graph_strm == NULL) {
crm_perror(LOG_ERR, "Could not open %s for writing", graph_file);
} else {
if (fprintf(graph_strm, "%s\n\n", msg_buffer) < 0) {
crm_perror(LOG_ERR, "Write to %s failed", graph_file);
}
fflush(graph_strm);
fclose(graph_strm);
}
free(msg_buffer);
}
if (dot_file != NULL) {
create_dotfile(&data_set, dot_file, all_actions);
}
if (quiet == FALSE) {
GListPtr gIter = NULL;
quiet_log("%sTransition Summary:\n", show_scores || show_utilization
|| modified ? "\n" : "");
fflush(stdout);
for (gIter = data_set.resources; gIter != NULL; gIter = gIter->next) {
resource_t *rsc = (resource_t *) gIter->data;
LogActions(rsc, &data_set, TRUE);
}
}
}
if (simulate) {
rc = run_simulation(&data_set);
}
done:
cleanup_alloc_calculations(&data_set);
global_cib->cmds->signoff(global_cib);
cib_delete(global_cib);
free(use_date);
crm_xml_cleanup();
fflush(stderr);
qb_log_fini();
return rc;
}

File Metadata

Mime Type
text/x-diff
Expires
Tue, Jul 8, 3:45 PM (15 h, 20 s)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
1991862
Default Alt Text
(174 KB)

Event Timeline