diff --git a/lib/services/dbus.c b/lib/services/dbus.c index 95984e4a12..b4b5afec52 100644 --- a/lib/services/dbus.c +++ b/lib/services/dbus.c @@ -1,791 +1,791 @@ /* * Copyright 2014-2020 the Pacemaker project contributors * * The version control history for this file may have further details. * * This source code is licensed under the GNU Lesser General Public License * version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY. */ #include #include #include #include #include /* * DBus message dispatch */ // List of DBus connections (DBusConnection*) with messages available static GList *conn_dispatches = NULL; /*! * \internal * \brief Save an indication that DBus messages need dispatching * * \param[in] connection DBus connection with messages to dispatch * \param[in] new_status Dispatch status as reported by DBus library * \param[in] data Ignored * * \note This is suitable to be used as a DBus status dispatch function. * As mentioned in the DBus documentation, dbus_connection_dispatch() must * not be called from within this function, and any re-entrancy is a bad * idea. Instead, this should just flag the main loop that messages need * to be dispatched. */ static void update_dispatch_status(DBusConnection *connection, DBusDispatchStatus new_status, void *data) { if (new_status == DBUS_DISPATCH_DATA_REMAINS) { crm_trace("DBus connection has messages available for dispatch"); conn_dispatches = g_list_prepend(conn_dispatches, connection); } else { crm_trace("DBus connection has no messages available for dispatch " "(status %d)", new_status); } } /*! * \internal * \brief Dispatch available messages on all DBus connections */ static void dispatch_messages(void) { for (GList *gIter = conn_dispatches; gIter != NULL; gIter = gIter->next) { DBusConnection *connection = gIter->data; while (dbus_connection_get_dispatch_status(connection) == DBUS_DISPATCH_DATA_REMAINS) { crm_trace("Dispatching available messages on DBus connection"); dbus_connection_dispatch(connection); } } g_list_free(conn_dispatches); conn_dispatches = NULL; } /* * DBus file descriptor watches * * The DBus library allows the caller to register functions for the library to * use for file descriptor notifications via a main loop. */ /* Copied from dbus-watch.c */ static const char* dbus_watch_flags_to_string(int flags) { const char *watch_type; if ((flags & DBUS_WATCH_READABLE) && (flags & DBUS_WATCH_WRITABLE)) { watch_type = "read/write"; } else if (flags & DBUS_WATCH_READABLE) { watch_type = "read"; } else if (flags & DBUS_WATCH_WRITABLE) { watch_type = "write"; } else { watch_type = "neither read nor write"; } return watch_type; } /*! * \internal * \brief Dispatch data available on a DBus file descriptor watch * * \param[in] userdata Pointer to the DBus watch * * \return Always 0 * \note This is suitable for use as a dispatch function in * struct mainloop_fd_callbacks (which means that a negative return value * would indicate the file descriptor is no longer required). */ static int dispatch_fd_data(gpointer userdata) { bool oom = FALSE; DBusWatch *watch = userdata; int flags = dbus_watch_get_flags(watch); bool enabled = dbus_watch_get_enabled (watch); crm_trace("Dispatching DBus watch for file descriptor %d " "with flags 0x%x (%s)", dbus_watch_get_unix_fd(watch), flags, dbus_watch_flags_to_string(flags)); if (enabled && (flags & (DBUS_WATCH_READABLE|DBUS_WATCH_WRITABLE))) { oom = !dbus_watch_handle(watch, flags); } else if (enabled) { oom = !dbus_watch_handle(watch, DBUS_WATCH_ERROR); } if (flags != dbus_watch_get_flags(watch)) { flags = dbus_watch_get_flags(watch); crm_trace("Dispatched DBus file descriptor watch: now 0x%x (%s)", flags, dbus_watch_flags_to_string(flags)); } if (oom) { crm_crit("Could not dispatch DBus file descriptor data: Out of memory"); } else { dispatch_messages(); } return 0; } static void watch_fd_closed(gpointer userdata) { crm_trace("DBus watch for file descriptor %d is now closed", dbus_watch_get_unix_fd((DBusWatch *) userdata)); } static struct mainloop_fd_callbacks pcmk_dbus_cb = { .dispatch = dispatch_fd_data, .destroy = watch_fd_closed, }; static dbus_bool_t add_dbus_watch(DBusWatch *watch, void *data) { int fd = dbus_watch_get_unix_fd(watch); mainloop_io_t *client = mainloop_add_fd("dbus", G_PRIORITY_DEFAULT, fd, watch, &pcmk_dbus_cb); crm_trace("Added DBus watch for file descriptor %d", fd); dbus_watch_set_data(watch, client, NULL); return TRUE; } static void toggle_dbus_watch(DBusWatch *watch, void *data) { // @TODO Should this do something more? crm_debug("DBus watch for file descriptor %d is now %s", dbus_watch_get_unix_fd(watch), (dbus_watch_get_enabled(watch)? "enabled" : "disabled")); } static void remove_dbus_watch(DBusWatch *watch, void *data) { crm_trace("Removed DBus watch for file descriptor %d", dbus_watch_get_unix_fd(watch)); mainloop_del_fd((mainloop_io_t *) dbus_watch_get_data(watch)); } static void register_watch_functions(DBusConnection *connection) { dbus_connection_set_watch_functions(connection, add_dbus_watch, remove_dbus_watch, toggle_dbus_watch, NULL, NULL); } /* * DBus main loop timeouts * * The DBus library allows the caller to register functions for the library to * use for managing timers via a main loop. */ static gboolean timer_popped(gpointer data) { crm_debug("%dms DBus timer expired", dbus_timeout_get_interval((DBusTimeout *) data)); dbus_timeout_handle(data); return FALSE; } static dbus_bool_t add_dbus_timer(DBusTimeout *timeout, void *data) { int interval_ms = dbus_timeout_get_interval(timeout); guint id = g_timeout_add(interval_ms, timer_popped, timeout); if (id) { dbus_timeout_set_data(timeout, GUINT_TO_POINTER(id), NULL); } crm_trace("Added %dms DBus timer", interval_ms); return TRUE; } static void remove_dbus_timer(DBusTimeout *timeout, void *data) { void *vid = dbus_timeout_get_data(timeout); guint id = GPOINTER_TO_UINT(vid); crm_trace("Removing %dms DBus timer", dbus_timeout_get_interval(timeout)); if (id) { g_source_remove(id); dbus_timeout_set_data(timeout, 0, NULL); } } static void toggle_dbus_timer(DBusTimeout *timeout, void *data) { bool enabled = dbus_timeout_get_enabled(timeout); crm_trace("Toggling %dms DBus timer %s", dbus_timeout_get_interval(timeout), (enabled? "off": "on")); if (enabled) { add_dbus_timer(timeout, data); } else { remove_dbus_timer(timeout, data); } } static void register_timer_functions(DBusConnection *connection) { dbus_connection_set_timeout_functions(connection, add_dbus_timer, remove_dbus_timer, toggle_dbus_timer, NULL, NULL); } /* * General DBus utilities */ DBusConnection * pcmk_dbus_connect(void) { DBusError err; DBusConnection *connection; dbus_error_init(&err); connection = dbus_bus_get(DBUS_BUS_SYSTEM, &err); if (dbus_error_is_set(&err)) { crm_err("Could not connect to DBus: %s", err.message); dbus_error_free(&err); return NULL; } if (connection == NULL) { return NULL; } /* Tell libdbus not to exit the process when a disconnect happens. This * defaults to FALSE but is toggled on by the dbus_bus_get() call above. */ dbus_connection_set_exit_on_disconnect(connection, FALSE); // Set custom handlers for various situations register_timer_functions(connection); register_watch_functions(connection); dbus_connection_set_dispatch_status_function(connection, update_dispatch_status, NULL, NULL); // Call the dispatch function to check for any messages waiting already update_dispatch_status(connection, dbus_connection_get_dispatch_status(connection), NULL); return connection; } void pcmk_dbus_disconnect(DBusConnection *connection) { /* Per the DBus documentation, connections created with * dbus_connection_open() are owned by libdbus and should never be closed. * * @TODO Should we call dbus_connection_unref() here? */ return; } /*! * \internal * \brief Check whether a DBus reply indicates an error occurred * * \param[in] pending If non-NULL, indicates that a DBus request was sent * \param[in] reply Reply received from DBus * \param[out] ret If non-NULL, will be set to DBus error, if any * * \return TRUE if an error was found, FALSE otherwise * * \note Following the DBus API convention, a TRUE return is exactly equivalent * to ret being set. If ret is provided and this function returns TRUE, * the caller is responsible for calling dbus_error_free() on ret when * done using it. */ bool pcmk_dbus_find_error(DBusPendingCall *pending, DBusMessage *reply, DBusError *ret) { DBusError error; dbus_error_init(&error); if (pending == NULL) { dbus_set_error_const(&error, "org.clusterlabs.pacemaker.NoRequest", "No request sent"); } else if (reply == NULL) { dbus_set_error_const(&error, "org.clusterlabs.pacemaker.NoReply", "No reply"); } else { DBusMessageIter args; int dtype = dbus_message_get_type(reply); switch (dtype) { case DBUS_MESSAGE_TYPE_METHOD_RETURN: { char *sig = NULL; dbus_message_iter_init(reply, &args); crm_trace("Received DBus reply with argument type '%s'", (sig = dbus_message_iter_get_signature(&args))); if (sig != NULL) { dbus_free(sig); } } break; case DBUS_MESSAGE_TYPE_INVALID: dbus_set_error_const(&error, "org.clusterlabs.pacemaker.InvalidReply", "Invalid reply"); break; case DBUS_MESSAGE_TYPE_METHOD_CALL: dbus_set_error_const(&error, "org.clusterlabs.pacemaker.InvalidReply.Method", "Invalid reply (method call)"); break; case DBUS_MESSAGE_TYPE_SIGNAL: dbus_set_error_const(&error, "org.clusterlabs.pacemaker.InvalidReply.Signal", "Invalid reply (signal)"); break; case DBUS_MESSAGE_TYPE_ERROR: dbus_set_error_from_message(&error, reply); break; default: dbus_set_error(&error, "org.clusterlabs.pacemaker.InvalidReply.Type", "Unknown reply type %d", dtype); } } if (dbus_error_is_set(&error)) { crm_trace("DBus reply indicated error '%s' (%s)", error.name, error.message); if (ret) { dbus_error_init(ret); dbus_move_error(&error, ret); } else { dbus_error_free(&error); } return TRUE; } return FALSE; } /*! * \internal * \brief Send a DBus request and wait for the reply * * \param[in] msg DBus request to send * \param[in] connection DBus connection to use * \param[out] error If non-NULL, will be set to error, if any * \param[in] timeout Timeout to use for request * * \return DBus reply * * \note If error is non-NULL, it is initialized, so the caller may always use * dbus_error_is_set() to determine whether an error occurred; the caller * is responsible for calling dbus_error_free() in this case. */ DBusMessage * pcmk_dbus_send_recv(DBusMessage *msg, DBusConnection *connection, DBusError *error, int timeout) { const char *method = NULL; DBusMessage *reply = NULL; DBusPendingCall* pending = NULL; CRM_ASSERT(dbus_message_get_type (msg) == DBUS_MESSAGE_TYPE_METHOD_CALL); method = dbus_message_get_member (msg); /* Ensure caller can reliably check whether error is set */ if (error) { dbus_error_init(error); } if (timeout <= 0) { /* DBUS_TIMEOUT_USE_DEFAULT (-1) tells DBus to use a sane default */ timeout = DBUS_TIMEOUT_USE_DEFAULT; } // send message and get a handle for a reply if (!dbus_connection_send_with_reply(connection, msg, &pending, timeout)) { if (error) { dbus_set_error(error, "org.clusterlabs.pacemaker.SendFailed", "Could not queue DBus '%s' request", method); } return NULL; } dbus_connection_flush(connection); if (pending) { /* block until we receive a reply */ dbus_pending_call_block(pending); /* get the reply message */ reply = dbus_pending_call_steal_reply(pending); } (void) pcmk_dbus_find_error(pending, reply, error); if (pending) { /* free the pending message handle */ dbus_pending_call_unref(pending); } return reply; } /*! * \internal * \brief Send a DBus message with a callback for the reply * * \param[in] msg DBus message to send * \param[in,out] connection DBus connection to send on * \param[in] done Function to call when pending call completes * \param[in] user_data Data to pass to done callback * * \return Handle for reply on success, NULL on error * \note The caller can assume that the done callback is called always and * only when the return value is non-NULL. (This allows the caller to * know where it should free dynamically allocated user_data.) */ DBusPendingCall * pcmk_dbus_send(DBusMessage *msg, DBusConnection *connection, void (*done)(DBusPendingCall *pending, void *user_data), void *user_data, int timeout) { const char *method = NULL; DBusPendingCall* pending = NULL; CRM_ASSERT(done); CRM_ASSERT(dbus_message_get_type(msg) == DBUS_MESSAGE_TYPE_METHOD_CALL); method = dbus_message_get_member(msg); if (timeout <= 0) { /* DBUS_TIMEOUT_USE_DEFAULT (-1) tells DBus to use a sane default */ timeout = DBUS_TIMEOUT_USE_DEFAULT; } // send message and get a handle for a reply if (!dbus_connection_send_with_reply(connection, msg, &pending, timeout)) { crm_err("Could not send DBus %s message: failed", method); return NULL; } else if (pending == NULL) { crm_err("Could not send DBus %s message: connection may be closed", method); return NULL; } if (dbus_pending_call_get_completed(pending)) { crm_info("DBus %s message completed too soon", method); /* Calling done() directly in this case instead of setting notify below * breaks things */ } if (!dbus_pending_call_set_notify(pending, done, user_data, NULL)) { return NULL; } return pending; } bool pcmk_dbus_type_check(DBusMessage *msg, DBusMessageIter *field, int expected, const char *function, int line) { int dtype = 0; DBusMessageIter lfield; if (field == NULL) { if (dbus_message_iter_init(msg, &lfield)) { field = &lfield; } } if (field == NULL) { - do_crm_log_alias(LOG_ERR, __FILE__, function, line, + do_crm_log_alias(LOG_INFO, __FILE__, function, line, "DBus reply has empty parameter list (expected '%c')", expected); return FALSE; } dtype = dbus_message_iter_get_arg_type(field); if (dtype != expected) { DBusMessageIter args; char *sig; dbus_message_iter_init(msg, &args); sig = dbus_message_iter_get_signature(&args); - do_crm_log_alias(LOG_ERR, __FILE__, function, line, + do_crm_log_alias(LOG_INFO, __FILE__, function, line, "DBus reply has unexpected type " "(expected '%c' not '%c' in '%s')", expected, dtype, sig); dbus_free(sig); return FALSE; } return TRUE; } /* * Property queries */ /* DBus APIs often provide queryable properties that use this standard * interface. See: * https://dbus.freedesktop.org/doc/dbus-specification.html#standard-interfaces-properties */ #define BUS_PROPERTY_IFACE "org.freedesktop.DBus.Properties" // Callback prototype for when a DBus property query result is received typedef void (*property_callback_func)(const char *name, // Property name const char *value, // Property value void *userdata); // Caller-provided data // Data needed by DBus property queries struct property_query { char *name; // Property name being queried char *target; // Name of DBus bus that query should be sent to char *object; // DBus object path for object with the property void *userdata; // Caller-provided data to supply to callback property_callback_func callback; // Function to call when result is received }; static void free_property_query(struct property_query *data) { free(data->target); free(data->object); free(data->name); free(data); } static char * handle_query_result(DBusMessage *reply, struct property_query *data) { DBusError error; char *output = NULL; DBusMessageIter dict; DBusMessageIter args; if (pcmk_dbus_find_error((void*)&error, reply, &error)) { crm_err("DBus query of %s for %s properties failed: %s", data->target, data->object, error.message); dbus_error_free(&error); goto cleanup; } dbus_message_iter_init(reply, &args); if (!pcmk_dbus_type_check(reply, &args, DBUS_TYPE_ARRAY, __func__, __LINE__)) { crm_err("DBus query of %s for %s properties failed: invalid reply", data->target, data->object); goto cleanup; } dbus_message_iter_recurse(&args, &dict); while (dbus_message_iter_get_arg_type (&dict) != DBUS_TYPE_INVALID) { DBusMessageIter sv; DBusMessageIter v; DBusBasicValue name; DBusBasicValue value; if (!pcmk_dbus_type_check(reply, &dict, DBUS_TYPE_DICT_ENTRY, __func__, __LINE__)) { dbus_message_iter_next (&dict); continue; } dbus_message_iter_recurse(&dict, &sv); while (dbus_message_iter_get_arg_type (&sv) != DBUS_TYPE_INVALID) { int dtype = dbus_message_iter_get_arg_type(&sv); switch (dtype) { case DBUS_TYPE_STRING: dbus_message_iter_get_basic(&sv, &name); if (!pcmk__str_eq(data->name, name.str, pcmk__str_null_matches)) { dbus_message_iter_next (&sv); /* Skip the value */ } break; case DBUS_TYPE_VARIANT: dbus_message_iter_recurse(&sv, &v); if (pcmk_dbus_type_check(reply, &v, DBUS_TYPE_STRING, __func__, __LINE__)) { dbus_message_iter_get_basic(&v, &value); crm_trace("DBus query of %s for %s properties: %s='%s'", data->target, data->object, name.str, value.str); if (data->callback) { data->callback(name.str, value.str, data->userdata); } else { free(output); output = strdup(value.str); } if (data->name) { goto cleanup; } } break; default: pcmk_dbus_type_check(reply, &sv, DBUS_TYPE_STRING, __func__, __LINE__); } dbus_message_iter_next (&sv); } dbus_message_iter_next (&dict); } if (data->name && data->callback) { crm_trace("DBus query of %s for %s properties: No value for %s", data->target, data->object, data->name); data->callback(data->name, NULL, data->userdata); } cleanup: free_property_query(data); return output; } static void async_query_result_cb(DBusPendingCall *pending, void *user_data) { DBusMessage *reply = NULL; char *value = NULL; if (pending) { reply = dbus_pending_call_steal_reply(pending); } value = handle_query_result(reply, user_data); free(value); if (reply) { dbus_message_unref(reply); } } /*! * \internal * \brief Query a property on a DBus object * * \param[in] connection An active connection to DBus * \param[in] target DBus name that the query should be sent to * \param[in] obj DBus object path for object with the property * \param[in] iface DBus interface for property to query * \param[in] name Name of property to query * \param[in] callback If not NULL, perform query asynchronously, and call * this function when query completes * \param[in] userdata Caller-provided data to provide to \p callback * \param[out] pending If \p callback is not NULL, this will be set to the * handle for the reply (or NULL on error) * \param[in] timeout Abort query if it takes longer than this (ms) * * \return NULL if \p callback is non-NULL (i.e. asynchronous), otherwise a * newly allocated string with property value * \note It is the caller's responsibility to free the result with free(). */ char * pcmk_dbus_get_property(DBusConnection *connection, const char *target, const char *obj, const gchar * iface, const char *name, property_callback_func callback, void *userdata, DBusPendingCall **pending, int timeout) { DBusMessage *msg; const char *method = "GetAll"; char *output = NULL; struct property_query *query_data = NULL; crm_trace("Querying DBus %s for %s properties (%s)", target, obj, (name? name : "all")); // Create a new message to use to invoke method msg = dbus_message_new_method_call(target, obj, BUS_PROPERTY_IFACE, method); if (NULL == msg) { crm_err("DBus query of %s for %s properties failed: " "Unable to create message", target, obj); return NULL; } CRM_LOG_ASSERT(dbus_message_append_args(msg, DBUS_TYPE_STRING, &iface, DBUS_TYPE_INVALID)); query_data = malloc(sizeof(struct property_query)); if (query_data == NULL) { crm_crit("DBus query of %s for %s properties failed: Out of memory", target, obj); return NULL; } query_data->target = strdup(target); query_data->object = strdup(obj); query_data->callback = callback; query_data->userdata = userdata; query_data->name = NULL; if (name) { query_data->name = strdup(name); } if (query_data->callback) { DBusPendingCall *local_pending; local_pending = pcmk_dbus_send(msg, connection, async_query_result_cb, query_data, timeout); if (local_pending == NULL) { // async_query_result_cb() was not called in this case free_property_query(query_data); query_data = NULL; } if (pending) { *pending = local_pending; } } else { DBusMessage *reply = pcmk_dbus_send_recv(msg, connection, NULL, timeout); output = handle_query_result(reply, query_data); if (reply) { dbus_message_unref(reply); } } dbus_message_unref(msg); return output; } diff --git a/lib/services/systemd.c b/lib/services/systemd.c index 0f2f5145cc..a2826ac3b7 100644 --- a/lib/services/systemd.c +++ b/lib/services/systemd.c @@ -1,861 +1,866 @@ /* * Copyright 2012-2020 the Pacemaker project contributors * * The version control history for this file may have further details. * * This source code is licensed under the GNU Lesser General Public License * version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY. */ #include #include #include #include #include #include #include #include #include #include gboolean systemd_unit_exec_with_unit(svc_action_t * op, const char *unit); #define BUS_NAME "org.freedesktop.systemd1" #define BUS_NAME_MANAGER BUS_NAME ".Manager" #define BUS_NAME_UNIT BUS_NAME ".Unit" #define BUS_PATH "/org/freedesktop/systemd1" static inline DBusMessage * systemd_new_method(const char *method) { crm_trace("Calling: %s on " BUS_NAME_MANAGER, method); return dbus_message_new_method_call(BUS_NAME, BUS_PATH, BUS_NAME_MANAGER, method); } /* * Functions to manage a static DBus connection */ static DBusConnection* systemd_proxy = NULL; static inline DBusPendingCall * systemd_send(DBusMessage *msg, void(*done)(DBusPendingCall *pending, void *user_data), void *user_data, int timeout) { return pcmk_dbus_send(msg, systemd_proxy, done, user_data, timeout); } static inline DBusMessage * systemd_send_recv(DBusMessage *msg, DBusError *error, int timeout) { return pcmk_dbus_send_recv(msg, systemd_proxy, error, timeout); } /*! * \internal * \brief Send a method to systemd without arguments, and wait for reply * * \param[in] method Method to send * * \return Systemd reply on success, NULL (and error will be logged) otherwise * * \note The caller must call dbus_message_unref() on the reply after * handling it. */ static DBusMessage * systemd_call_simple_method(const char *method) { DBusMessage *msg = systemd_new_method(method); DBusMessage *reply = NULL; DBusError error; /* Don't call systemd_init() here, because that calls this */ CRM_CHECK(systemd_proxy, return NULL); if (msg == NULL) { crm_err("Could not create message to send %s to systemd", method); return NULL; } dbus_error_init(&error); reply = systemd_send_recv(msg, &error, DBUS_TIMEOUT_USE_DEFAULT); dbus_message_unref(msg); if (dbus_error_is_set(&error)) { crm_err("Could not send %s to systemd: %s (%s)", method, error.message, error.name); dbus_error_free(&error); return NULL; } else if (reply == NULL) { crm_err("Could not send %s to systemd: no reply received", method); return NULL; } return reply; } static gboolean systemd_init(void) { static int need_init = 1; // https://dbus.freedesktop.org/doc/api/html/group__DBusConnection.html if (systemd_proxy && dbus_connection_get_is_connected(systemd_proxy) == FALSE) { crm_warn("Connection to System DBus is closed. Reconnecting..."); pcmk_dbus_disconnect(systemd_proxy); systemd_proxy = NULL; need_init = 1; } if (need_init) { need_init = 0; systemd_proxy = pcmk_dbus_connect(); } if (systemd_proxy == NULL) { return FALSE; } return TRUE; } static inline char * systemd_get_property(const char *unit, const char *name, void (*callback)(const char *name, const char *value, void *userdata), void *userdata, DBusPendingCall **pending, int timeout) { return systemd_proxy? pcmk_dbus_get_property(systemd_proxy, BUS_NAME, unit, BUS_NAME_UNIT, name, callback, userdata, pending, timeout) : NULL; } void systemd_cleanup(void) { if (systemd_proxy) { pcmk_dbus_disconnect(systemd_proxy); systemd_proxy = NULL; } } /* * end of systemd_proxy functions */ /*! * \internal * \brief Check whether a file name represents a manageable systemd unit * * \param[in] name File name to check * * \return Pointer to "dot" before filename extension if so, NULL otherwise */ static const char * systemd_unit_extension(const char *name) { if (name) { const char *dot = strrchr(name, '.'); if (dot && (!strcmp(dot, ".service") || !strcmp(dot, ".socket") || !strcmp(dot, ".mount") || !strcmp(dot, ".timer") || !strcmp(dot, ".path"))) { return dot; } } return NULL; } static char * systemd_service_name(const char *name) { if (name == NULL) { return NULL; } if (systemd_unit_extension(name)) { return strdup(name); } return crm_strdup_printf("%s.service", name); } static void systemd_daemon_reload_complete(DBusPendingCall *pending, void *user_data) { DBusError error; DBusMessage *reply = NULL; unsigned int reload_count = GPOINTER_TO_UINT(user_data); dbus_error_init(&error); if(pending) { reply = dbus_pending_call_steal_reply(pending); } if (pcmk_dbus_find_error(pending, reply, &error)) { crm_err("Could not issue systemd reload %d: %s", reload_count, error.message); dbus_error_free(&error); } else { crm_trace("Reload %d complete", reload_count); } if(pending) { dbus_pending_call_unref(pending); } if(reply) { dbus_message_unref(reply); } } static bool systemd_daemon_reload(int timeout) { static unsigned int reload_count = 0; DBusMessage *msg = systemd_new_method("Reload"); reload_count++; CRM_ASSERT(msg != NULL); systemd_send(msg, systemd_daemon_reload_complete, GUINT_TO_POINTER(reload_count), timeout); dbus_message_unref(msg); return TRUE; } static bool systemd_mask_error(svc_action_t *op, const char *error) { crm_trace("Could not issue %s for %s: %s", op->action, op->rsc, error); if(strstr(error, "org.freedesktop.systemd1.InvalidName") || strstr(error, "org.freedesktop.systemd1.LoadFailed") || strstr(error, "org.freedesktop.systemd1.NoSuchUnit")) { if (pcmk__str_eq(op->action, "stop", pcmk__str_casei)) { crm_trace("Masking %s failure for %s: unknown services are stopped", op->action, op->rsc); op->rc = PCMK_OCF_OK; return TRUE; } else { crm_trace("Mapping %s failure for %s: unknown services are not installed", op->action, op->rsc); op->rc = PCMK_OCF_NOT_INSTALLED; op->status = PCMK_LRM_OP_NOT_INSTALLED; return FALSE; } } return FALSE; } static const char * systemd_loadunit_result(DBusMessage *reply, svc_action_t * op) { const char *path = NULL; DBusError error; if (pcmk_dbus_find_error((void*)&path, reply, &error)) { if(op && !systemd_mask_error(op, error.name)) { crm_err("Could not load systemd unit %s for %s: %s", op->agent, op->id, error.message); } dbus_error_free(&error); - } else if(pcmk_dbus_type_check(reply, NULL, DBUS_TYPE_OBJECT_PATH, __func__, __LINE__)) { + } else if (!pcmk_dbus_type_check(reply, NULL, DBUS_TYPE_OBJECT_PATH, + __func__, __LINE__)) { + crm_err("Could not load systemd unit %s for %s: " + "systemd reply has unexpected type", op->agent, op->id); + + } else { dbus_message_get_args (reply, NULL, DBUS_TYPE_OBJECT_PATH, &path, DBUS_TYPE_INVALID); } if(op) { if (path) { systemd_unit_exec_with_unit(op, path); } else if (op->synchronous == FALSE) { operation_finalize(op); } } return path; } static void systemd_loadunit_cb(DBusPendingCall *pending, void *user_data) { DBusMessage *reply = NULL; svc_action_t * op = user_data; if(pending) { reply = dbus_pending_call_steal_reply(pending); } crm_trace("Got result: %p for %p / %p for %s", reply, pending, op->opaque->pending, op->id); CRM_LOG_ASSERT(pending == op->opaque->pending); services_set_op_pending(op, NULL); systemd_loadunit_result(reply, user_data); if(reply) { dbus_message_unref(reply); } } static char * systemd_unit_by_name(const gchar * arg_name, svc_action_t *op) { DBusMessage *msg; DBusMessage *reply = NULL; DBusPendingCall* pending = NULL; char *name = NULL; /* Equivalent to GetUnit if it's already loaded */ if (systemd_init() == FALSE) { return FALSE; } msg = systemd_new_method("LoadUnit"); CRM_ASSERT(msg != NULL); name = systemd_service_name(arg_name); CRM_LOG_ASSERT(dbus_message_append_args(msg, DBUS_TYPE_STRING, &name, DBUS_TYPE_INVALID)); free(name); if(op == NULL || op->synchronous) { const char *unit = NULL; char *munit = NULL; reply = systemd_send_recv(msg, NULL, (op? op->timeout : DBUS_TIMEOUT_USE_DEFAULT)); dbus_message_unref(msg); unit = systemd_loadunit_result(reply, op); if(unit) { munit = strdup(unit); } if(reply) { dbus_message_unref(reply); } return munit; } pending = systemd_send(msg, systemd_loadunit_cb, op, op->timeout); if(pending) { services_set_op_pending(op, pending); } dbus_message_unref(msg); return NULL; } /*! * \internal * \brief Compare two strings alphabetically (case-insensitive) * * \param[in] a First string to compare * \param[in] b Second string to compare * * \return 0 if strings are equal, -1 if a < b, 1 if a > b * * \note Usable as a GCompareFunc with g_list_sort(). * NULL is considered less than non-NULL. */ static gint sort_str(gconstpointer a, gconstpointer b) { if (!a && !b) { return 0; } else if (!a) { return -1; } else if (!b) { return 1; } return strcasecmp(a, b); } GList * systemd_unit_listall(void) { int nfiles = 0; GList *units = NULL; DBusMessageIter args; DBusMessageIter unit; DBusMessageIter elem; DBusMessage *reply = NULL; if (systemd_init() == FALSE) { return NULL; } /* " \n" \ " \n" \ " \n" \ */ reply = systemd_call_simple_method("ListUnitFiles"); if (reply == NULL) { return NULL; } if (!dbus_message_iter_init(reply, &args)) { crm_err("Could not list systemd unit files: systemd reply has no arguments"); dbus_message_unref(reply); return NULL; } if (!pcmk_dbus_type_check(reply, &args, DBUS_TYPE_ARRAY, __func__, __LINE__)) { crm_err("Could not list systemd unit files: systemd reply has invalid arguments"); dbus_message_unref(reply); return NULL; } dbus_message_iter_recurse(&args, &unit); for (; dbus_message_iter_get_arg_type(&unit) != DBUS_TYPE_INVALID; dbus_message_iter_next(&unit)) { DBusBasicValue value; const char *match = NULL; char *unit_name = NULL; char *basename = NULL; if(!pcmk_dbus_type_check(reply, &unit, DBUS_TYPE_STRUCT, __func__, __LINE__)) { - crm_debug("ListUnitFiles reply has unexpected type"); + crm_warn("Skipping systemd reply argument with unexpected type"); continue; } dbus_message_iter_recurse(&unit, &elem); if(!pcmk_dbus_type_check(reply, &elem, DBUS_TYPE_STRING, __func__, __LINE__)) { - crm_debug("ListUnitFiles reply does not contain a string"); + crm_warn("Skipping systemd reply argument with no string"); continue; } dbus_message_iter_get_basic(&elem, &value); if (value.str == NULL) { crm_debug("ListUnitFiles reply did not provide a string"); continue; } crm_trace("DBus ListUnitFiles listed: %s", value.str); match = systemd_unit_extension(value.str); if (match == NULL) { // This is not a unit file type we know how to manage crm_debug("ListUnitFiles entry '%s' is not supported as resource", value.str); continue; } // ListUnitFiles returns full path names, we just want base name basename = strrchr(value.str, '/'); if (basename) { basename = basename + 1; } else { basename = value.str; } if (!strcmp(match, ".service")) { // Service is the "default" unit type, so strip it unit_name = strndup(basename, match - basename); } else { unit_name = strdup(basename); } nfiles++; units = g_list_prepend(units, unit_name); } dbus_message_unref(reply); crm_trace("Found %d manageable systemd unit files", nfiles); units = g_list_sort(units, sort_str); return units; } gboolean systemd_unit_exists(const char *name) { char *unit = NULL; /* Note: Makes a blocking dbus calls * Used by resources_find_service_class() when resource class=service */ unit = systemd_unit_by_name(name, NULL); if(unit) { free(unit); return TRUE; } return FALSE; } static char * systemd_unit_metadata(const char *name, int timeout) { char *meta = NULL; char *desc = NULL; char *path = systemd_unit_by_name(name, NULL); if (path) { /* TODO: Worth a making blocking call for? Probably not. Possibly if cached. */ desc = systemd_get_property(path, "Description", NULL, NULL, NULL, timeout); } else { desc = crm_strdup_printf("Systemd unit file for %s", name); } meta = crm_strdup_printf("\n" "\n" "\n" " 1.0\n" " \n" " %s\n" " \n" " systemd unit file for %s\n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" "\n", name, desc, name); free(desc); free(path); return meta; } static void systemd_exec_result(DBusMessage *reply, svc_action_t *op) { DBusError error; if (pcmk_dbus_find_error((void*)&error, reply, &error)) { /* ignore "already started" or "not running" errors */ if (!systemd_mask_error(op, error.name)) { crm_err("Could not issue %s for %s: %s", op->action, op->rsc, error.message); } dbus_error_free(&error); } else { if(!pcmk_dbus_type_check(reply, NULL, DBUS_TYPE_OBJECT_PATH, __func__, __LINE__)) { crm_warn("Call to %s passed but return type was unexpected", op->action); op->rc = PCMK_OCF_OK; } else { const char *path = NULL; dbus_message_get_args (reply, NULL, DBUS_TYPE_OBJECT_PATH, &path, DBUS_TYPE_INVALID); crm_info("Call to %s passed: %s", op->action, path); op->rc = PCMK_OCF_OK; } } operation_finalize(op); } static void systemd_async_dispatch(DBusPendingCall *pending, void *user_data) { DBusMessage *reply = NULL; svc_action_t *op = user_data; if(pending) { reply = dbus_pending_call_steal_reply(pending); } crm_trace("Got result: %p for %p for %s, %s", reply, pending, op->rsc, op->action); CRM_LOG_ASSERT(pending == op->opaque->pending); services_set_op_pending(op, NULL); systemd_exec_result(reply, op); if(reply) { dbus_message_unref(reply); } } #define SYSTEMD_OVERRIDE_ROOT "/run/systemd/system/" /* When the cluster manages a systemd resource, we create a unit file override * to order the service "before" pacemaker. The "before" relationship won't * actually be used, since systemd won't ever start the resource -- we're * interested in the reverse shutdown ordering it creates, to ensure that * systemd doesn't stop the resource at shutdown while pacemaker is still * running. * * @TODO Add start timeout */ #define SYSTEMD_OVERRIDE_TEMPLATE \ "[Unit]\n" \ "Description=Cluster Controlled %s\n" \ "Before=pacemaker.service pacemaker_remote.service\n" \ "\n" \ "[Service]\n" \ "Restart=no\n" // Temporarily use rwxr-xr-x umask when opening a file for writing static FILE * create_world_readable(const char *filename) { mode_t orig_umask = umask(S_IWGRP | S_IWOTH); FILE *fp = fopen(filename, "w"); umask(orig_umask); return fp; } static void create_override_dir(const char *agent) { char *override_dir = crm_strdup_printf(SYSTEMD_OVERRIDE_ROOT "/%s.service.d", agent); crm_build_path(override_dir, 0755); free(override_dir); } static char * get_override_filename(const char *agent) { return crm_strdup_printf(SYSTEMD_OVERRIDE_ROOT "/%s.service.d/50-pacemaker.conf", agent); } static void systemd_create_override(const char *agent, int timeout) { FILE *file_strm = NULL; char *override_file = get_override_filename(agent); create_override_dir(agent); /* Ensure the override file is world-readable. This is not strictly * necessary, but it avoids a systemd warning in the logs. */ file_strm = create_world_readable(override_file); if (file_strm == NULL) { crm_err("Cannot open systemd override file %s for writing", override_file); } else { char *override = crm_strdup_printf(SYSTEMD_OVERRIDE_TEMPLATE, agent); int rc = fprintf(file_strm, "%s\n", override); free(override); if (rc < 0) { crm_perror(LOG_WARNING, "Cannot write to systemd override file %s", override_file); } fflush(file_strm); fclose(file_strm); systemd_daemon_reload(timeout); } free(override_file); } static void systemd_remove_override(const char *agent, int timeout) { char *override_file = get_override_filename(agent); int rc = unlink(override_file); if (rc < 0) { // Stop may be called when already stopped, which is fine crm_perror(LOG_DEBUG, "Cannot remove systemd override file %s", override_file); } else { systemd_daemon_reload(timeout); } free(override_file); } static void systemd_unit_check(const char *name, const char *state, void *userdata) { svc_action_t * op = userdata; crm_trace("Resource %s has %s='%s'", op->rsc, name, state); if(state == NULL) { op->rc = PCMK_OCF_NOT_RUNNING; } else if (g_strcmp0(state, "active") == 0) { op->rc = PCMK_OCF_OK; } else if (g_strcmp0(state, "reloading") == 0) { op->rc = PCMK_OCF_OK; } else if (g_strcmp0(state, "activating") == 0) { op->rc = PCMK_OCF_PENDING; } else if (g_strcmp0(state, "deactivating") == 0) { op->rc = PCMK_OCF_PENDING; } else { op->rc = PCMK_OCF_NOT_RUNNING; } if (op->synchronous == FALSE) { services_set_op_pending(op, NULL); operation_finalize(op); } } gboolean systemd_unit_exec_with_unit(svc_action_t * op, const char *unit) { const char *method = op->action; DBusMessage *msg = NULL; DBusMessage *reply = NULL; CRM_ASSERT(unit); if (pcmk__str_eq(op->action, "monitor", pcmk__str_casei) || pcmk__str_eq(method, "status", pcmk__str_casei)) { DBusPendingCall *pending = NULL; char *state; state = systemd_get_property(unit, "ActiveState", (op->synchronous? NULL : systemd_unit_check), op, (op->synchronous? NULL : &pending), op->timeout); if (op->synchronous) { systemd_unit_check("ActiveState", state, op); free(state); return op->rc == PCMK_OCF_OK; } else if (pending) { services_set_op_pending(op, pending); return TRUE; } else { return operation_finalize(op); } } else if (g_strcmp0(method, "start") == 0) { method = "StartUnit"; systemd_create_override(op->agent, op->timeout); } else if (g_strcmp0(method, "stop") == 0) { method = "StopUnit"; systemd_remove_override(op->agent, op->timeout); } else if (g_strcmp0(method, "restart") == 0) { method = "RestartUnit"; } else { op->rc = PCMK_OCF_UNIMPLEMENT_FEATURE; goto cleanup; } crm_debug("Calling %s for %s: %s", method, op->rsc, unit); msg = systemd_new_method(method); CRM_ASSERT(msg != NULL); /* (ss) */ { const char *replace_s = "replace"; char *name = systemd_service_name(op->agent); CRM_LOG_ASSERT(dbus_message_append_args(msg, DBUS_TYPE_STRING, &name, DBUS_TYPE_INVALID)); CRM_LOG_ASSERT(dbus_message_append_args(msg, DBUS_TYPE_STRING, &replace_s, DBUS_TYPE_INVALID)); free(name); } if (op->synchronous == FALSE) { DBusPendingCall *pending = systemd_send(msg, systemd_async_dispatch, op, op->timeout); dbus_message_unref(msg); if(pending) { services_set_op_pending(op, pending); return TRUE; } else { return operation_finalize(op); } } else { reply = systemd_send_recv(msg, NULL, op->timeout); dbus_message_unref(msg); systemd_exec_result(reply, op); if(reply) { dbus_message_unref(reply); } return FALSE; } cleanup: if (op->synchronous == FALSE) { return operation_finalize(op); } return op->rc == PCMK_OCF_OK; } static gboolean systemd_timeout_callback(gpointer p) { svc_action_t * op = p; op->opaque->timerid = 0; crm_warn("%s operation on systemd unit %s named '%s' timed out", op->action, op->agent, op->rsc); operation_finalize(op); return FALSE; } /* For an asynchronous 'op', returns FALSE if 'op' should be free'd by the caller */ /* For a synchronous 'op', returns FALSE if 'op' fails */ gboolean systemd_unit_exec(svc_action_t * op) { char *unit = NULL; CRM_ASSERT(op); CRM_ASSERT(systemd_init()); op->rc = PCMK_OCF_UNKNOWN_ERROR; crm_debug("Performing %ssynchronous %s op on systemd unit %s named '%s'", op->synchronous ? "" : "a", op->action, op->agent, op->rsc); if (pcmk__str_eq(op->action, "meta-data", pcmk__str_casei)) { // @TODO Implement an async meta-data call in executor API op->stdout_data = systemd_unit_metadata(op->agent, op->timeout); op->rc = PCMK_OCF_OK; if (op->synchronous == FALSE) { return operation_finalize(op); } return TRUE; } unit = systemd_unit_by_name(op->agent, op); free(unit); if (op->synchronous == FALSE) { if (op->opaque->pending) { op->opaque->timerid = g_timeout_add(op->timeout + 5000, systemd_timeout_callback, op); services_add_inflight_op(op); return TRUE; } else { return operation_finalize(op); } } return op->rc == PCMK_OCF_OK; } diff --git a/lib/services/upstart.c b/lib/services/upstart.c index 972f4f6d79..b1efa81c12 100644 --- a/lib/services/upstart.c +++ b/lib/services/upstart.c @@ -1,563 +1,564 @@ /* * Copyright (C) 2010 Senko Rasic * Copyright (c) 2010 Ante Karamatic * * This source code is licensed under the GNU Lesser General Public License * version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY. */ #include #include #include #include #include #include #include #include #include #include #include #define BUS_NAME "com.ubuntu.Upstart" #define BUS_PATH "/com/ubuntu/Upstart" #define UPSTART_06_API BUS_NAME"0_6" #define UPSTART_JOB_IFACE UPSTART_06_API".Job" #define BUS_PROPERTY_IFACE "org.freedesktop.DBus.Properties" /* http://upstart.ubuntu.com/wiki/DBusInterface */ static DBusConnection *upstart_proxy = NULL; static gboolean upstart_init(void) { static int need_init = 1; if (need_init) { need_init = 0; upstart_proxy = pcmk_dbus_connect(); } if (upstart_proxy == NULL) { return FALSE; } return TRUE; } void upstart_cleanup(void) { if (upstart_proxy) { pcmk_dbus_disconnect(upstart_proxy); upstart_proxy = NULL; } } static gboolean upstart_job_by_name(const gchar * arg_name, gchar ** out_unit, int timeout) { /* com.ubuntu.Upstart0_6.GetJobByName (in String name, out ObjectPath job) */ DBusError error; DBusMessage *msg; DBusMessage *reply = NULL; const char *method = "GetJobByName"; if(upstart_init() == FALSE) { return FALSE; } msg = dbus_message_new_method_call(BUS_NAME, // target for the method call BUS_PATH, // object to call on UPSTART_06_API, // interface to call on method); // method name dbus_error_init(&error); CRM_LOG_ASSERT(dbus_message_append_args(msg, DBUS_TYPE_STRING, &arg_name, DBUS_TYPE_INVALID)); reply = pcmk_dbus_send_recv(msg, upstart_proxy, &error, timeout); dbus_message_unref(msg); if (dbus_error_is_set(&error)) { crm_err("Could not issue %s for %s: %s", method, arg_name, error.message); dbus_error_free(&error); } else if(!pcmk_dbus_type_check(reply, NULL, DBUS_TYPE_OBJECT_PATH, __func__, __LINE__)) { crm_err("Invalid return type for %s", method); } else { if(out_unit) { char *path = NULL; dbus_message_get_args (reply, NULL, DBUS_TYPE_OBJECT_PATH, &path, DBUS_TYPE_INVALID); *out_unit = strdup(path); } dbus_message_unref(reply); return TRUE; } if(reply) { dbus_message_unref(reply); } return FALSE; } static void fix(char *input, const char *search, char replace) { char *match = NULL; int shuffle = strlen(search) - 1; while (TRUE) { int len, lpc; match = strstr(input, search); if (match == NULL) { break; } crm_trace("Found: %s", match); match[0] = replace; len = strlen(match) - shuffle; for (lpc = 1; lpc <= len; lpc++) { match[lpc] = match[lpc + shuffle]; } } } static char * fix_upstart_name(const char *input) { char *output = strdup(input); fix(output, "_2b", '+'); fix(output, "_2c", ','); fix(output, "_2d", '-'); fix(output, "_2e", '.'); fix(output, "_40", '@'); fix(output, "_5f", '_'); return output; } GList * upstart_job_listall(void) { GList *units = NULL; DBusMessageIter args; DBusMessageIter unit; DBusMessage *msg = NULL; DBusMessage *reply = NULL; const char *method = "GetAllJobs"; DBusError error; int lpc = 0; if (upstart_init() == FALSE) { return NULL; } /* com.ubuntu.Upstart0_6.GetAllJobs (out jobs) */ dbus_error_init(&error); msg = dbus_message_new_method_call(BUS_NAME, // target for the method call BUS_PATH, // object to call on UPSTART_06_API, // interface to call on method); // method name CRM_ASSERT(msg != NULL); reply = pcmk_dbus_send_recv(msg, upstart_proxy, &error, DBUS_TIMEOUT_USE_DEFAULT); dbus_message_unref(msg); if (dbus_error_is_set(&error)) { crm_err("Call to %s failed: %s", method, error.message); dbus_error_free(&error); return NULL; } else if (!dbus_message_iter_init(reply, &args)) { crm_err("Call to %s failed: Message has no arguments", method); dbus_message_unref(reply); return NULL; } if(!pcmk_dbus_type_check(reply, &args, DBUS_TYPE_ARRAY, __func__, __LINE__)) { crm_err("Call to %s failed: Message has invalid arguments", method); dbus_message_unref(reply); return NULL; } dbus_message_iter_recurse(&args, &unit); while (dbus_message_iter_get_arg_type (&unit) != DBUS_TYPE_INVALID) { DBusBasicValue value; const char *job = NULL; char *path = NULL; if(!pcmk_dbus_type_check(reply, &unit, DBUS_TYPE_OBJECT_PATH, __func__, __LINE__)) { + crm_warn("Skipping Upstart reply argument with unexpected type"); continue; } dbus_message_iter_get_basic(&unit, &value); if(value.str) { int llpc = 0; path = value.str; job = value.str; while (path[llpc] != 0) { if (path[llpc] == '/') { job = path + llpc + 1; } llpc++; } lpc++; crm_trace("%s -> %s", path, job); units = g_list_append(units, fix_upstart_name(job)); } dbus_message_iter_next (&unit); } dbus_message_unref(reply); crm_trace("Found %d upstart jobs", lpc); return units; } gboolean upstart_job_exists(const char *name) { return upstart_job_by_name(name, NULL, DBUS_TIMEOUT_USE_DEFAULT); } static char * get_first_instance(const gchar * job, int timeout) { char *instance = NULL; const char *method = "GetAllInstances"; DBusError error; DBusMessage *msg; DBusMessage *reply; DBusMessageIter args; DBusMessageIter unit; dbus_error_init(&error); msg = dbus_message_new_method_call(BUS_NAME, // target for the method call job, // object to call on UPSTART_JOB_IFACE, // interface to call on method); // method name CRM_ASSERT(msg != NULL); dbus_message_append_args(msg, DBUS_TYPE_INVALID); reply = pcmk_dbus_send_recv(msg, upstart_proxy, &error, timeout); dbus_message_unref(msg); if (dbus_error_is_set(&error)) { crm_err("Call to %s failed: %s", method, error.message); dbus_error_free(&error); goto done; } else if(reply == NULL) { crm_err("Call to %s failed: no reply", method); goto done; } else if (!dbus_message_iter_init(reply, &args)) { crm_err("Call to %s failed: Message has no arguments", method); goto done; } if(!pcmk_dbus_type_check(reply, &args, DBUS_TYPE_ARRAY, __func__, __LINE__)) { crm_err("Call to %s failed: Message has invalid arguments", method); goto done; } dbus_message_iter_recurse(&args, &unit); if(pcmk_dbus_type_check(reply, &unit, DBUS_TYPE_OBJECT_PATH, __func__, __LINE__)) { DBusBasicValue value; dbus_message_iter_get_basic(&unit, &value); if(value.str) { instance = strdup(value.str); crm_trace("Result: %s", instance); } } done: if(reply) { dbus_message_unref(reply); } return instance; } static void upstart_job_check(const char *name, const char *state, void *userdata) { svc_action_t * op = userdata; if (state && g_strcmp0(state, "running") == 0) { op->rc = PCMK_OCF_OK; /* } else if (g_strcmp0(state, "activating") == 0) { */ /* op->rc = PCMK_OCF_PENDING; */ } else { op->rc = PCMK_OCF_NOT_RUNNING; } if (op->synchronous == FALSE) { services_set_op_pending(op, NULL); operation_finalize(op); } } static char * upstart_job_metadata(const char *name) { return crm_strdup_printf("\n" "\n" "\n" " 1.0\n" " \n" " Upstart agent for controlling the system %s service\n" " \n" " %s upstart agent\n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" "\n", name, name, name); } static bool upstart_mask_error(svc_action_t *op, const char *error) { crm_trace("Could not issue %s for %s: %s", op->action, op->rsc, error); if(strstr(error, UPSTART_06_API ".Error.UnknownInstance")) { if(pcmk__str_eq(op->action, "stop", pcmk__str_casei)) { crm_trace("Masking %s failure for %s: unknown services are stopped", op->action, op->rsc); op->rc = PCMK_OCF_OK; } else if(pcmk__str_eq(op->action, "start", pcmk__str_casei)) { crm_trace("Mapping %s failure for %s: unknown services are not installed", op->action, op->rsc); op->rc = PCMK_OCF_NOT_INSTALLED; op->status = PCMK_LRM_OP_NOT_INSTALLED; } return TRUE; } else if (pcmk__str_eq(op->action, "start", pcmk__str_casei) && strstr(error, UPSTART_06_API ".Error.AlreadyStarted")) { crm_trace("Mapping %s failure for %s: starting a started resource is allowed", op->action, op->rsc); op->rc = PCMK_OCF_OK; return TRUE; } return FALSE; } static void upstart_async_dispatch(DBusPendingCall *pending, void *user_data) { DBusError error; DBusMessage *reply = NULL; svc_action_t *op = user_data; dbus_error_init(&error); if(pending) { reply = dbus_pending_call_steal_reply(pending); } if (pcmk_dbus_find_error(pending, reply, &error)) { /* ignore "already started" or "not running" errors */ if (!upstart_mask_error(op, error.name)) { crm_err("%s for %s: %s", op->action, op->rsc, error.message); } dbus_error_free(&error); } else if (!g_strcmp0(op->action, "stop")) { /* No return vaue */ op->rc = PCMK_OCF_OK; } else { if(!pcmk_dbus_type_check(reply, NULL, DBUS_TYPE_OBJECT_PATH, __func__, __LINE__)) { crm_warn("Call to %s passed but return type was unexpected", op->action); op->rc = PCMK_OCF_OK; } else { const char *path = NULL; dbus_message_get_args (reply, NULL, DBUS_TYPE_OBJECT_PATH, &path, DBUS_TYPE_INVALID); crm_info("Call to %s passed: %s", op->action, path); op->rc = PCMK_OCF_OK; } } CRM_LOG_ASSERT(pending == op->opaque->pending); services_set_op_pending(op, NULL); operation_finalize(op); if(reply) { dbus_message_unref(reply); } } /* For an asynchronous 'op', returns FALSE if 'op' should be free'd by the caller */ /* For a synchronous 'op', returns FALSE if 'op' fails */ gboolean upstart_job_exec(svc_action_t * op) { char *job = NULL; int arg_wait = TRUE; const char *arg_env = "pacemaker=1"; const char *action = op->action; DBusError error; DBusMessage *msg = NULL; DBusMessage *reply = NULL; DBusMessageIter iter, array_iter; op->rc = PCMK_OCF_UNKNOWN_ERROR; CRM_ASSERT(upstart_init()); if (pcmk__str_eq(op->action, "meta-data", pcmk__str_casei)) { op->stdout_data = upstart_job_metadata(op->agent); op->rc = PCMK_OCF_OK; goto cleanup; } if(!upstart_job_by_name(op->agent, &job, op->timeout)) { crm_debug("Could not obtain job named '%s' to %s", op->agent, action); if (!g_strcmp0(action, "stop")) { op->rc = PCMK_OCF_OK; } else { op->rc = PCMK_OCF_NOT_INSTALLED; op->status = PCMK_LRM_OP_NOT_INSTALLED; } goto cleanup; } if (pcmk__strcase_any_of(op->action, "monitor", "status", NULL)) { char *path = get_first_instance(job, op->timeout); op->rc = PCMK_OCF_NOT_RUNNING; if(path) { DBusPendingCall *pending = NULL; char *state = pcmk_dbus_get_property( upstart_proxy, BUS_NAME, path, UPSTART_06_API ".Instance", "state", op->synchronous?NULL:upstart_job_check, op, op->synchronous?NULL:&pending, op->timeout); free(job); free(path); if(op->synchronous) { upstart_job_check("state", state, op); free(state); return op->rc == PCMK_OCF_OK; } else if (pending) { services_set_op_pending(op, pending); services_add_inflight_op(op); return TRUE; } return FALSE; } goto cleanup; } else if (!g_strcmp0(action, "start")) { action = "Start"; } else if (!g_strcmp0(action, "stop")) { action = "Stop"; } else if (!g_strcmp0(action, "restart")) { action = "Restart"; } else { op->rc = PCMK_OCF_UNIMPLEMENT_FEATURE; goto cleanup; } crm_debug("Calling %s for %s on %s", action, op->rsc, job); msg = dbus_message_new_method_call(BUS_NAME, // target for the method call job, // object to call on UPSTART_JOB_IFACE, // interface to call on action); // method name CRM_ASSERT(msg != NULL); dbus_message_iter_init_append (msg, &iter); CRM_LOG_ASSERT(dbus_message_iter_open_container (&iter, DBUS_TYPE_ARRAY, DBUS_TYPE_STRING_AS_STRING, &array_iter)); CRM_LOG_ASSERT(dbus_message_iter_append_basic (&array_iter, DBUS_TYPE_STRING, &arg_env)); CRM_LOG_ASSERT(dbus_message_iter_close_container (&iter, &array_iter)); CRM_LOG_ASSERT(dbus_message_append_args(msg, DBUS_TYPE_BOOLEAN, &arg_wait, DBUS_TYPE_INVALID)); if (op->synchronous == FALSE) { DBusPendingCall* pending = pcmk_dbus_send(msg, upstart_proxy, upstart_async_dispatch, op, op->timeout); free(job); if(pending) { services_set_op_pending(op, pending); services_add_inflight_op(op); return TRUE; } return FALSE; } dbus_error_init(&error); reply = pcmk_dbus_send_recv(msg, upstart_proxy, &error, op->timeout); if (dbus_error_is_set(&error)) { if(!upstart_mask_error(op, error.name)) { crm_err("Could not issue %s for %s: %s (%s)", action, op->rsc, error.message, job); } dbus_error_free(&error); } else if (!g_strcmp0(op->action, "stop")) { /* No return vaue */ op->rc = PCMK_OCF_OK; } else if(!pcmk_dbus_type_check(reply, NULL, DBUS_TYPE_OBJECT_PATH, __func__, __LINE__)) { crm_warn("Call to %s passed but return type was unexpected", op->action); op->rc = PCMK_OCF_OK; } else { const char *path = NULL; dbus_message_get_args (reply, NULL, DBUS_TYPE_OBJECT_PATH, &path, DBUS_TYPE_INVALID); crm_info("Call to %s passed: %s", op->action, path); op->rc = PCMK_OCF_OK; } cleanup: free(job); if(msg) { dbus_message_unref(msg); } if(reply) { dbus_message_unref(reply); } if (op->synchronous == FALSE) { return operation_finalize(op); } return op->rc == PCMK_OCF_OK; }