diff --git a/include/crm/common/mainloop.h b/include/crm/common/mainloop.h index bf0a159ce0..9ba429a743 100644 --- a/include/crm/common/mainloop.h +++ b/include/crm/common/mainloop.h @@ -1,74 +1,98 @@ /* * Copyright (C) 2009 Andrew Beekhof * * 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_MAINLOOP__H # define CRM_COMMON_MAINLOOP__H # include typedef struct trigger_s crm_trigger_t; extern crm_trigger_t *mainloop_add_trigger(int priority, int(*dispatch) (gpointer user_data), gpointer userdata); extern void mainloop_set_trigger(crm_trigger_t * source); extern void mainloop_trigger_complete(crm_trigger_t *trig); extern gboolean mainloop_destroy_trigger(crm_trigger_t * source); extern gboolean crm_signal(int sig, void (*dispatch) (int sig)); extern gboolean mainloop_add_signal(int sig, void (*dispatch) (int sig)); extern gboolean mainloop_destroy_signal(int sig); #include struct ipc_client_callbacks { int (*dispatch)(const char *buffer, ssize_t length, gpointer userdata); void (*destroy) (gpointer); }; qb_ipcs_service_t *mainloop_add_ipc_server( const char *name, enum qb_ipc_type type, struct qb_ipcs_service_handlers *callbacks); void mainloop_del_ipc_server(qb_ipcs_service_t *server); typedef struct mainloop_io_s mainloop_io_t; mainloop_io_t *mainloop_add_ipc_client( const char *name, size_t max_size, void *userdata, struct ipc_client_callbacks *callbacks); void mainloop_del_ipc_client(mainloop_io_t *client); crm_ipc_t *mainloop_get_ipc_client(mainloop_io_t *client); struct mainloop_fd_callbacks { int (*dispatch)(gpointer userdata); void (*destroy)(gpointer userdata); }; mainloop_io_t *mainloop_add_fd( const char *name, int fd, void *userdata, struct mainloop_fd_callbacks *callbacks); void mainloop_del_fd(mainloop_io_t *client); +typedef struct mainloop_child_s mainloop_child_t; +/* + * Create a new tracked process + * To track a process group, use -pid + */ +void +mainloop_add_child(pid_t pid, + int timeout, + const char *desc, + void *userdata, + void (*callback)(mainloop_child_t* p, + int status, + int signo, + int exitcode)); + +void * +mainloop_get_child_userdata(mainloop_child_t *child); +int +mainloop_get_child_timeout(mainloop_child_t *child); +pid_t +mainloop_get_child_pid(mainloop_child_t *child); +void +mainloop_clear_child_userdata(mainloop_child_t *child); + #endif diff --git a/include/crm/common/util.h b/include/crm/common/util.h index 9bccdde7f5..5c1bcaf997 100644 --- a/include/crm/common/util.h +++ b/include/crm/common/util.h @@ -1,400 +1,406 @@ /* * Copyright (C) 2004 Andrew Beekhof * * 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 # include # include # include # include # include # if SUPPORT_HEARTBEAT # include # 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 # 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); } extern void crm_log_deinit(void); extern 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); extern void crm_log_args(int argc, char **argv); extern int crm_should_log(int level); extern void crm_bump_log_level(void); extern void crm_enable_stderr(int enable); /* returns the old value */ extern unsigned int set_crm_log_level(unsigned int level); extern unsigned int get_crm_log_level(void); extern char *crm_itoa(int an_int); extern char *crm_strdup_fn(const char *a, const char *file, const char *fn, int line); extern char *generate_hash_key(const char *crm_msg_reference, const char *sys); extern char *generate_hash_value(const char *src_node, const char *src_subsys); extern gboolean decodeNVpair(const char *srcstring, char separator, char **name, char **value); extern int compare_version(const char *version1, const char *version2); extern char *generateReference(const char *custom1, const char *custom2); extern void g_hash_destroy_str(gpointer data); extern gboolean crm_is_true(const char *s); extern int crm_str_to_boolean(const char *s, int *ret); extern long long crm_get_msec(const char *input); extern unsigned long long crm_get_interval(const char *input); extern const char *op_status2text(op_status_t status); extern char *generate_op_key(const char *rsc_id, const char *op_type, int interval); extern gboolean parse_op_key(const char *key, char **rsc_id, char **op_type, int *interval); extern char *generate_notify_key(const char *rsc_id, const char *notify_type, const char *op_type); extern char *generate_transition_magic_v202(const char *transition_key, int op_status); extern char *generate_transition_magic(const char *transition_key, int op_status, int op_rc); extern gboolean decode_transition_magic(const char *magic, char **uuid, int *transition_id, int *action_id, int *op_status, int *op_rc, int *target_rc); extern char *generate_transition_key(int action, int transition_id, int target_rc, const char *node); extern gboolean decode_transition_key(const char *key, char **uuid, int *action, int *transition_id, int *target_rc); extern char *crm_concat(const char *prefix, const char *suffix, char join); extern gboolean decode_op_key(const char *key, char **rsc_id, char **op_type, int *interval); extern void filter_action_parameters(xmlNode * param_set, const char *version); extern 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) extern gboolean crm_str_eq(const char *a, const char *b, gboolean use_case); extern gboolean safe_str_neq(const char *a, const char *b); extern int crm_parse_int(const char *text, const char *default_text); extern long long crm_int_helper(const char *text, char **end_text); # define crm_atoi(text, default_text) crm_parse_int(text, default_text) extern void crm_abort(const char *file, const char *function, int line, const char *condition, gboolean do_core, gboolean do_fork); extern char *generate_series_filename(const char *directory, const char *series, int sequence, gboolean bzip); extern int get_last_sequence(const char *directory, const char *series); extern void write_last_sequence(const char *directory, const char *series, int sequence, int max); extern int crm_pid_active(long pid); extern int crm_read_pidfile(const char *filename); extern int crm_lock_pidfile(const char *filename); extern 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; extern const char *cluster_option(GHashTable * options, gboolean(*validate) (const char *), const char *name, const char *old_name, const char *def_value); extern const char *get_cluster_pref(GHashTable * options, pe_cluster_option * option_list, int len, const char *name); extern void config_metadata(const char *name, const char *version, const char *desc_short, const char *desc_long, pe_cluster_option * option_list, int len); extern void verify_all_options(GHashTable * options, pe_cluster_option * option_list, int len); extern gboolean check_time(const char *value); extern gboolean check_timer(const char *value); extern gboolean check_boolean(const char *value); extern gboolean check_number(const char *value); extern int char2score(const char *score); extern char *score2char(int score); extern 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); } extern xmlNode *cib_recv_remote_msg(void *session, gboolean encrypted); extern void cib_send_remote_msg(void *session, xmlNode * msg, gboolean encrypted); extern char *crm_meta_name(const char *field); extern const char *crm_meta_value(GHashTable * hash, const char *field); extern void crm_set_options(const char *short_options, const char *usage, struct crm_option *long_options, const char *app_desc); extern int crm_get_option(int argc, char **argv, int *index); extern void crm_help(char cmd, int exit_code); extern int rsc_op_expected_rc(lrm_op_t * op); extern gboolean did_rsc_op_fail(lrm_op_t * op, int target_rc); extern int node_score_red; extern int node_score_green; extern int node_score_yellow; extern int node_score_infinity; # include extern xmlNode *create_operation_update(xmlNode * parent, lrm_op_t * op, const char *caller_version, int target_rc, const char *origin, int level); extern void free_lrm_op(lrm_op_t * op); # 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; } extern void determine_request_user(char **user, IPC_Channel * channel, xmlNode * request, const char *field); # endif extern void *find_library_function(void **handle, const char *lib, const char *fn); extern void *convert_const_pointer(const void *ptr); extern 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); #endif diff --git a/include/crm/services.h b/include/crm/services.h new file mode 100644 index 0000000000..fed9bcb608 --- /dev/null +++ b/include/crm/services.h @@ -0,0 +1,355 @@ +/* + * Copyright (C) 2010 Andrew Beekhof + * + * 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.1 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +/** + * \file + * \brief Services API + * \ingroup coreapi + */ + +#ifndef __PCMK_SERVICES__ +#define __PCMK_SERVICES__ + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include + +#ifndef OCF_ROOT_DIR +#define OCF_ROOT_DIR "/usr/lib/ocf" +#endif + +#ifndef LSB_ROOT_DIR +#define LSB_ROOT_DIR "/etc/init.d" +#endif + +/* TODO: Autodetect these two ?*/ +#ifndef SYSTEMCTL +#define SYSTEMCTL "/bin/systemctl" +#endif + +#ifndef SERVICE_SCRIPT +#define SERVICE_SCRIPT "/sbin/service" +#endif + +/* *INDENT-OFF* */ +enum lsb_exitcode { + PCMK_LSB_OK = 0, + PCMK_LSB_UNKNOWN_ERROR = 1, + PCMK_LSB_INVALID_PARAM = 2, + PCMK_LSB_UNIMPLEMENT_FEATURE = 3, + PCMK_LSB_INSUFFICIENT_PRIV = 4, + PCMK_LSB_NOT_INSTALLED = 5, + PCMK_LSB_NOT_CONFIGURED = 6, + PCMK_LSB_NOT_RUNNING = 7, + + /* 150-199 reserved for application use */ + PCMK_LSB_SIGNAL = 194, + PCMK_LSB_NOT_SUPPORTED = 195, + PCMK_LSB_PENDING = 196, + PCMK_LSB_CANCELLED = 197, + PCMK_LSB_TIMEOUT = 198, + PCMK_LSB_OTHER_ERROR = 199, +}; + +/* The return codes for the status operation are not the same for other + * operatios - go figure */ +enum lsb_status_exitcode { + PCMK_LSB_STATUS_OK = 0, + PCMK_LSB_STATUS_VAR_PID = 1, + PCMK_LSB_STATUS_VAR_LOCK = 2, + PCMK_LSB_STATUS_NOT_RUNNING = 3, + PCMK_LSB_STATUS_NOT_INSTALLED = 4, + + /* 150-199 reserved for application use */ + PCMK_LSB_STATUS_SIGNAL = 194, + PCMK_LSB_STATUS_NOT_SUPPORTED = 195, + PCMK_LSB_STATUS_PENDING = 196, + PCMK_LSB_STATUS_CANCELLED = 197, + PCMK_LSB_STATUS_TIMEOUT = 198, + PCMK_LSB_STATUS_OTHER_ERROR = 199, +}; + +enum ocf_exitcode { + PCMK_OCF_OK = 0, + PCMK_OCF_UNKNOWN_ERROR = 1, + PCMK_OCF_INVALID_PARAM = 2, + PCMK_OCF_UNIMPLEMENT_FEATURE = 3, + PCMK_OCF_INSUFFICIENT_PRIV = 4, + PCMK_OCF_NOT_INSTALLED = 5, + PCMK_OCF_NOT_CONFIGURED = 6, + PCMK_OCF_NOT_RUNNING = 7, + PCMK_OCF_RUNNING_MASTER = 8, + PCMK_OCF_FAILED_MASTER = 9, + + /* 150-199 reserved for application use */ + PCMK_OCF_SIGNAL = 194, + PCMK_OCF_NOT_SUPPORTED = 195, + PCMK_OCF_PENDING = 196, + PCMK_OCF_CANCELLED = 197, + PCMK_OCF_TIMEOUT = 198, + PCMK_OCF_OTHER_ERROR = 199, /* Keep the same codes as PCMK_LSB */ +}; + +enum op_status { + PCMK_LRM_OP_PENDING = -1, + PCMK_LRM_OP_DONE, + PCMK_LRM_OP_CANCELLED, + PCMK_LRM_OP_TIMEOUT, + PCMK_LRM_OP_NOTSUPPORTED, + PCMK_LRM_OP_ERROR +}; +/* *INDENT-ON* */ + +typedef struct svc_action_private_s svc_action_private_t; +typedef struct svc_action_s +{ + char *id; + char *rsc; + char *action; + int interval; + + char *standard; + char *provider; + char *agent; + + int timeout; + GHashTable *params; + + int rc; + int pid; + int cancel; + int status; + int sequence; + int expected_rc; + + char *stderr_data; + char *stdout_data; + + /** + * Data stored by the creator of the action. + * + * This may be used to hold data that is needed later on by a callback, + * for example. + */ + void *cb_data; + + svc_action_private_t *opaque; + +} svc_action_t; + +/** + * Get a list of files or directories in a given path + * + * \param[in] root full path to a directory to read + * \param[in] files true to get a list of files, false for a list of directories + * + * \return a list of what was found. The list items are gchar *. This list _must_ + * be destroyed using g_list_free_full(list, free). + */ +GList * +get_directory_list(const char *root, gboolean files); + +/** + * Get a list of services + * + * \return a list of services. The list items are gchar *. This list _must_ + * be destroyed using g_list_free_full(list, free). + */ +GList * +services_list(void); + +/** + * Get a list of providers + * + * \param[in] the standard for providers to check for (such as "ocf") + * + * \return a list of providers. The list items are gchar *. This list _must_ + * be destroyed using g_list_free_full(list, free). + */ +GList * +resources_list_providers(const char *standard); + +/** + * Get a list of resource agents + * + * \param[in] the standard for research agents to check for + * (such as "ocf", "lsb", or "windows") + * + * \return a list of resource agents. The list items are gchar *. This list _must_ + * be destroyed using g_list_free_full(list, free). + */ +GList * +resources_list_agents(const char *standard, const char *provider); + +/** + * Get list of available standards + * + * \return a list of resource standards. The list items are char *. This list _must_ + * be destroyed using g_list_free_full(list, free). + */ +GList * +resources_list_standards(void); + +svc_action_t * +services_action_create(const char *name, const char *action, + int interval /* ms */, int timeout /* ms */); + +/** + * Create a resources action. + * + * \param[in] timeout the timeout in milliseconds + * \param[in] interval how often to repeat this action, in milliseconds. + * If this value is 0, only execute this action one time. + * + * \post After the call, 'params' is owned, and later free'd by the svc_action_t result + */ +svc_action_t * +resources_action_create(const char *name, const char *standard, + const char *provider, const char *agent, + const char *action, int interval /* ms */, + int timeout /* ms */, GHashTable *params); + +/** + * Utilize services API to execute an arbitrary command. + * + * This API has useful infrastructure in place to be able to run a command + * in the background and get notified via a callback when the command finishes. + * + * \param[in] exec command to execute + * \param[in] args arguments to the command, NULL terminated + * + * \return a svc_action_t object, used to pass to the execute function + * (services_action_sync() or services_action_async()) and is + * provided to the callback. + */ +svc_action_t * +services_action_create_generic(const char *exec, const char *args[]); + +void +services_action_free(svc_action_t *op); + +gboolean +services_action_sync(svc_action_t *op); + +/** + * Run an action asynchronously. + * + * \param[in] op services action data + * \param[in] action_callback callback for when the action completes + * + * \retval TRUE succesfully started execution + * \retval FALSE failed to start execution, no callback will be received + */ +gboolean +services_action_async(svc_action_t *op, void (*action_callback)(svc_action_t *)); + +gboolean +services_action_cancel(const char *name, const char *action, int interval); + +static inline const char* +services_lrm_status_str(enum op_status status) +{ + switch (status) { + case PCMK_LRM_OP_PENDING: + return "pending"; + case PCMK_LRM_OP_DONE: + return "complete"; + case PCMK_LRM_OP_CANCELLED: + return "Cancelled"; + case PCMK_LRM_OP_TIMEOUT: + return "Timed Out"; + case PCMK_LRM_OP_NOTSUPPORTED: + return "NOT SUPPORTED"; + case PCMK_LRM_OP_ERROR: + return "Error"; + default: + return "UNKNOWN!"; + } +} + +static inline const char* +services_ocf_exitcode_str(enum ocf_exitcode code) +{ + switch (code) { + case PCMK_OCF_OK: + return "OCF_OK"; + case PCMK_OCF_UNKNOWN_ERROR: + return "OCF_UNKNOWN_ERROR"; + case PCMK_OCF_INVALID_PARAM: + return "OCF_INVALID_PARAM"; + case PCMK_OCF_UNIMPLEMENT_FEATURE: + return "OCF_UNIMPLEMENT_FEATURE"; + case PCMK_OCF_INSUFFICIENT_PRIV: + return "OCF_INSUFFICIENT_PRIV"; + case PCMK_OCF_NOT_INSTALLED: + return "OCF_NOT_INSTALLED"; + case PCMK_OCF_NOT_CONFIGURED: + return "OCF_NOT_CONFIGURED"; + case PCMK_OCF_NOT_RUNNING: + return "OCF_NOT_RUNNING"; + case PCMK_OCF_RUNNING_MASTER: + return "OCF_RUNNING_MASTER"; + case PCMK_OCF_FAILED_MASTER: + return "OCF_FAILED_MASTER"; + case PCMK_OCF_SIGNAL: + return "OCF_SIGNAL"; + case PCMK_OCF_NOT_SUPPORTED: + return "OCF_NOT_SUPPORTED"; + case PCMK_OCF_PENDING: + return "OCF_PENDING"; + case PCMK_OCF_CANCELLED: + return "OCF_CANCELLED"; + case PCMK_OCF_TIMEOUT: + return "OCF_TIMEOUT"; + case PCMK_OCF_OTHER_ERROR: + return "OCF_OTHER_ERROR"; + default: + return "unknown"; + } +} + +static inline enum ocf_exitcode +services_get_ocf_exitcode(char *action, int lsb_exitcode) +{ + if (action != NULL && strcmp("status", action) == 0) { + switch (lsb_exitcode) { + case PCMK_LSB_STATUS_OK: return PCMK_OCF_OK; + case PCMK_LSB_STATUS_VAR_PID: return PCMK_OCF_NOT_RUNNING; + case PCMK_LSB_STATUS_VAR_LOCK: return PCMK_OCF_NOT_RUNNING; + case PCMK_LSB_STATUS_NOT_RUNNING: return PCMK_OCF_NOT_RUNNING; + case PCMK_LSB_STATUS_NOT_INSTALLED: return PCMK_OCF_UNKNOWN_ERROR; + default: return PCMK_OCF_UNKNOWN_ERROR; + } + + } else if (lsb_exitcode > PCMK_LSB_NOT_RUNNING) { + return PCMK_OCF_UNKNOWN_ERROR; + } + + /* For non-status operations, the PCMK_LSB and PCMK_OCF share error code meaning + * for rc <= 7 */ + return (enum ocf_exitcode)lsb_exitcode; +} + +#ifdef __cplusplus +} +#endif + +#endif /* __PCMK_SERVICES__ */ diff --git a/lib/common/mainloop.c b/lib/common/mainloop.c index dd00e9c85e..b1ae1d7313 100644 --- a/lib/common/mainloop.c +++ b/lib/common/mainloop.c @@ -1,625 +1,767 @@ /* * Copyright (C) 2004 Andrew Beekhof * * 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 #ifndef _GNU_SOURCE # define _GNU_SOURCE #endif #include #include #include +#include + #include #include #include #include +struct mainloop_child_s { + pid_t pid; + char *desc; + unsigned timerid; + unsigned watchid; + gboolean timeout; + void *privatedata; + + /* Called when a process dies */ + void (*callback)(mainloop_child_t* p, int status, int signo, int exitcode); +}; + typedef struct trigger_s { GSource source; gboolean running; gboolean trigger; void *user_data; guint id; } crm_trigger_t; static gboolean crm_trigger_prepare(GSource * source, gint * timeout) { crm_trigger_t *trig = (crm_trigger_t *) source; /* cluster-glue's FD and IPC related sources make use of * g_source_add_poll() but do not set a timeout in their prepare * functions * * This means mainloop's poll() will block until an event for one * of these sources occurs - any /other/ type of source, such as * this one or g_idle_*, that doesn't use g_source_add_poll() is * S-O-L and wont be processed until there is something fd-based * happens. * * Luckily the timeout we can set here affects all sources and * puts an upper limit on how long poll() can take. * * So unconditionally set a small-ish timeout, not too small that * we're in constant motion, which will act as an upper bound on * how long the signal handling might be delayed for. */ *timeout = 500; /* Timeout in ms */ return trig->trigger; } static gboolean crm_trigger_check(GSource * source) { crm_trigger_t *trig = (crm_trigger_t *) source; return trig->trigger; } static gboolean crm_trigger_dispatch(GSource * source, GSourceFunc callback, gpointer userdata) { int rc = TRUE; crm_trigger_t *trig = (crm_trigger_t *) source; if(trig->running) { /* Wait until the existing job is complete before starting the next one */ return TRUE; } trig->trigger = FALSE; if (callback) { rc = callback(trig->user_data); if(rc < 0) { crm_trace("Trigger handler %p not yet complete", trig); trig->running = TRUE; rc = TRUE; } } return rc; } static GSourceFuncs crm_trigger_funcs = { crm_trigger_prepare, crm_trigger_check, crm_trigger_dispatch, NULL }; static crm_trigger_t * mainloop_setup_trigger(GSource * source, int priority, int(*dispatch) (gpointer user_data), gpointer userdata) { crm_trigger_t *trigger = NULL; trigger = (crm_trigger_t *) source; trigger->id = 0; trigger->trigger = FALSE; trigger->user_data = userdata; if (dispatch) { g_source_set_callback(source, dispatch, trigger, NULL); } g_source_set_priority(source, priority); g_source_set_can_recurse(source, FALSE); trigger->id = g_source_attach(source, NULL); return trigger; } void mainloop_trigger_complete(crm_trigger_t *trig) { crm_trace("Trigger handler %p complete", trig); trig->running = FALSE; } /* If dispatch returns: * -1: Job running but not complete * 0: Remove the trigger from mainloop * 1: Leave the trigger in mainloop */ crm_trigger_t * mainloop_add_trigger(int priority, int(*dispatch) (gpointer user_data), gpointer userdata) { GSource *source = NULL; CRM_ASSERT(sizeof(crm_trigger_t) > sizeof(GSource)); source = g_source_new(&crm_trigger_funcs, sizeof(crm_trigger_t)); CRM_ASSERT(source != NULL); return mainloop_setup_trigger(source, priority, dispatch, userdata); } void mainloop_set_trigger(crm_trigger_t * source) { source->trigger = TRUE; } gboolean mainloop_destroy_trigger(crm_trigger_t * source) { source->trigger = FALSE; if (source->id > 0) { g_source_remove(source->id); } return TRUE; } typedef struct signal_s { crm_trigger_t trigger; /* must be first */ void (*handler) (int sig); int signal; } crm_signal_t; static crm_signal_t *crm_signals[NSIG]; static gboolean crm_signal_dispatch(GSource * source, GSourceFunc callback, gpointer userdata) { crm_signal_t *sig = (crm_signal_t *) source; crm_info("Invoking handler for signal %d: %s", sig->signal, strsignal(sig->signal)); sig->trigger.trigger = FALSE; if (sig->handler) { sig->handler(sig->signal); } return TRUE; } static void mainloop_signal_handler(int sig) { if (sig > 0 && sig < NSIG && crm_signals[sig] != NULL) { mainloop_set_trigger((crm_trigger_t *) crm_signals[sig]); } } static GSourceFuncs crm_signal_funcs = { crm_trigger_prepare, crm_trigger_check, crm_signal_dispatch, NULL }; gboolean crm_signal(int sig, void (*dispatch) (int sig)) { sigset_t mask; struct sigaction sa; struct sigaction old; if (sigemptyset(&mask) < 0) { crm_perror(LOG_ERR, "Call to sigemptyset failed"); return FALSE; } memset(&sa, 0, sizeof(struct sigaction)); sa.sa_handler = dispatch; sa.sa_flags = SA_RESTART; sa.sa_mask = mask; if (sigaction(sig, &sa, &old) < 0) { crm_perror(LOG_ERR, "Could not install signal handler for signal %d", sig); return FALSE; } return TRUE; } gboolean mainloop_add_signal(int sig, void (*dispatch) (int sig)) { GSource *source = NULL; int priority = G_PRIORITY_HIGH - 1; if (sig == SIGTERM) { /* TERM is higher priority than other signals, * signals are higher priority than other ipc. * Yes, minus: smaller is "higher" */ priority--; } if (sig >= NSIG || sig < 0) { crm_err("Signal %d is out of range", sig); return FALSE; } else if (crm_signals[sig] != NULL) { crm_err("Signal handler for %d is already installed", sig); return FALSE; } CRM_ASSERT(sizeof(crm_signal_t) > sizeof(GSource)); source = g_source_new(&crm_signal_funcs, sizeof(crm_signal_t)); crm_signals[sig] = (crm_signal_t *) mainloop_setup_trigger(source, priority, NULL, NULL); CRM_ASSERT(crm_signals[sig] != NULL); crm_signals[sig]->handler = dispatch; crm_signals[sig]->signal = sig; if (crm_signal(sig, mainloop_signal_handler) == FALSE) { crm_signal_t *tmp = crm_signals[sig]; crm_signals[sig] = NULL; mainloop_destroy_trigger((crm_trigger_t *) tmp); return FALSE; } #if 0 /* If we want signals to interrupt mainloop's poll(), instead of waiting for * the timeout, then we should call siginterrupt() below * * For now, just enforce a low timeout */ if (siginterrupt(sig, 1) < 0) { crm_perror(LOG_INFO, "Could not enable system call interruptions for signal %d", sig); } #endif return TRUE; } gboolean mainloop_destroy_signal(int sig) { crm_signal_t *tmp = NULL; if (sig >= NSIG || sig < 0) { crm_err("Signal %d is out of range", sig); return FALSE; } else if (crm_signal(sig, NULL) == FALSE) { crm_perror(LOG_ERR, "Could not uninstall signal handler for signal %d", sig); return FALSE; } else if (crm_signals[sig] == NULL) { return TRUE; } tmp = crm_signals[sig]; crm_signals[sig] = NULL; mainloop_destroy_trigger((crm_trigger_t *) tmp); return TRUE; } static qb_array_t *gio_map = NULL; /* * libqb... */ struct gio_to_qb_poll { int32_t is_used; GIOChannel *channel; int32_t events; void * data; qb_ipcs_dispatch_fn_t fn; enum qb_loop_priority p; }; static gboolean gio_read_socket (GIOChannel *gio, GIOCondition condition, gpointer data) { struct gio_to_qb_poll *adaptor = (struct gio_to_qb_poll *)data; gint fd = g_io_channel_unix_get_fd(gio); crm_trace("%p.%d %d vs. %d (G_IO_IN)", data, fd, condition, (condition & G_IO_IN)); crm_trace("%p.%d %d vs. %d (G_IO_HUP)", data, fd, condition, (condition & G_IO_HUP)); if(condition & G_IO_NVAL) { crm_trace("Marking failed adaptor %p unused", adaptor); adaptor->is_used = QB_FALSE; } return (adaptor->fn(fd, condition, adaptor->data) == 0); } static void gio_destroy(gpointer data) { struct gio_to_qb_poll *adaptor = (struct gio_to_qb_poll *)data; crm_trace("Marking adaptor %p unused", adaptor); adaptor->is_used = QB_FALSE; } static int32_t gio_poll_dispatch_add(enum qb_loop_priority p, int32_t fd, int32_t evts, void *data, qb_ipcs_dispatch_fn_t fn) { struct gio_to_qb_poll *adaptor; GIOChannel *channel; int32_t res = 0; res = qb_array_index(gio_map, fd, (void**)&adaptor); if (res < 0) { crm_err("Array lookup failed for fd=%d: %d", fd, res); return res; } crm_trace("Adding fd=%d to mainloop as adapater %p", fd, adaptor); if (adaptor->is_used) { crm_err("Adapter for descriptor %d is still in-use", fd); return -EEXIST; } channel = g_io_channel_unix_new(fd); if (!channel) { crm_err("No memory left to add fd=%d", fd); return -ENOMEM; } /* Because unlike the poll() API, glib doesn't tell us about HUPs by default */ evts |= (G_IO_HUP|G_IO_NVAL|G_IO_ERR); adaptor->channel = channel; adaptor->fn = fn; adaptor->events = evts; adaptor->data = data; adaptor->p = p; adaptor->is_used = QB_TRUE; res = g_io_add_watch_full(channel, G_PRIORITY_DEFAULT, evts, gio_read_socket, adaptor, gio_destroy); crm_trace("Added to mainloop with gsource id=%d", res); if(res > 0) { return 0; } return -EINVAL; } static int32_t gio_poll_dispatch_mod(enum qb_loop_priority p, int32_t fd, int32_t evts, void *data, qb_ipcs_dispatch_fn_t fn) { return 0; } static int32_t gio_poll_dispatch_del(int32_t fd) { struct gio_to_qb_poll *adaptor; crm_trace("Looking for fd=%d", fd); if (qb_array_index(gio_map, fd, (void**)&adaptor) == 0) { crm_trace("Marking adaptor %p unused", adaptor); g_io_channel_unref(adaptor->channel); adaptor->is_used = QB_FALSE; } return 0; } struct qb_ipcs_poll_handlers gio_poll_funcs = { .job_add = NULL, .dispatch_add = gio_poll_dispatch_add, .dispatch_mod = gio_poll_dispatch_mod, .dispatch_del = gio_poll_dispatch_del, }; static enum qb_ipc_type pick_ipc_type(enum qb_ipc_type requested) { const char *env = getenv("PCMK_ipc_type"); if(env && strcmp("shared-mem", env) == 0) { return QB_IPC_SHM; } else if(env && strcmp("socket", env) == 0) { return QB_IPC_SOCKET; } else if(env && strcmp("posix", env) == 0) { return QB_IPC_POSIX_MQ; } else if(env && strcmp("sysv", env) == 0) { return QB_IPC_SYSV_MQ; } else if(requested == QB_IPC_NATIVE) { /* We prefer sockets actually */ return QB_IPC_SOCKET; } return requested; } qb_ipcs_service_t *mainloop_add_ipc_server( const char *name, enum qb_ipc_type type, struct qb_ipcs_service_handlers *callbacks) { int rc = 0; qb_ipcs_service_t* server = NULL; if(gio_map == NULL) { gio_map = qb_array_create_2(64, sizeof(struct gio_to_qb_poll), 1); } server = qb_ipcs_create(name, 0, pick_ipc_type(type), callbacks); qb_ipcs_poll_handlers_set(server, &gio_poll_funcs); rc = qb_ipcs_run(server); if (rc < 0) { crm_err("Could not start %s IPC server: %s (%d)", name, strerror(rc), rc); return NULL; } return server; } void mainloop_del_ipc_server(qb_ipcs_service_t *server) { qb_ipcs_destroy(server); } typedef struct mainloop_io_s { char *name; void *userdata; guint source; crm_ipc_t *ipc; GIOChannel *channel; int (*dispatch_fn_ipc)(const char *buffer, ssize_t length, gpointer userdata); int (*dispatch_fn_io) (gpointer userdata); void (*destroy_fn) (gpointer userdata); } mainloop_io_t; static gboolean mainloop_gio_callback(GIOChannel *gio, GIOCondition condition, gpointer data) { gboolean keep = TRUE; mainloop_io_t *client = data; if(condition & G_IO_IN) { if(client->ipc) { long rc = crm_ipc_read(client->ipc); crm_trace("New message from %s[%p] = %d", client->name, client, rc); if(rc <= 0) { crm_perror(LOG_TRACE, "Message acquisition failed: %ld", rc); } else if(client->dispatch_fn_ipc) { const char *buffer = crm_ipc_buffer(client->ipc); if(client->dispatch_fn_ipc(buffer, rc, client->userdata) < 0) { crm_trace("Connection to %s no longer required", client->name); keep = FALSE; } } } else { crm_trace("New message from %s[%p]", client->name, client); if(client->dispatch_fn_io) { if(client->dispatch_fn_io(client->userdata) < 0) { crm_trace("Connection to %s no longer required", client->name); keep = FALSE; } } } } if(client->ipc && crm_ipc_connected(client->ipc) == FALSE) { crm_err("Connection to %s[%p] closed", client->name, client); keep = FALSE; } else if(condition & G_IO_HUP) { crm_trace("Recieved G_IO_HUP for %s [%p] connection", client->name, client); keep = FALSE; } else if(condition & G_IO_NVAL) { crm_err("Recieved G_IO_NVAL for %s [%p] connection", client->name, client); keep = FALSE; } else if(condition & G_IO_ERR) { crm_err("Recieved G_IO_ERR for %s [%p] connection", client->name, client); keep = FALSE; } return keep; } static void mainloop_gio_destroy(gpointer c) { mainloop_io_t *client = c; crm_trace("Destroying %s[%p]", client->name, c); if(client->destroy_fn) { client->destroy_fn(client->userdata); } if(client->ipc) { crm_ipc_close(client->ipc); crm_ipc_destroy(client->ipc); } free(client->name); free(client); } mainloop_io_t * mainloop_add_ipc_client( const char *name, size_t max_size, void *userdata, struct ipc_client_callbacks *callbacks) { mainloop_io_t *client = NULL; crm_ipc_t *conn = crm_ipc_new(name, max_size); if(conn && crm_ipc_connect(conn)) { int32_t fd = crm_ipc_get_fd(conn); client = mainloop_add_fd(name, fd, userdata, NULL); client->ipc = conn; client->destroy_fn = callbacks->destroy; client->dispatch_fn_ipc = callbacks->dispatch; } if(conn && client == NULL) { crm_trace("Connection to %s failed", name); crm_ipc_close(conn); crm_ipc_destroy(conn); } return client; } void mainloop_del_ipc_client(mainloop_io_t *client) { mainloop_del_fd(client); } crm_ipc_t * mainloop_get_ipc_client(mainloop_io_t *client) { if(client) { return client->ipc; } return NULL; } mainloop_io_t * mainloop_add_fd( const char *name, int fd, void *userdata, struct mainloop_fd_callbacks *callbacks) { mainloop_io_t *client = NULL; if(fd > 0) { crm_malloc0(client, sizeof(mainloop_io_t)); client->name = crm_strdup(name); client->userdata = userdata; if(callbacks) { client->destroy_fn = callbacks->destroy; client->dispatch_fn_io = callbacks->dispatch; } client->channel = g_io_channel_unix_new(fd); client->source = g_io_add_watch_full( client->channel, G_PRIORITY_DEFAULT, (G_IO_IN|G_IO_HUP|G_IO_NVAL|G_IO_ERR), mainloop_gio_callback, client, mainloop_gio_destroy); crm_trace("Added connection %d for %s[%p].%d", client->source, client->name, client, fd); } return client; } void mainloop_del_fd(mainloop_io_t *client) { if(client != NULL) { - crm_trace("Removing client %s[%p]", client->name, client); - g_io_channel_unref(client->channel); + if (client->channel) { + crm_trace("Removing client %s[%p]", client->name, client); + g_io_channel_unref(client->channel); + client->channel = NULL; + } + if (client->source) { + g_source_remove(client->source); + client->source = 0; + } /* Results in mainloop_ipcc_destroy() being called once the source is removed from mainloop? */ } } + +pid_t +mainloop_get_child_pid(mainloop_child_t *child) +{ + return child->pid; +} + +int +mainloop_get_child_timeout(mainloop_child_t *child) +{ + return child->timeout; +} + +void * +mainloop_get_child_userdata(mainloop_child_t *child) +{ + return child->privatedata; +} + +void +mainloop_clear_child_userdata(mainloop_child_t *child) +{ + child->privatedata = NULL; +} + +static gboolean +child_timeout_callback(gpointer p) +{ + mainloop_child_t *child = p; + + child->timerid = 0; + if (child->timeout) { + crm_crit("%s process (PID %d) will not die!", child->desc, (int)child->pid); + return FALSE; + } + + child->timeout = TRUE; + crm_warn("%s process (PID %d) timed out", child->desc, (int)child->pid); + + if (kill(child->pid, SIGKILL) < 0) { + if (errno == ESRCH) { + /* Nothing left to do */ + return FALSE; + } + crm_perror(LOG_ERR, "kill(%d, KILL) failed", child->pid); + } + + child->timerid = g_timeout_add(5000, child_timeout_callback, child); + return FALSE; +} + +static void +mainloop_child_destroy(mainloop_child_t *child) +{ + if (child->timerid != 0) { + crm_trace("Removing timer %d", child->timerid); + g_source_remove(child->timerid); + child->timerid = 0; + } + + free(child->desc); + g_free(child); +} + +static void +child_death_dispatch(GPid pid, gint status, gpointer user_data) +{ + int signo = 0; + int exitcode = 0; + mainloop_child_t *child = user_data; + + crm_trace("Managed process %d exited: %p", pid, child); + + if (WIFEXITED(status)) { + exitcode = WEXITSTATUS(status); + crm_trace("Managed process %d (%s) exited with rc=%d", pid, + child->desc, exitcode); + + } else if (WIFSIGNALED(status)) { + signo = WTERMSIG(status); + crm_trace("Managed process %d (%s) exited with signal=%d", pid, + child->desc, signo); + } +#ifdef WCOREDUMP + if (WCOREDUMP(status)) { + crm_err("Managed process %d (%s) dumped core", pid, child->desc); + } +#endif + + if (child->callback) { + child->callback(child, status, signo, exitcode); + } + crm_trace("Removed process entry for %d", pid); + + mainloop_child_destroy(child); + return; +} + +/* Create/Log a new tracked process + * To track a process group, use -pid + */ +void +mainloop_add_child(pid_t pid, int timeout, const char *desc, void * privatedata, + void (*callback)(mainloop_child_t *p, int status, int signo, int exitcode)) +{ + mainloop_child_t *child = g_new(mainloop_child_t, 1); + + child->pid = pid; + child->timerid = 0; + child->timeout = FALSE; + child->desc = strdup(desc); + child->privatedata = privatedata; + child->callback = callback; + + if (timeout) { + child->timerid = g_timeout_add( + timeout, child_timeout_callback, child); + } + + child->watchid = g_child_watch_add(pid, child_death_dispatch, child); +} diff --git a/lib/services/Makefile.am b/lib/services/Makefile.am new file mode 100644 index 0000000000..92747b737c --- /dev/null +++ b/lib/services/Makefile.am @@ -0,0 +1,28 @@ +# Copyright (c) 2012 David Vossel +# +# 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 +# +# + +MAINTAINERCLEANFILES = Makefile.in + +INCLUDES = -I$(top_builddir)/include + +lib_LTLIBRARIES = libcrmservice.la + +libcrmservice_la_SOURCES = services.c services_linux.c +libcrmservice_la_LDFLAGS = -version-info 1:0:0 + +AM_CFLAGS = $(INCLUDES) diff --git a/lib/services/services.c b/lib/services/services.c new file mode 100644 index 0000000000..6a74198ebb --- /dev/null +++ b/lib/services/services.c @@ -0,0 +1,355 @@ +/* + * Copyright (C) 2010 Andrew Beekhof + * + * 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.1 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include + +#ifndef _GNU_SOURCE +#define _GNU_SOURCE +#endif + +#include +#include +#include + +#include +#include +#include +#include + +#include "crm/crm.h" +#include "crm/common/mainloop.h" +#include "crm/services.h" +#include "services_private.h" + +/* TODO: Develop a rollover strategy */ + +static int operations = 0; +GHashTable *recurring_actions = NULL; + +svc_action_t * +services_action_create(const char *name, const char *action, int interval, + int timeout) +{ + return resources_action_create(name, "lsb", NULL, name, action, interval, timeout, NULL); +} + +svc_action_t *resources_action_create( + const char *name, const char *standard, const char *provider, const char *agent, + const char *action, int interval, int timeout, GHashTable *params) +{ + svc_action_t *op; + + /* + * Do some up front sanity checks before we go off and + * build the svc_action_t instance. + */ + + if (crm_strlen_zero(name)) { + crm_err("A service or resource action must have a name."); + return NULL; + } + + if (crm_strlen_zero(standard)) { + crm_err("A service action must have a valid standard."); + return NULL; + } + + if (!strcasecmp(standard, "ocf") && crm_strlen_zero(provider)) { + crm_err("An OCF resource action must have a provider."); + return NULL; + } + + if (crm_strlen_zero(agent)) { + crm_err("A service or resource action must have an agent."); + return NULL; + } + + if (crm_strlen_zero(action)) { + crm_err("A service or resource action must specify an action."); + return NULL; + } + + /* + * Sanity checks passed, proceed! + */ + + op = calloc(1, sizeof(svc_action_t)); + op->opaque = calloc(1, sizeof(svc_action_private_t)); + op->rsc = strdup(name); + op->action = strdup(action); + op->interval = interval; + op->timeout = timeout; + op->standard = strdup(standard); + op->agent = strdup(agent); + op->sequence = ++operations; + if (asprintf(&op->id, "%s_%s_%d", name, action, interval) == -1) { + goto return_error; + } + + if(strcasecmp(standard, "ocf") == 0) { + op->provider = strdup(provider); + op->params = params; + + if (asprintf(&op->opaque->exec, "%s/resource.d/%s/%s", + OCF_ROOT_DIR, provider, agent) == -1) { + goto return_error; + } + op->opaque->args[0] = strdup(op->opaque->exec); + op->opaque->args[1] = strdup(action); + + } else if(strcasecmp(standard, "lsb") == 0) { + if (op->agent[0] == '/') { + /* if given an absolute path, use that instead + * of tacking on the LSB_ROOT_DIR path to the front */ + op->opaque->exec = crm_strdup(op->agent); + } else if (asprintf(&op->opaque->exec, "%s/%s", LSB_ROOT_DIR, op->agent) == -1) { + goto return_error; + } + op->opaque->args[0] = strdup(op->opaque->exec); + op->opaque->args[1] = strdup(op->action); + op->opaque->args[2] = NULL; + } else if(strcasecmp(standard, "systemd") == 0) { + char *service; + op->opaque->exec = strdup(SYSTEMCTL); + op->opaque->args[0] = strdup(SYSTEMCTL); + op->opaque->args[1] = strdup(action); + if (asprintf(&service, "%s.service", agent) == -1) { + goto return_error; + } + op->opaque->args[2] = service; + } else if(strcasecmp(standard, "service") == 0) { + op->opaque->exec = strdup(SERVICE_SCRIPT); + op->opaque->args[0] = strdup(SERVICE_SCRIPT); + op->opaque->args[1] = strdup(agent); + op->opaque->args[2] = strdup(action); + } else { + crm_err("Unknown resource standard: %s", standard); + services_action_free(op); + op = NULL; + } + + return op; + +return_error: + services_action_free(op); + + return NULL; +} + +svc_action_t * +services_action_create_generic(const char *exec, const char *args[]) +{ + svc_action_t *op; + unsigned int cur_arg; + + op = calloc(1, sizeof(*op)); + op->opaque = calloc(1, sizeof(svc_action_private_t)); + + op->opaque->exec = strdup(exec); + op->opaque->args[0] = strdup(exec); + + for (cur_arg = 1; args && args[cur_arg - 1]; cur_arg++) { + op->opaque->args[cur_arg] = strdup(args[cur_arg - 1]); + + if (cur_arg == DIMOF(op->opaque->args) - 1) { + crm_err("svc_action_t args list not long enough for '%s' execution request.", exec); + break; + } + } + + return op; +} + +void +services_action_free(svc_action_t *op) +{ + unsigned int i; + + if (op == NULL) { + return; + } + + if (op->opaque->stderr_gsource) { + mainloop_del_fd(op->opaque->stderr_gsource); + op->opaque->stderr_gsource = NULL; + } + + if (op->opaque->stdout_gsource) { + mainloop_del_fd(op->opaque->stdout_gsource); + op->opaque->stdout_gsource = NULL; + } + + free(op->id); + free(op->opaque->exec); + + for (i = 0; i < DIMOF(op->opaque->args); i++) { + free(op->opaque->args[i]); + } + + free(op->rsc); + free(op->action); + + free(op->standard); + free(op->agent); + free(op->provider); + + free(op->stdout_data); + free(op->stderr_data); + + if (op->params) { + g_hash_table_destroy(op->params); + op->params = NULL; + } + + free(op); +} + +gboolean +cancel_recurring_action(svc_action_t *op) +{ + if (op->pid) { + return FALSE; + } + + crm_info("Cancelling operation %s", op->id); + + if (recurring_actions) { + g_hash_table_remove(recurring_actions, op->id); + } + + if (op->opaque->repeat_timer) { + g_source_remove(op->opaque->repeat_timer); + } + + return TRUE; +} + +gboolean +services_action_cancel(const char *name, const char *action, int interval) +{ + svc_action_t* op = NULL; + char id[512]; + + snprintf(id, sizeof(id), "%s_%s_%d", name, action, interval); + + if (!(op = g_hash_table_lookup(recurring_actions, id))) { + return FALSE; + } + + if (cancel_recurring_action(op)) { + op->status = PCMK_LRM_OP_CANCELLED; + if (op->opaque->callback) { + op->opaque->callback(op); + } + services_action_free(op); + } else { + crm_info("Cancelling op: %s will occur once operation completes", id); + op->cancel = 1; + } + + return TRUE; +} + +gboolean +services_action_async(svc_action_t* op, void (*action_callback)(svc_action_t *)) +{ + if (action_callback) { + op->opaque->callback = action_callback; + } + + if (recurring_actions == NULL) { + recurring_actions = g_hash_table_new_full(g_str_hash, g_str_equal, + NULL, NULL); + } + + if (op->interval > 0) { + g_hash_table_replace(recurring_actions, op->id, op); + } + + return services_os_action_execute(op, FALSE); +} + +gboolean +services_action_sync(svc_action_t* op) +{ + gboolean rc = services_os_action_execute(op, TRUE); + crm_trace(" > %s_%s_%d: %s = %d", op->rsc, op->action, op->interval, + op->opaque->exec, op->rc); + if (op->stdout_data) { + crm_trace(" > stdout: %s", op->stdout_data); + } + if (op->stderr_data) { + crm_trace(" > stderr: %s", op->stderr_data); + } + return rc; +} + +GList * +get_directory_list(const char *root, gboolean files) +{ + return services_os_get_directory_list(root, files); +} + +GList * +services_list(void) +{ + return resources_list_agents("lsb", NULL); +} + +GList * +resources_list_standards(void) +{ + GList *standards = NULL; + standards = g_list_append(standards, strdup("ocf")); + standards = g_list_append(standards, strdup("lsb")); + if (g_file_test(SYSTEMCTL, G_FILE_TEST_IS_REGULAR)) { + standards = g_list_append(standards, strdup("systemd")); + } + if (g_file_test(SERVICE_SCRIPT, G_FILE_TEST_IS_REGULAR)) { + standards = g_list_append(standards, strdup("service")); + } + return standards; +} + +GList * +resources_list_providers(const char *standard) +{ + if (strcasecmp(standard, "ocf") == 0) { + return resources_os_list_ocf_providers(); + } + + return NULL; +} + +GList * +resources_list_agents(const char *standard, const char *provider) +{ + if (strcasecmp(standard, "ocf") == 0) { + return resources_os_list_ocf_agents(provider); + } else if (strcasecmp(standard, "lsb") == 0) { + return resources_os_list_lsb_agents(); + } else if (strcasecmp(standard, "systemd") == 0) { + return resources_os_list_systemd_services(); + } else if (strcasecmp(standard, "service") == 0) { + GList *tmp1 = resources_os_list_lsb_agents(); + GList *tmp2 = resources_os_list_systemd_services(); + return g_list_concat(tmp1, tmp2); + } + + return NULL; +} diff --git a/lib/services/services_linux.c b/lib/services/services_linux.c new file mode 100644 index 0000000000..3a31a4f74c --- /dev/null +++ b/lib/services/services_linux.c @@ -0,0 +1,571 @@ +/* + * Copyright (C) 2010 Andrew Beekhof + * + * 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.1 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include + +#ifndef _GNU_SOURCE +#define _GNU_SOURCE +#endif + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "crm/crm.h" +#include "crm/common/mainloop.h" +#include "crm/services.h" + +#include "services_private.h" + +static inline void +set_fd_opts(int fd, int opts) +{ + int flag; + if ((flag = fcntl(fd, F_GETFL)) >= 0) { + if (fcntl(fd, F_SETFL, flag | opts) < 0) { + crm_err( "fcntl() write failed"); + } + } else { + crm_err( "fcntl() read failed"); + } +} + +static gboolean +read_output(int fd, svc_action_t *op) +{ + char *data = NULL; + int rc = 0, len = 0; + gboolean is_err = FALSE; + char buf[500]; + static const size_t buf_read_len = sizeof(buf) - 1; + + crm_trace("%p", op); + + if (fd < 0) { + return FALSE; + } + + if (fd == op->opaque->stderr_fd) { + is_err = TRUE; + if (op->stderr_data) { + len = strlen(op->stderr_data); + data = op->stderr_data; + } + } else if (op->stdout_data) { + len = strlen(op->stdout_data); + data = op->stdout_data; + } + + do { + rc = read(fd, buf, buf_read_len); + if (rc > 0) { + buf[rc] = 0; + crm_realloc(data, len + rc + 1); + sprintf(data + len, "%s", buf); + len += rc; + } else if (errno != EINTR) { + /* error or EOF + * Cleanup happens in pipe_done() + */ + rc = FALSE; + break; + } + + } while (rc == buf_read_len || rc < 0); + + if (data != NULL && is_err) { + op->stderr_data = data; + } else if (data != NULL) { + op->stdout_data = data; + } + + return rc; +} + +static int +dispatch_stdout(gpointer userdata) +{ + svc_action_t* op = (svc_action_t *) userdata; + return read_output(op->opaque->stdout_fd, op); +} + +static int +dispatch_stderr(gpointer userdata) +{ + svc_action_t* op = (svc_action_t *) userdata; + return read_output(op->opaque->stderr_fd, op); +} + +static void +pipe_out_done(gpointer user_data) +{ + svc_action_t* op = (svc_action_t *) user_data; + + crm_trace("%p", op); + + op->opaque->stdout_gsource = NULL; + if (op->opaque->stdout_fd > STDOUT_FILENO) { + close(op->opaque->stdout_fd); + } + op->opaque->stdout_fd = -1; +} + +static void +pipe_err_done(gpointer user_data) +{ + svc_action_t* op = (svc_action_t *) user_data; + op->opaque->stderr_gsource = NULL; + if (op->opaque->stderr_fd > STDERR_FILENO) { + close(op->opaque->stderr_fd); + } + op->opaque->stderr_fd = -1; +} + +static struct mainloop_fd_callbacks stdout_callbacks = { + .dispatch = dispatch_stdout, + .destroy = pipe_out_done, +}; + +static struct mainloop_fd_callbacks stderr_callbacks = { + .dispatch = dispatch_stderr, + .destroy = pipe_err_done, +}; + +static void +set_ocf_env(const char *key, const char *value, gpointer user_data) +{ + if (setenv(key, value, 1) != 0) { + crm_perror(LOG_ERR, "setenv failed for key:%s and value:%s", key, value); + } +} + +static void +set_ocf_env_with_prefix(gpointer key, gpointer value, gpointer user_data) +{ + char buffer[500]; + snprintf(buffer, sizeof(buffer), "OCF_RESKEY_%s", (char *) key); + set_ocf_env(buffer, value, user_data); +} + +static void +add_OCF_env_vars(svc_action_t *op) +{ + if (!op->standard || strcasecmp("ocf", op->standard) != 0) { + return; + } + + if (op->params) { + g_hash_table_foreach(op->params, set_ocf_env_with_prefix, NULL); + } + + set_ocf_env("OCF_RA_VERSION_MAJOR", "1", NULL); + set_ocf_env("OCF_RA_VERSION_MINOR", "0", NULL); + set_ocf_env("OCF_ROOT", OCF_ROOT_DIR, NULL); + + if (op->rsc) { + set_ocf_env("OCF_RESOURCE_INSTANCE", op->rsc, NULL); + } + + if (op->agent != NULL) { + set_ocf_env("OCF_RESOURCE_TYPE", op->agent, NULL); + } + + /* Notes: this is not added to specification yet. Sept 10,2004 */ + if (op->provider != NULL) { + set_ocf_env("OCF_RESOURCE_PROVIDER", op->provider, NULL); + } +} + +static gboolean recurring_action_timer(gpointer data) +{ + svc_action_t *op = data; + crm_debug("Scheduling another invokation of %s", op->id); + + /* Clean out the old result */ + free(op->stdout_data); op->stdout_data = NULL; + free(op->stderr_data); op->stderr_data = NULL; + + services_action_async(op, NULL); + return FALSE; +} + +static void +operation_finished(mainloop_child_t *p, int status, int signo, int exitcode) +{ + char *next = NULL; + char *offset = NULL; + svc_action_t *op = mainloop_get_child_userdata(p); + int recurring = 0; + pid_t pid = mainloop_get_child_pid(p); + + mainloop_clear_child_userdata(p); + op->status = PCMK_LRM_OP_DONE; + CRM_ASSERT(op->pid == pid); + + if (op->opaque->stderr_gsource) { + /* Make sure we have read everything from the buffer. + * Depending on the priority mainloop gives the fd, operation_finished + * could occur before all the reads are done. Force the read now.*/ + dispatch_stderr(op); + } + + if (op->opaque->stdout_gsource) { + /* Make sure we have read everything from the buffer. + * Depending on the priority mainloop gives the fd, operation_finished + * could occur before all the reads are done. Force the read now.*/ + dispatch_stdout(op); + } + + if (signo) { + if (mainloop_get_child_timeout(p)) { + crm_warn("%s:%d - timed out after %dms", op->id, op->pid, + op->timeout); + op->status = PCMK_LRM_OP_TIMEOUT; + op->rc = PCMK_OCF_TIMEOUT; + + } else { + crm_warn("%s:%d - terminated with signal %d", op->id, op->pid, + signo); + op->status = PCMK_LRM_OP_ERROR; + op->rc = PCMK_OCF_SIGNAL; + } + + } else { + op->rc = exitcode; + crm_debug("%s:%d - exited with rc=%d", op->id, op->pid, exitcode); + + if (op->stdout_data) { + next = op->stdout_data; + do { + offset = next; + next = strchrnul(offset, '\n'); + crm_debug("%s:%d [ %.*s ]", op->id, op->pid, + (int) (next - offset), offset); + if (next[0] != 0) { + next++; + } + + } while (next != NULL && next[0] != 0); + } + + if (op->stderr_data) { + next = op->stderr_data; + do { + offset = next; + next = strchrnul(offset, '\n'); + crm_notice("%s:%d [ %.*s ]", op->id, op->pid, + (int) (next - offset), offset); + if (next[0] != 0) { + next++; + } + + } while (next != NULL && next[0] != 0); + } + } + + op->pid = 0; + + if (op->interval) { + if (op->cancel) { + op->status = PCMK_LRM_OP_CANCELLED; + cancel_recurring_action(op); + } else { + recurring = 1; + op->opaque->repeat_timer = g_timeout_add(op->interval, + recurring_action_timer, + (void *) op); + } + } + + if (op->opaque->callback) { + op->opaque->callback(op); + } + + if (!recurring) { + /* + * If this is a recurring action, do not free explicitly. + * It will get freed whenever the action gets cancelled. + */ + services_action_free(op); + } +} + +gboolean +services_os_action_execute(svc_action_t* op, gboolean synchronous) +{ + int rc, lpc; + int stdout_fd[2]; + int stderr_fd[2]; + + if (pipe(stdout_fd) < 0) { + crm_err( "pipe() failed"); + } + + if (pipe(stderr_fd) < 0) { + crm_err( "pipe() failed"); + } + + op->pid = fork(); + switch (op->pid) { + case -1: + crm_err( "fork() failed"); + close(stdout_fd[0]); + close(stdout_fd[1]); + close(stderr_fd[0]); + close(stderr_fd[1]); + return FALSE; + + case 0: /* Child */ + /* Man: The call setpgrp() is equivalent to setpgid(0,0) + * _and_ compiles on BSD variants too + * need to investigate if it works the same too. + */ + setpgid(0, 0); + close(stdout_fd[0]); + close(stderr_fd[0]); + if (STDOUT_FILENO != stdout_fd[1]) { + if (dup2(stdout_fd[1], STDOUT_FILENO) != STDOUT_FILENO) { + crm_err( "dup2() failed (stdout)"); + } + close(stdout_fd[1]); + } + if (STDERR_FILENO != stderr_fd[1]) { + if (dup2(stderr_fd[1], STDERR_FILENO) != STDERR_FILENO) { + crm_err( "dup2() failed (stderr)"); + } + close(stderr_fd[1]); + } + + /* close all descriptors except stdin/out/err and channels to logd */ + for (lpc = getdtablesize() - 1; lpc > STDERR_FILENO; lpc--) { + close(lpc); + } + + /* Setup environment correctly */ + add_OCF_env_vars(op); + + /* execute the RA */ + execvp(op->opaque->exec, op->opaque->args); + + switch (errno) { /* see execve(2) */ + case ENOENT: /* No such file or directory */ + case EISDIR: /* Is a directory */ + rc = PCMK_OCF_NOT_INSTALLED; + break; + case EACCES: /* permission denied (various errors) */ + rc = PCMK_OCF_INSUFFICIENT_PRIV; + break; + default: + rc = PCMK_OCF_UNKNOWN_ERROR; + break; + } + _exit(rc); + } + + /* Only the parent reaches here */ + close(stdout_fd[1]); + close(stderr_fd[1]); + + op->opaque->stdout_fd = stdout_fd[0]; + set_fd_opts(op->opaque->stdout_fd, O_NONBLOCK); + + op->opaque->stderr_fd = stderr_fd[0]; + set_fd_opts(op->opaque->stderr_fd, O_NONBLOCK); + + if (synchronous) { + int status = 0; + int timeout = (1 + op->timeout) / 1000; + crm_trace("Waiting for %d", op->pid); + while (timeout > 0 && waitpid(op->pid, &status, WNOHANG) <= 0) { + sleep(1); + read_output(op->opaque->stdout_fd, op); + read_output(op->opaque->stderr_fd, op); + timeout--; + } + + crm_trace("Child done: %d", op->pid); + if (timeout == 0) { + int killrc = kill(op->pid, 9 /*SIGKILL*/); + + op->status = PCMK_LRM_OP_TIMEOUT; + crm_warn("%s:%d - timed out after %dms", op->id, op->pid, + op->timeout); + + if (killrc && errno != ESRCH) { + crm_err("kill(%d, KILL) failed: %d", op->pid, errno); + } + + } else if (WIFEXITED(status)) { + op->status = PCMK_LRM_OP_DONE; + op->rc = WEXITSTATUS(status); + crm_info("Managed %s process %d exited with rc=%d", op->id, op->pid, + op->rc); + + } else if (WIFSIGNALED(status)) { + int signo = WTERMSIG(status); + op->status = PCMK_LRM_OP_ERROR; + crm_err("Managed %s process %d exited with signal=%d", op->id, + op->pid, signo); + } +#ifdef WCOREDUMP + if (WCOREDUMP(status)) { + crm_err("Managed %s process %d dumped core", op->id, op->pid); + } +#endif + + read_output(op->opaque->stdout_fd, op); + read_output(op->opaque->stderr_fd, op); + + } else { + crm_trace("Async waiting for %d - %s", op->pid, op->opaque->exec); + mainloop_add_child(op->pid, op->timeout, op->id, op, + operation_finished); + + op->opaque->stdout_gsource = mainloop_add_fd(op->id, + op->opaque->stdout_fd, + op, + &stdout_callbacks); + + op->opaque->stderr_gsource = mainloop_add_fd(op->id, + op->opaque->stderr_fd, + op, + &stderr_callbacks); + } + + return TRUE; +} + +GList * +services_os_get_directory_list(const char *root, gboolean files) +{ + GList *list = NULL; + struct dirent **namelist; + int entries = 0, lpc = 0; + char buffer[PATH_MAX]; + + entries = scandir(root, &namelist, NULL, alphasort); + if (entries <= 0) { + return list; + } + + for (lpc = 0; lpc < entries; lpc++) { + struct stat sb; + if ('.' == namelist[lpc]->d_name[0]) { + free(namelist[lpc]); + continue; + } + + snprintf(buffer, sizeof(buffer), "%s/%s", root, namelist[lpc]->d_name); + + stat(buffer, &sb); + if (S_ISDIR(sb.st_mode)) { + if (files) { + free(namelist[lpc]); + continue; + } + + } else if (S_ISREG(sb.st_mode)) { + if (files == FALSE) { + free(namelist[lpc]); + continue; + + } else if ((sb.st_mode & S_IXUSR) == 0 + && (sb.st_mode & S_IXGRP) == 0 + && (sb.st_mode & S_IXOTH) == 0) { + free(namelist[lpc]); + continue; + } + } + + list = g_list_append(list, strdup(namelist[lpc]->d_name)); + + free(namelist[lpc]); + } + + free(namelist); + return list; +} + +GList * +resources_os_list_lsb_agents(void) +{ + return get_directory_list(LSB_ROOT_DIR, TRUE); +} + +GList * +resources_os_list_ocf_providers(void) +{ + return get_directory_list(OCF_ROOT_DIR "/resource.d", FALSE); +} + +GList * +resources_os_list_ocf_agents(const char *provider) +{ + if (provider) { + char buffer[500]; + snprintf(buffer, sizeof(buffer), "%s/resource.d/%s", OCF_ROOT_DIR, + provider); + return get_directory_list(buffer, TRUE); + } + return NULL; +} + +GList * +resources_os_list_systemd_services(void) +{ + GList *list = NULL; + char *ptr, *service, *end; + svc_action_t *action; + int len; + const char *args[] = { "list-units", "--all", "--type=service", "--full", + "--no-pager", NULL }; + + if (!(action = services_action_create_generic(SYSTEMCTL, args))) { + return NULL; + } + + action->timeout = 5000; + + if (!services_action_sync(action)) { + services_action_free(action); + return NULL; + } + ptr = action->stdout_data; + // Skip first line with column labels + while ((ptr = strchr(ptr, '\n')) != NULL) { + // Skip the line break + ptr++; + + // Read beggining of the line until ".service" + if (!(end = strstr(ptr, ".service"))) + break; + // Length of service name + len = end - ptr; + // Append the name to the list of services + crm_malloc0(service, sizeof(char) * (len + 1)); + service = strncpy(service, ptr, len); + list = g_list_append(list, service); + } + services_action_free(action); + return list; +} diff --git a/lib/services/services_private.h b/lib/services/services_private.h new file mode 100644 index 0000000000..f585d6e32e --- /dev/null +++ b/lib/services/services_private.h @@ -0,0 +1,57 @@ +/* + * Copyright (C) 2010 - 2011, Red Hat, Inc. + * + * 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.1 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef __MH_SERVICES_PRIVATE_H__ +#define __MH_SERVICES_PRIVATE_H__ + +struct svc_action_private_s { + char *exec; + char *args[7]; + + guint repeat_timer; + void (*callback)(svc_action_t *op); + + int stderr_fd; + mainloop_io_t *stderr_gsource; + + int stdout_fd; + mainloop_io_t *stdout_gsource; +}; + +GList * +services_os_get_directory_list(const char *root, gboolean files); + +gboolean +services_os_action_execute(svc_action_t *op, gboolean synchronous); + +GList * +resources_os_list_lsb_agents(void); + +GList * +resources_os_list_ocf_providers(void); + +GList * +resources_os_list_ocf_agents(const char *provider); + +GList * +resources_os_list_systemd_services(void); + +gboolean +cancel_recurring_action(svc_action_t *op); + +#endif /* __MH_SERVICES_PRIVATE_H__ */