diff --git a/.gitignore b/.gitignore index 215eb3d..29d2bde 100644 --- a/.gitignore +++ b/.gitignore @@ -31,6 +31,7 @@ plymouth-generate-initrd plymouth-populate-initrd plymouth-set-default-theme plymouth-log-viewer +plymouth-upstart-bridge plymouthd *.pc tags diff --git a/configure.ac b/configure.ac index 2209df1..585e6c8 100644 --- a/configure.ac +++ b/configure.ac @@ -225,6 +225,23 @@ if test x$enable_gdm_transition = xyes; then AC_DEFINE(PLY_ENABLE_GDM_TRANSITION, 1, [Enable smooth transition to GDM]) fi +AC_ARG_ENABLE(upstart-monitoring, AS_HELP_STRING([--enable-upstart-monitoring],[listen for messages on the Upstart D-Bus interface]),enable_upstart_monitoring=$enableval,enable_upstart_monitoring=no) +if test x$enable_upstart_monitoring = xyes; then + PKG_CHECK_MODULES(DBUS, [dbus-1]) + AC_SUBST(DBUS_CFLAGS) + AC_SUBST(DBUS_LIBS) + AC_CHECK_HEADERS([ncursesw/term.h ncurses/term.h term.h], [break]) + AC_CHECK_LIB([ncursesw], [initscr], + [CURSES_LIBS="$CURSES_LIBS -lncursesw"], + [AC_CHECK_LIB([ncurses], [initscr], + [CURSES_LIBS="$CURSES_LIBS -lncurses"], + [AC_CHECK_LIB([curses], [initscr], + [CURSES_LIBS="$CURSES_LIBS -lcurses"], + [AC_MSG_ERROR([no curses library found])])])]) + AC_SUBST(CURSES_LIBS) +fi +AM_CONDITIONAL(ENABLE_UPSTART_MONITORING, [test "$enable_upstart_monitoring" = yes]) + AC_ARG_WITH(system-root-install, AS_HELP_STRING([--with-system-root-install],[Install client in /bin and daemon in /sbin]),with_system_root_install=${withval},with_system_root_install=yes) AM_CONDITIONAL(WITH_SYSTEM_ROOT_INSTALL, [test "$with_system_root_install" = yes]) @@ -398,6 +415,7 @@ AC_CONFIG_FILES([Makefile src/client/ply-boot-client.pc src/client/Makefile src/viewer/Makefile + src/upstart-bridge/Makefile src/tests/Makefile src/libply/tests/Makefile src/client/tests/Makefile diff --git a/src/Makefile.am b/src/Makefile.am index 67b71da..c6de826 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -1,4 +1,7 @@ SUBDIRS = libply libply-splash-core libply-splash-graphics . plugins client viewer tests +if ENABLE_UPSTART_MONITORING +SUBDIRS += upstart-bridge +endif INCLUDES = -I$(top_srcdir) \ -I$(srcdir)/libply \ -I$(srcdir)/libply-splash-core \ diff --git a/src/upstart-bridge/Makefile.am b/src/upstart-bridge/Makefile.am new file mode 100644 index 0000000..628ef20 --- /dev/null +++ b/src/upstart-bridge/Makefile.am @@ -0,0 +1,21 @@ +INCLUDES = -I$(top_srcdir) \ + -I$(top_srcdir)/src \ + -I$(top_srcdir)/src/libply \ + -I$(top_srcdir)/src/client \ + -I$(srcdir) +plymouthdir = $(plymouthclientdir) + +plymouth_PROGRAMS = plymouth-upstart-bridge + +plymouth_upstart_bridge_CFLAGS = $(PLYMOUTH_CFLAGS) $(DBUS_CFLAGS) +plymouth_upstart_bridge_LDADD = \ + $(PLYMOUTH_LIBS) \ + $(DBUS_LIBS) \ + $(CURSES_LIBS) \ + ../libply/libply.la \ + ../client/libply-boot-client.la +plymouth_upstart_bridge_SOURCES = \ + $(srcdir)/../ply-boot-protocol.h \ + $(srcdir)/ply-upstart-monitor.h \ + $(srcdir)/ply-upstart-monitor.c \ + $(srcdir)/plymouth-upstart-bridge.c diff --git a/src/upstart-bridge/ply-upstart-monitor.c b/src/upstart-bridge/ply-upstart-monitor.c new file mode 100644 index 0000000..b19d95a --- /dev/null +++ b/src/upstart-bridge/ply-upstart-monitor.c @@ -0,0 +1,1301 @@ +/* ply-upstart-monitor.c - Upstart D-Bus monitor + * + * Copyright (C) 2010, 2011 Canonical Ltd. + * + * 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, or (at your option) + * any later version. + * + * This program 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 program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + * + * Written by: Colin Watson + */ +#include "config.h" +#include "ply-upstart-monitor.h" + +#include +#include +#include +#include +#include +#include + +#include + +#include "ply-logger.h" +#include "ply-event-loop.h" +#include "ply-hashtable.h" +#include "ply-list.h" +#include "ply-utils.h" + +typedef struct +{ + ply_upstart_monitor_t *monitor; + DBusTimeout *timeout; +} ply_upstart_monitor_timeout_t; + +struct _ply_upstart_monitor +{ + DBusConnection *connection; + char *owner; + ply_event_loop_t *loop; + ply_hashtable_t *jobs; + ply_hashtable_t *all_instances; + ply_upstart_monitor_state_changed_handler_t state_changed_handler; + void *state_changed_data; + ply_upstart_monitor_failed_handler_t failed_handler; + void *failed_data; + int dispatch_fd; +}; + +typedef struct +{ + ply_upstart_monitor_t *monitor; + ply_upstart_monitor_job_properties_t properties; + ply_hashtable_t *instances; + ply_list_t *pending_calls; +} ply_upstart_monitor_job_t; + +typedef struct +{ + ply_upstart_monitor_job_t *job; + ply_upstart_monitor_instance_properties_t properties; + ply_list_t *pending_calls; + uint32_t state_changed : 1; + uint32_t call_failed : 1; +} ply_upstart_monitor_instance_t; + +#define UPSTART_SERVICE "com.ubuntu.Upstart" +#define UPSTART_PATH "/com/ubuntu/Upstart" +#define UPSTART_INTERFACE_0_6 "com.ubuntu.Upstart0_6" +#define UPSTART_INTERFACE_0_6_JOB "com.ubuntu.Upstart0_6.Job" +#define UPSTART_INTERFACE_0_6_INSTANCE "com.ubuntu.Upstart0_6.Instance" + +/* Remove an entry from a hashtable, free the key, and return the data. */ +static void * +hashtable_remove_and_free_key (ply_hashtable_t *hashtable, + const void *key) +{ + void *reply_key, *reply_data; + + if (!ply_hashtable_lookup_full (hashtable, (void *) key, + &reply_key, &reply_data)) + return NULL; + ply_hashtable_remove (hashtable, (void *) key); + free (reply_key); + + return reply_data; +} + +/* We assume, in general, that Upstart responds to D-Bus messages in a + * single thread, and that it processes messages on a given connection in + * the order in which they were sent. Taken together, these assumptions + * imply a kind of coherence: a Properties.GetAll reply received after a + * StateChanged signal must have been computed entirely after the state + * change. Thus, if this function returns false (properties have not been + * fetched yet), it should be safe to record an event as call until such + * time as the properties of the instance are known. + */ +static bool +instance_is_initialized (ply_upstart_monitor_instance_t *instance) +{ + /* Note that the job may not have a description. */ + if (instance->job->properties.name && + instance->properties.name && instance->properties.goal && + instance->properties.state) + return true; + else + return false; +} + +static void +on_get_all_instance_properties_finished (DBusPendingCall *call, + ply_upstart_monitor_instance_t *instance) +{ + DBusMessage *reply; + DBusMessageIter iter, array_iter, dict_iter, variant_iter; + const char *key, *name, *goal, *state; + ply_upstart_monitor_t *monitor; + + assert (call != NULL); + assert (instance != NULL); + + reply = dbus_pending_call_steal_reply (call); + if (reply == NULL) + return; + if (dbus_message_get_type (reply) != DBUS_MESSAGE_TYPE_METHOD_RETURN) + goto out; + + dbus_message_iter_init (reply, &iter); + if (dbus_message_iter_get_arg_type (&iter) != DBUS_TYPE_ARRAY) + goto out; + dbus_message_iter_recurse (&iter, &array_iter); + + while (dbus_message_iter_get_arg_type (&array_iter) == DBUS_TYPE_DICT_ENTRY) + { + dbus_message_iter_recurse (&array_iter, &dict_iter); + + if (dbus_message_iter_get_arg_type (&dict_iter) != DBUS_TYPE_STRING) + goto next_item; + + dbus_message_iter_get_basic (&dict_iter, &key); + if (key == NULL) + goto next_item; + + dbus_message_iter_next (&dict_iter); + if (dbus_message_iter_get_arg_type (&dict_iter) != DBUS_TYPE_VARIANT) + goto next_item; + dbus_message_iter_recurse (&dict_iter, &variant_iter); + if (dbus_message_iter_get_arg_type (&variant_iter) != DBUS_TYPE_STRING) + goto next_item; + + if (strcmp (key, "name") == 0) + { + dbus_message_iter_get_basic (&variant_iter, &name); + if (name != NULL) + { + ply_trace ("%s: name = '%s'", + instance->job->properties.name, name); + instance->properties.name = strdup (name); + } + } + else if (strcmp (key, "goal") == 0) + { + dbus_message_iter_get_basic (&variant_iter, &goal); + if (goal != NULL) + { + ply_trace ("%s: goal = '%s'", + instance->job->properties.name, goal); + instance->properties.goal = strdup (goal); + } + } + else if (strcmp (key, "state") == 0) + { + dbus_message_iter_get_basic (&variant_iter, &state); + if (state != NULL) + { + ply_trace ("%s: state = '%s'", + instance->job->properties.name, state); + instance->properties.state = strdup (state); + } + } + +next_item: + dbus_message_iter_next (&array_iter); + } + +out: + dbus_message_unref (reply); + + if (instance_is_initialized (instance)) + { + /* Process any call events. */ + monitor = instance->job->monitor; + + if (instance->state_changed && monitor->state_changed_handler) + monitor->state_changed_handler (monitor->state_changed_data, NULL, + &instance->job->properties, + &instance->properties); + instance->state_changed = false; + + if (instance->call_failed && monitor->failed_handler) + monitor->failed_handler (monitor->failed_data, + &instance->job->properties, + &instance->properties, + instance->properties.failed); + instance->call_failed = false; + } +} + +static void +on_get_all_job_properties_finished (DBusPendingCall *call, + ply_upstart_monitor_job_t *job) +{ + DBusMessage *reply; + DBusMessageIter iter, array_iter, dict_iter, variant_iter; + const char *key, *name, *description; + dbus_uint32_t task; + + assert (call != NULL); + assert (job != NULL); + + reply = dbus_pending_call_steal_reply (call); + if (reply == NULL) + return; + if (dbus_message_get_type (reply) != DBUS_MESSAGE_TYPE_METHOD_RETURN) + goto out; + + dbus_message_iter_init (reply, &iter); + if (dbus_message_iter_get_arg_type (&iter) != DBUS_TYPE_ARRAY) + goto out; + dbus_message_iter_recurse (&iter, &array_iter); + + while (dbus_message_iter_get_arg_type (&array_iter) == DBUS_TYPE_DICT_ENTRY) + { + dbus_message_iter_recurse (&array_iter, &dict_iter); + + if (dbus_message_iter_get_arg_type (&dict_iter) != DBUS_TYPE_STRING) + goto next_item; + + dbus_message_iter_get_basic (&dict_iter, &key); + if (key == NULL) + goto next_item; + + dbus_message_iter_next (&dict_iter); + if (dbus_message_iter_get_arg_type (&dict_iter) != DBUS_TYPE_VARIANT) + goto next_item; + dbus_message_iter_recurse (&dict_iter, &variant_iter); + + if (strcmp (key, "name") == 0) + { + if (dbus_message_iter_get_arg_type (&variant_iter) != + DBUS_TYPE_STRING) + goto next_item; + dbus_message_iter_get_basic (&variant_iter, &name); + if (name != NULL) + { + ply_trace ("name = '%s'", name); + job->properties.name = strdup (name); + } + } + else if (strcmp (key, "description") == 0) + { + if (dbus_message_iter_get_arg_type (&variant_iter) != + DBUS_TYPE_STRING) + goto next_item; + dbus_message_iter_get_basic (&variant_iter, &description); + if (description != NULL) + { + ply_trace ("description = '%s'", description); + job->properties.description = strdup (description); + } + } + else if (strcmp (key, "task") == 0) + { + if (dbus_message_iter_get_arg_type (&variant_iter) != + DBUS_TYPE_BOOLEAN) + goto next_item; + dbus_message_iter_get_basic (&variant_iter, &task); + ply_trace ("task = %s", task ? "TRUE" : "FALSE"); + job->properties.is_task = task ? true : false; + } + +next_item: + dbus_message_iter_next (&array_iter); + } + +out: + dbus_message_unref (reply); +} + +static void +remove_instance_internal (ply_upstart_monitor_job_t *job, const char *path) +{ + ply_upstart_monitor_instance_t *instance; + ply_list_node_t *node; + + instance = hashtable_remove_and_free_key (job->instances, path); + if (instance == NULL) + return; + hashtable_remove_and_free_key (job->monitor->all_instances, path); + + node = ply_list_get_first_node (instance->pending_calls); + while (node != NULL) + { + DBusPendingCall *call; + ply_list_node_t *next_node; + + call = ply_list_node_get_data (node); + next_node = ply_list_get_next_node (instance->pending_calls, node); + dbus_pending_call_cancel (call); + dbus_pending_call_unref (call); + node = next_node; + } + ply_list_free (instance->pending_calls); + + free (instance->properties.name); + free (instance->properties.goal); + free (instance->properties.state); + free (instance); +} + +static void +add_instance (ply_upstart_monitor_job_t *job, + const char *path) +{ + ply_upstart_monitor_instance_t *instance; + DBusMessage *message; + const char *interface = UPSTART_INTERFACE_0_6_INSTANCE; + DBusPendingCall *call; + + ply_trace ("adding instance: %s", path); + + remove_instance_internal (job, path); + + instance = calloc (1, sizeof (ply_upstart_monitor_instance_t)); + instance->job = job; + instance->properties.name = NULL; + instance->properties.goal = NULL; + instance->properties.state = NULL; + instance->properties.failed = false; + instance->pending_calls = ply_list_new (); + instance->state_changed = false; + instance->call_failed = false; + + /* Keep a hash of instances per job, to make InstanceRemoved handling + * easy. + */ + ply_hashtable_insert (job->instances, strdup (path), instance); + /* Keep a separate hash of all instances, to make StateChanged handling + * easy. + */ + ply_hashtable_insert (job->monitor->all_instances, strdup (path), instance); + + /* Ask Upstart for the name, goal, and state properties. */ + ply_trace ("fetching properties of instance %s", path); + message = dbus_message_new_method_call (UPSTART_SERVICE, path, + DBUS_INTERFACE_PROPERTIES, "GetAll"); + dbus_message_append_args (message, + DBUS_TYPE_STRING, &interface, + DBUS_TYPE_INVALID); + dbus_connection_send_with_reply (job->monitor->connection, message, + &call, -1); + dbus_message_unref (message); + if (call != NULL) + { + dbus_pending_call_set_notify (call, + (DBusPendingCallNotifyFunction) + on_get_all_instance_properties_finished, + instance, NULL); + ply_list_append_data (instance->pending_calls, call); + } +} + +static void +remove_instance (ply_upstart_monitor_job_t *job, + const char *path) +{ + ply_trace ("removing instance: %s", path); + + remove_instance_internal (job, path); +} + +static void +on_get_all_instances_finished (DBusPendingCall *call, + ply_upstart_monitor_job_t *job) +{ + DBusMessage *reply; + DBusError error; + char **instances; + int n_instances, i; + + assert (call != NULL); + assert (job != NULL); + + reply = dbus_pending_call_steal_reply (call); + if (reply == NULL) + return; + if (dbus_message_get_type (reply) != DBUS_MESSAGE_TYPE_METHOD_RETURN) + goto out; + + dbus_error_init (&error); + dbus_message_get_args (reply, &error, + DBUS_TYPE_ARRAY, DBUS_TYPE_OBJECT_PATH, + &instances, &n_instances, + DBUS_TYPE_INVALID); + if (dbus_error_is_set (&error)) + goto out; + dbus_error_free (&error); + + for (i = 0; i < n_instances; ++i) + add_instance (job, instances[i]); + + dbus_free_string_array (instances); + +out: + dbus_message_unref (reply); +} + +static void +free_job_instance (void *key, void *data, void *user_data) +{ + const char *path = key; + ply_upstart_monitor_instance_t *instance = data; + ply_upstart_monitor_t *monitor = user_data; + + assert (monitor != NULL); + + if (instance == NULL) + return; + + hashtable_remove_and_free_key (monitor->all_instances, path); + free (instance->properties.name); + free (instance->properties.goal); + free (instance->properties.state); + free (instance); +} + +static void +remove_job_internal (ply_upstart_monitor_t *monitor, const char *path) +{ + ply_upstart_monitor_job_t *job; + ply_list_node_t *node; + + job = hashtable_remove_and_free_key (monitor->jobs, path); + if (job == NULL) + return; + + node = ply_list_get_first_node (job->pending_calls); + while (node != NULL) + { + DBusPendingCall *call; + ply_list_node_t *next_node; + + call = ply_list_node_get_data (node); + next_node = ply_list_get_next_node (job->pending_calls, node); + dbus_pending_call_cancel (call); + dbus_pending_call_unref (call); + node = next_node; + } + ply_list_free (job->pending_calls); + + free (job->properties.name); + free (job->properties.description); + ply_hashtable_foreach (job->instances, free_job_instance, monitor); + ply_hashtable_free (job->instances); + free (job); +} + +static void +add_job (ply_upstart_monitor_t *monitor, const char *path) +{ + ply_upstart_monitor_job_t *job; + DBusMessage *message; + const char *interface = UPSTART_INTERFACE_0_6_JOB; + DBusPendingCall *call; + + ply_trace ("adding job: %s", path); + + remove_job_internal (monitor, path); + + job = calloc (1, sizeof (ply_upstart_monitor_job_t)); + job->monitor = monitor; + job->properties.name = NULL; + job->properties.description = NULL; + job->properties.is_task = false; + job->instances = ply_hashtable_new (ply_hashtable_string_hash, + ply_hashtable_string_compare); + job->pending_calls = ply_list_new (); + + ply_hashtable_insert (monitor->jobs, strdup (path), job); + + /* Ask Upstart for the name and description properties. */ + ply_trace ("fetching properties of job %s", path); + message = dbus_message_new_method_call (UPSTART_SERVICE, path, + DBUS_INTERFACE_PROPERTIES, "GetAll"); + dbus_message_append_args (message, + DBUS_TYPE_STRING, &interface, + DBUS_TYPE_INVALID); + dbus_connection_send_with_reply (monitor->connection, message, &call, -1); + dbus_message_unref (message); + if (call != NULL) + { + dbus_pending_call_set_notify (call, + (DBusPendingCallNotifyFunction) + on_get_all_job_properties_finished, + job, + NULL); + ply_list_append_data (job->pending_calls, call); + } + + /* Ask Upstart for a list of all instances of this job. */ + ply_trace ("calling GetAllInstances on job %s", path); + message = dbus_message_new_method_call (UPSTART_SERVICE, path, + UPSTART_INTERFACE_0_6_JOB, + "GetAllInstances"); + dbus_connection_send_with_reply (monitor->connection, message, &call, -1); + dbus_message_unref (message); + if (call != NULL) + { + dbus_pending_call_set_notify (call, + (DBusPendingCallNotifyFunction) + on_get_all_instances_finished, + job, + NULL); + ply_list_append_data (job->pending_calls, call); + } +} + +static void +remove_job (ply_upstart_monitor_t *monitor, const char *path) +{ + ply_trace ("removing job: %s", path); + + remove_job_internal (monitor, path); +} + +static void +on_get_all_jobs_finished (DBusPendingCall *call, + ply_upstart_monitor_t *monitor) +{ + DBusMessage *reply; + DBusError error; + char **jobs; + int n_jobs, i; + + assert (call != NULL); + assert (monitor != NULL); + + reply = dbus_pending_call_steal_reply (call); + if (reply == NULL) + return; + if (dbus_message_get_type (reply) != DBUS_MESSAGE_TYPE_METHOD_RETURN) + goto out; + + dbus_error_init (&error); + dbus_message_get_args (reply, &error, + DBUS_TYPE_ARRAY, DBUS_TYPE_OBJECT_PATH, + &jobs, &n_jobs, + DBUS_TYPE_INVALID); + if (dbus_error_is_set (&error)) + goto out; + dbus_error_free (&error); + + for (i = 0; i < n_jobs; ++i) + add_job (monitor, jobs[i]); + + dbus_free_string_array (jobs); + +out: + dbus_message_unref (reply); +} + +static void +on_get_name_owner_finished (DBusPendingCall *call, + ply_upstart_monitor_t *monitor) +{ + DBusMessage *reply, *message; + DBusError error; + const char *owner; + + assert (call != NULL); + assert (monitor != NULL); + + reply = dbus_pending_call_steal_reply (call); + if (reply == NULL) + return; + if (dbus_message_get_type (reply) != DBUS_MESSAGE_TYPE_METHOD_RETURN) + goto out; + + dbus_error_init (&error); + dbus_message_get_args (reply, &error, + DBUS_TYPE_STRING, &owner, + DBUS_TYPE_INVALID); + if (dbus_error_is_set (&error)) + goto out; + dbus_error_free (&error); + + ply_trace ("owner = '%s'", owner); + + free (monitor->owner); + monitor->owner = strdup (owner); + + ply_trace ("calling GetAllJobs"); + message = dbus_message_new_method_call (UPSTART_SERVICE, UPSTART_PATH, + UPSTART_INTERFACE_0_6, + "GetAllJobs"); + dbus_connection_send_with_reply (monitor->connection, message, &call, -1); + dbus_message_unref (message); + if (call != NULL) + dbus_pending_call_set_notify (call, + (DBusPendingCallNotifyFunction) + on_get_all_jobs_finished, + monitor, NULL); + +out: + dbus_message_unref (reply); +} + +static DBusHandlerResult +name_owner_changed_handler (DBusConnection *connection, + DBusMessage *message, + ply_upstart_monitor_t *monitor) +{ + DBusError error; + const char *name, *old_owner, *new_owner; + + assert (connection != NULL); + assert (message != NULL); + assert (monitor != NULL); + + dbus_error_init (&error); + if (dbus_message_get_args (message, &error, + DBUS_TYPE_STRING, &name, + DBUS_TYPE_STRING, &old_owner, + DBUS_TYPE_STRING, &new_owner, + DBUS_TYPE_INVALID) && + strcmp (name, UPSTART_SERVICE) == 0) + { + if (new_owner) + ply_trace ("owner changed from '%s' to '%s'", old_owner, new_owner); + else + ply_trace ("owner left bus"); + free (monitor->owner); + monitor->owner = new_owner ? strdup (new_owner) : NULL; + } + + return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; /* let other handlers try */ +} + +static DBusHandlerResult +job_added_handler (DBusConnection *connection, + DBusMessage *message, + ply_upstart_monitor_t *monitor) +{ + DBusError error; + const char *signal_path; + + ply_trace ("got JobAdded"); + dbus_error_init (&error); + if (dbus_message_get_args (message, &error, + DBUS_TYPE_OBJECT_PATH, &signal_path, + DBUS_TYPE_INVALID)) + add_job (monitor, signal_path); + dbus_error_free (&error); + return DBUS_HANDLER_RESULT_HANDLED; +} + +static DBusHandlerResult +job_removed_handler (DBusConnection *connection, + DBusMessage *message, + ply_upstart_monitor_t *monitor) +{ + DBusError error; + const char *signal_path; + + ply_trace ("got JobRemoved"); + dbus_error_init (&error); + if (dbus_message_get_args (message, &error, + DBUS_TYPE_OBJECT_PATH, &signal_path, + DBUS_TYPE_INVALID)) + remove_job (monitor, signal_path); + dbus_error_free (&error); + return DBUS_HANDLER_RESULT_HANDLED; +} + +static DBusHandlerResult +instance_added_handler (DBusConnection *connection, DBusMessage *message, + ply_upstart_monitor_t *monitor, const char *path) +{ + DBusError error; + const char *signal_path; + ply_upstart_monitor_job_t *job; + + ply_trace ("got %s InstanceAdded", path); + job = ply_hashtable_lookup (monitor->jobs, (void *) path); + if (job != NULL) + { + dbus_error_init (&error); + if (dbus_message_get_args (message, &error, + DBUS_TYPE_OBJECT_PATH, &signal_path, + DBUS_TYPE_INVALID)) + add_instance (job, signal_path); + dbus_error_free (&error); + } + return DBUS_HANDLER_RESULT_HANDLED; +} + +static DBusHandlerResult +instance_removed_handler (DBusConnection *connection, DBusMessage *message, + ply_upstart_monitor_t *monitor, const char *path) +{ + DBusError error; + const char *signal_path; + ply_upstart_monitor_job_t *job; + + ply_trace ("got %s InstanceRemoved", path); + job = ply_hashtable_lookup (monitor->jobs, (void *) path); + if (job != NULL) + { + dbus_error_init (&error); + if (dbus_message_get_args (message, &error, + DBUS_TYPE_OBJECT_PATH, &signal_path, + DBUS_TYPE_INVALID)) + remove_instance (job, signal_path); + dbus_error_free (&error); + } + return DBUS_HANDLER_RESULT_HANDLED; +} + +static DBusHandlerResult +goal_changed_handler (DBusConnection *connection, DBusMessage *message, + ply_upstart_monitor_t *monitor, const char *path) +{ + DBusError error; + const char *goal; + ply_upstart_monitor_instance_t *instance; + char *old_goal; + + ply_trace ("got %s GoalChanged", path); + instance = ply_hashtable_lookup (monitor->all_instances, (void *) path); + if (instance != NULL) + { + dbus_error_init (&error); + if (dbus_message_get_args (message, &error, + DBUS_TYPE_STRING, &goal, + DBUS_TYPE_INVALID)) + { + old_goal = instance->properties.goal; + instance->properties.goal = strdup (goal); + ply_trace ("goal changed from '%s' to '%s'", old_goal, goal); + free (old_goal); + } + dbus_error_free (&error); + } + return DBUS_HANDLER_RESULT_HANDLED; +} + +static DBusHandlerResult +state_changed_handler (DBusConnection *connection, DBusMessage *message, + ply_upstart_monitor_t *monitor, const char *path) +{ + DBusError error; + const char *state; + ply_upstart_monitor_instance_t *instance; + char *old_state; + + ply_trace ("got %s StateChanged", path); + instance = ply_hashtable_lookup (monitor->all_instances, (void *) path); + if (instance != NULL) + { + dbus_error_init (&error); + if (dbus_message_get_args (message, &error, + DBUS_TYPE_STRING, &state, + DBUS_TYPE_INVALID)) + { + old_state = instance->properties.state; + instance->properties.state = strdup (state); + ply_trace ("state changed from '%s' to '%s'", old_state, state); + if (strcmp (state, "starting") == 0) + { + /* Clear any old failed information. */ + instance->properties.failed = 0; + instance->call_failed = false; + } + if (instance_is_initialized (instance)) + { + if (monitor->state_changed_handler) + monitor->state_changed_handler (monitor->state_changed_data, + old_state, + &instance->job->properties, + &instance->properties); + } + else + instance->state_changed = true; + free (old_state); + } + dbus_error_free (&error); + } + return DBUS_HANDLER_RESULT_HANDLED; +} + +static DBusHandlerResult +failed_handler (DBusConnection *connection, DBusMessage *message, + ply_upstart_monitor_t *monitor, const char *path) +{ + DBusError error; + ply_upstart_monitor_instance_t *instance; + dbus_int32_t failed_status; + + ply_trace ("got %s Failed", path); + instance = ply_hashtable_lookup (monitor->all_instances, (void *) path); + if (instance != NULL) + { + dbus_error_init (&error); + if (dbus_message_get_args (message, &error, + DBUS_TYPE_INT32, &failed_status, + DBUS_TYPE_INVALID)) + { + instance->properties.failed = failed_status; + if (instance_is_initialized (instance)) + { + if (monitor->failed_handler) + monitor->failed_handler (monitor->failed_data, + &instance->job->properties, + &instance->properties, + (int) failed_status); + } + else + instance->call_failed = true; + } + dbus_error_free (&error); + } + return DBUS_HANDLER_RESULT_HANDLED; +} + +static DBusHandlerResult +message_handler (DBusConnection *connection, DBusMessage *message, void *data) +{ + ply_upstart_monitor_t *monitor = data; + const char *path; + + assert (connection != NULL); + assert (message != NULL); + assert (monitor != NULL); + + path = dbus_message_get_path (message); + if (path == NULL) + return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; + + if (dbus_message_is_signal (message, DBUS_INTERFACE_DBUS, + "NameOwnerChanged") && + dbus_message_has_path (message, DBUS_PATH_DBUS) && + dbus_message_has_sender (message, DBUS_SERVICE_DBUS)) + return name_owner_changed_handler (connection, message, monitor); + + if (dbus_message_is_signal (message, UPSTART_INTERFACE_0_6, + "JobAdded")) + return job_added_handler (connection, message, monitor); + + if (dbus_message_is_signal (message, UPSTART_INTERFACE_0_6, + "JobRemoved")) + return job_removed_handler (connection, message, monitor); + + if (dbus_message_is_signal (message, UPSTART_INTERFACE_0_6_JOB, + "InstanceAdded")) + return instance_added_handler (connection, message, monitor, path); + + if (dbus_message_is_signal (message, UPSTART_INTERFACE_0_6_JOB, + "InstanceRemoved")) + return instance_removed_handler (connection, message, monitor, path); + + if (dbus_message_is_signal (message, UPSTART_INTERFACE_0_6_INSTANCE, + "GoalChanged")) + return goal_changed_handler (connection, message, monitor, path); + + if (dbus_message_is_signal (message, UPSTART_INTERFACE_0_6_INSTANCE, + "StateChanged")) + return state_changed_handler (connection, message, monitor, path); + + if (dbus_message_is_signal (message, UPSTART_INTERFACE_0_6_INSTANCE, + "Failed")) + return failed_handler (connection, message, monitor, path); + + return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; +} + +ply_upstart_monitor_t * +ply_upstart_monitor_new (ply_event_loop_t *loop) +{ + DBusError error; + DBusConnection *connection; + ply_upstart_monitor_t *monitor; + char *rule; + DBusMessage *message; + const char *monitor_service = UPSTART_SERVICE; + DBusPendingCall *call; + + dbus_error_init (&error); + + /* Get a connection to the system bus and set it up to listen for messages + * from Upstart. + */ + connection = dbus_bus_get (DBUS_BUS_SYSTEM, &error); + if (connection == NULL) + { + ply_error ("unable to connect to system bus: %s", error.message); + dbus_error_free (&error); + return NULL; + } + dbus_error_free (&error); + + monitor = calloc (1, sizeof (ply_upstart_monitor_t)); + monitor->connection = connection; + monitor->loop = NULL; + monitor->jobs = ply_hashtable_new (ply_hashtable_string_hash, + ply_hashtable_string_compare); + monitor->all_instances = ply_hashtable_new (ply_hashtable_string_hash, + ply_hashtable_string_compare); + monitor->state_changed_handler = NULL; + monitor->state_changed_data = NULL; + monitor->failed_handler = NULL; + monitor->failed_data = NULL; + monitor->dispatch_fd = -1; + + if (!dbus_connection_add_filter (connection, message_handler, monitor, NULL)) + { + ply_error ("unable to add filter to system bus connection"); + ply_upstart_monitor_free (monitor); + return NULL; + } + + asprintf (&rule, "type='%s',sender='%s',path='%s'," + "interface='%s',member='%s',arg0='%s'", + "signal", DBUS_SERVICE_DBUS, DBUS_PATH_DBUS, + DBUS_INTERFACE_DBUS, "NameOwnerChanged", UPSTART_SERVICE); + dbus_bus_add_match (connection, rule, &error); + free (rule); + if (dbus_error_is_set (&error)) + { + ply_error ("unable to add match rule to system bus connection: %s", + error.message); + ply_upstart_monitor_free (monitor); + dbus_error_free (&error); + return NULL; + } + + asprintf (&rule, "type='%s',sender='%s'", "signal", UPSTART_SERVICE); + dbus_bus_add_match (connection, rule, &error); + free (rule); + if (dbus_error_is_set (&error)) + { + ply_error ("unable to add match rule to system bus connection: %s", + error.message); + ply_upstart_monitor_free (monitor); + dbus_error_free (&error); + return NULL; + } + + /* Start the state machine going: find out the current owner of the + * well-known Upstart name. + * Ignore errors: the worst case is that we don't get any messages back + * and our state machine does nothing. + */ + ply_trace ("calling GetNameOwner"); + message = dbus_message_new_method_call (DBUS_SERVICE_DBUS, DBUS_PATH_DBUS, + DBUS_INTERFACE_DBUS, "GetNameOwner"); + dbus_message_append_args (message, + DBUS_TYPE_STRING, &monitor_service, + DBUS_TYPE_INVALID); + dbus_connection_send_with_reply (connection, message, &call, -1); + dbus_message_unref (message); + if (call != NULL) + dbus_pending_call_set_notify (call, + (DBusPendingCallNotifyFunction) + on_get_name_owner_finished, + monitor, + NULL); + + if (loop != NULL) + ply_upstart_monitor_connect_to_event_loop (monitor, loop); + + return monitor; +} + +void +ply_upstart_monitor_free (ply_upstart_monitor_t *monitor) +{ + if (monitor == NULL) + return; + + ply_hashtable_free (monitor->all_instances); + ply_hashtable_free (monitor->jobs); + dbus_connection_unref (monitor->connection); + if (monitor->dispatch_fd >= 0) + close (monitor->dispatch_fd); + free (monitor); +} + +static void +read_watch_handler (void *data, int fd) +{ + DBusWatch *watch = data; + + assert (watch != NULL); + + dbus_watch_handle (watch, DBUS_WATCH_READABLE); +} + +static void +write_watch_handler (void *data, int fd) +{ + DBusWatch *watch = data; + + assert (watch != NULL); + + dbus_watch_handle (watch, DBUS_WATCH_WRITABLE); +} + +static dbus_bool_t +add_watch (DBusWatch *watch, void *data) +{ + ply_upstart_monitor_t *monitor = data; + unsigned int flags; + int fd; + ply_event_loop_fd_status_t status; + ply_fd_watch_t *read_watch_event = NULL, *write_watch_event = NULL; + + assert (monitor != NULL); + assert (watch != NULL); + + if (!dbus_watch_get_enabled (watch)) + return TRUE; + + assert (dbus_watch_get_data (watch) == NULL); + + flags = dbus_watch_get_flags (watch); + fd = dbus_watch_get_unix_fd (watch); + + if (flags & DBUS_WATCH_READABLE) + { + status = PLY_EVENT_LOOP_FD_STATUS_HAS_DATA; + read_watch_event = ply_event_loop_watch_fd (monitor->loop, fd, status, + read_watch_handler, NULL, + watch); + if (read_watch_event == NULL) + return FALSE; + dbus_watch_set_data (watch, read_watch_event, NULL); + } + + if (flags & DBUS_WATCH_WRITABLE) + { + status = PLY_EVENT_LOOP_FD_STATUS_CAN_TAKE_DATA; + write_watch_event = ply_event_loop_watch_fd (monitor->loop, fd, status, + write_watch_handler, NULL, + watch); + if (write_watch_event == NULL) + { + if (read_watch_event != NULL) + ply_event_loop_stop_watching_fd (monitor->loop, read_watch_event); + return FALSE; + } + dbus_watch_set_data (watch, write_watch_event, NULL); + } + + return TRUE; +} + +static void +remove_watch (DBusWatch *watch, void *data) +{ + ply_upstart_monitor_t *monitor = data; + ply_fd_watch_t *watch_event; + + assert (monitor != NULL); + assert (watch != NULL); + + watch_event = dbus_watch_get_data (watch); + if (watch_event == NULL) + return; + + ply_event_loop_stop_watching_fd (monitor->loop, watch_event); + + dbus_watch_set_data (watch, NULL, NULL); +} + +static void +toggled_watch (DBusWatch *watch, void *data) +{ + if (dbus_watch_get_enabled (watch)) + add_watch (watch, data); + else + remove_watch (watch, data); +} + +static ply_upstart_monitor_timeout_t * +timeout_user_data_new (ply_upstart_monitor_t *monitor, DBusTimeout *timeout) +{ + ply_upstart_monitor_timeout_t *monitor_timeout; + + monitor_timeout = calloc (1, sizeof (ply_upstart_monitor_timeout_t)); + monitor_timeout->monitor = monitor; + monitor_timeout->timeout = timeout; + + return monitor_timeout; +} + +static void +timeout_user_data_free (void *data) +{ + ply_upstart_monitor_timeout_t *monitor_timeout = data; + + free (monitor_timeout); +} + +static void +timeout_handler (void *data, ply_event_loop_t *loop) +{ + ply_upstart_monitor_timeout_t *monitor_timeout = data; + + assert (monitor_timeout != NULL); + + dbus_timeout_handle (monitor_timeout->timeout); +} + +static dbus_bool_t +add_timeout (DBusTimeout *timeout, void *data) +{ + ply_upstart_monitor_t *monitor = data; + int interval; + ply_upstart_monitor_timeout_t *monitor_timeout; + + assert (monitor != NULL); + assert (timeout != NULL); + + if (!dbus_timeout_get_enabled (timeout)) + return TRUE; + + interval = dbus_timeout_get_interval (timeout) * 1000; + + monitor_timeout = timeout_user_data_new (monitor, timeout); + + ply_event_loop_watch_for_timeout (monitor->loop, (double) interval, + timeout_handler, monitor_timeout); + + dbus_timeout_set_data (timeout, monitor_timeout, timeout_user_data_free); + + return TRUE; +} + +static void +remove_timeout (DBusTimeout *timeout, void *data) +{ + ply_upstart_monitor_t *monitor = data; + ply_upstart_monitor_timeout_t *monitor_timeout; + + assert (monitor != NULL); + assert (timeout != NULL); + + monitor_timeout = dbus_timeout_get_data (timeout); + if (monitor_timeout == NULL) + return; + + ply_event_loop_stop_watching_for_timeout (monitor->loop, + timeout_handler, monitor_timeout); + + dbus_timeout_set_data (timeout, NULL, NULL); +} + +static void +toggled_timeout (DBusTimeout *timeout, void *data) +{ + if (dbus_timeout_get_enabled (timeout)) + add_timeout (timeout, data); + else + remove_timeout (timeout, data); +} + +static void +dispatch_status (DBusConnection *connection, DBusDispatchStatus new_status, + void *data) +{ + ply_upstart_monitor_t *monitor = data; + uint64_t event_payload; + + assert (monitor != NULL); + + if (new_status != DBUS_DISPATCH_DATA_REMAINS) + return; + + /* wake up event loop */ + event_payload = 1; + ply_write (monitor->dispatch_fd, &event_payload, sizeof (event_payload)); +} + +static void +dispatch (void *data, int fd) +{ + ply_upstart_monitor_t *monitor = data; + uint64_t event_payload; + + assert (monitor != NULL); + + /* reset eventfd to zero */ + ply_read (fd, &event_payload, sizeof (event_payload)); + + while (dbus_connection_dispatch (monitor->connection) == + DBUS_DISPATCH_DATA_REMAINS) + ; +} + +bool +ply_upstart_monitor_connect_to_event_loop (ply_upstart_monitor_t *monitor, + ply_event_loop_t *loop) +{ + ply_fd_watch_t *dispatch_event = NULL; + uint64_t event_payload; + + assert (monitor != NULL); + + monitor->loop = loop; + monitor->dispatch_fd = -1; + + if (!dbus_connection_set_watch_functions (monitor->connection, + add_watch, + remove_watch, + toggled_watch, + monitor, NULL)) + goto err; + + if (!dbus_connection_set_timeout_functions (monitor->connection, + add_timeout, + remove_timeout, + toggled_timeout, + monitor, NULL)) + goto err; + + monitor->dispatch_fd = eventfd (0, EFD_CLOEXEC | EFD_NONBLOCK); + if (monitor->dispatch_fd < 0) + goto err; + /* make sure we wake up to dispatch the first time through */ + event_payload = 1; + ply_write (monitor->dispatch_fd, &event_payload, sizeof (event_payload)); + + dispatch_event = ply_event_loop_watch_fd (monitor->loop, + monitor->dispatch_fd, + PLY_EVENT_LOOP_FD_STATUS_HAS_DATA, + dispatch, NULL, monitor); + if (dispatch_event == NULL) + goto err; + + dbus_connection_set_dispatch_status_function (monitor->connection, + dispatch_status, + monitor, NULL); + + return true; + +err: + dbus_connection_set_watch_functions (monitor->connection, + NULL, NULL, NULL, NULL, NULL); + dbus_connection_set_timeout_functions (monitor->connection, + NULL, NULL, NULL, NULL, NULL); + dbus_connection_set_dispatch_status_function (monitor->connection, + NULL, NULL, NULL); + if (dispatch_event != NULL) + ply_event_loop_stop_watching_fd (monitor->loop, dispatch_event); + if (monitor->dispatch_fd >= 0) + { + close (monitor->dispatch_fd); + monitor->dispatch_fd = -1; + } + monitor->loop = NULL; + return false; +} + +void +ply_upstart_monitor_add_state_changed_handler (ply_upstart_monitor_t *monitor, + ply_upstart_monitor_state_changed_handler_t handler, + void *user_data) +{ + monitor->state_changed_handler = handler; + monitor->state_changed_data = user_data; +} + +void +ply_upstart_monitor_add_failed_handler (ply_upstart_monitor_t *monitor, + ply_upstart_monitor_failed_handler_t handler, + void *user_data) +{ + monitor->failed_handler = handler; + monitor->failed_data = user_data; +} +/* vim: set ts=4 sw=4 expandtab autoindent cindent cino={.5s,(0: */ diff --git a/src/upstart-bridge/ply-upstart-monitor.h b/src/upstart-bridge/ply-upstart-monitor.h new file mode 100644 index 0000000..810e57a --- /dev/null +++ b/src/upstart-bridge/ply-upstart-monitor.h @@ -0,0 +1,68 @@ +/* ply-upstart-monitor.h - Upstart D-Bus listener + * + * Copyright (C) 2010, 2011 Canonical Ltd. + * + * 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, or (at your option) + * any later version. + * + * This program 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 program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + * + * Written by: Colin Watson + */ +#ifndef PLY_UPSTART_H +#define PLY_UPSTART_H + +#include + +#include "ply-event-loop.h" + +typedef struct _ply_upstart_monitor ply_upstart_monitor_t; + +typedef struct { + char *name; + char *description; + bool is_task; +} ply_upstart_monitor_job_properties_t; + +typedef struct { + char *name; + char *goal; + char *state; + uint32_t failed : 1; +} ply_upstart_monitor_instance_properties_t; + +typedef void (* ply_upstart_monitor_state_changed_handler_t) (void *user_data, + const char *old_state, + ply_upstart_monitor_job_properties_t *job, + ply_upstart_monitor_instance_properties_t *instance); + +typedef void (* ply_upstart_monitor_failed_handler_t) (void *user_data, + ply_upstart_monitor_job_properties_t *job, + ply_upstart_monitor_instance_properties_t *instance, + int status); + +#ifndef PLY_HIDE_FUNCTION_DECLARATIONS +ply_upstart_monitor_t *ply_upstart_monitor_new (ply_event_loop_t *loop); +void ply_upstart_monitor_free (ply_upstart_monitor_t *upstart); +bool ply_upstart_monitor_connect_to_event_loop (ply_upstart_monitor_t *upstart, + ply_event_loop_t *loop); +void ply_upstart_monitor_add_state_changed_handler (ply_upstart_monitor_t *upstart, + ply_upstart_monitor_state_changed_handler_t handler, + void *user_data); +void ply_upstart_monitor_add_failed_handler (ply_upstart_monitor_t *upstart, + ply_upstart_monitor_failed_handler_t handler, + void *user_data); +#endif + +#endif +/* vim: set ts=4 sw=4 expandtab autoindent cindent cino={.5s,(0: */ diff --git a/src/upstart-bridge/plymouth-upstart-bridge.c b/src/upstart-bridge/plymouth-upstart-bridge.c new file mode 100644 index 0000000..7fe1f42 --- /dev/null +++ b/src/upstart-bridge/plymouth-upstart-bridge.c @@ -0,0 +1,339 @@ +/* plymouth-upstart-bridge.c - bridge Upstart job state changes to Plymouth + * + * Copyright (C) 2010, 2011 Canonical Ltd. + * + * 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, or (at your option) + * any later version. + * + * This program 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 program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + * + * Written by: Colin Watson + */ +#include "config.h" + +#include +#include +#include +#include +#include +#include + +#if defined(HAVE_NCURSESW_TERM_H) +#include +#elif defined(HAVE_NCURSES_TERM_H) +#include +#else +#include +#endif + +#include "ply-boot-client.h" +#include "ply-command-parser.h" +#include "ply-event-loop.h" +#include "ply-logger.h" +#include "ply-upstart-monitor.h" + +typedef struct +{ + ply_event_loop_t *loop; + ply_boot_client_t *client; + ply_upstart_monitor_t *upstart; + ply_command_parser_t *command_parser; +} state_t; + +#ifndef TERMINAL_COLOR_RED +#define TERMINAL_COLOR_RED 1 +#endif + +/* We don't care about the difference between "not a string capability" and + * "cancelled or absent". + */ +static const char * +get_string_capability (const char *name) +{ + const char *value; + + value = tigetstr ((char *) name); + if (value == (const char *) -1) + value = NULL; + + return value; +} + +static bool +terminal_ignores_new_line_after_80_chars (void) +{ + return tigetflag ((char *) "xenl") != 0; +} + +static int +get_number_of_columns (void) +{ + int number_of_columns; + + number_of_columns = tigetnum ((char *) "cols"); + + return number_of_columns; +} + +static bool +can_set_cursor_column (void) +{ + const char *capability; + + capability = get_string_capability ("hpa"); + + return capability != NULL; +} + +static void +set_cursor_column (int column) +{ + const char *capability; + const char *terminal_string; + + capability = get_string_capability ("hpa"); + terminal_string = tiparm (capability, column); + fputs (terminal_string, stdout); +} + +static bool +can_set_fg_color (void) +{ + const char *capability; + + capability = get_string_capability ("setaf"); + + return capability != NULL; +} + +static void +set_fg_color (int color) +{ + const char *capability; + const char *terminal_string; + + capability = get_string_capability ("setaf"); + terminal_string = tiparm (capability, color); + fputs (terminal_string, stdout); +} + +static void +unset_fg_color (void) +{ + const char *terminal_string; + + terminal_string = get_string_capability ("op"); + + if (terminal_string == NULL) + return; + + fputs (terminal_string, stdout); +} + +static void +update_status (state_t *state, + ply_upstart_monitor_job_properties_t *job, + ply_upstart_monitor_instance_properties_t *instance, + const char *action, + bool is_okay) +{ + ply_boot_client_update_daemon (state->client, job->name, NULL, NULL, state); + + if (job->description == NULL) + return; + + printf (" * %s%s%s", + action ? action : "", action ? " " : "", job->description); + + if (terminal_ignores_new_line_after_80_chars () && can_set_cursor_column ()) + { + int number_of_columns, column; + + number_of_columns = get_number_of_columns (); + + if (number_of_columns < (int) strlen("[fail]")) + number_of_columns = 80; + + column = number_of_columns - strlen ("[fail]") - 1; + + set_cursor_column (column); + + if (is_okay) + puts ("[ OK ]"); + else + { + bool supports_color; + + supports_color = can_set_fg_color (); + + fputs ("[", stdout); + + if (supports_color) + set_fg_color (TERMINAL_COLOR_RED); + + fputs ("fail", stdout); + + if (supports_color) + unset_fg_color (); + + puts ("]"); + } + } + else + { + if (is_okay) + puts (" ...done."); + else + puts (" ...fail!"); + } +} + +static void +on_failed (void *data, + ply_upstart_monitor_job_properties_t *job, + ply_upstart_monitor_instance_properties_t *instance, + int status) +{ + state_t *state = data; + + if (job->is_task) + update_status (state, job, instance, NULL, false); + else + { + if (strcmp (instance->goal, "start") == 0) + update_status (state, job, instance, "Starting", false); + else if (strcmp (instance->goal, "stop") == 0) + update_status (state, job, instance, "Stopping", false); + } +} + +static void +on_state_changed (state_t *state, + const char *old_state, + ply_upstart_monitor_job_properties_t *job, + ply_upstart_monitor_instance_properties_t *instance) +{ + if (instance->failed) + return; + + if (job->is_task) + { + if (strcmp (instance->state, "waiting") == 0) + update_status (state, job, instance, NULL, true); + } + else + { + if (strcmp (instance->goal, "start") == 0) + { + if (strcmp (instance->state, "running") == 0) + update_status (state, job, instance, "Starting", true); + } + else if (strcmp (instance->goal, "stop") == 0) + { + if (strcmp (instance->state, "waiting") == 0) + update_status (state, job, instance, "Stopping", true); + } + } +} + +static void +on_disconnect (state_t *state) +{ + ply_trace ("disconnected from boot status daemon"); + ply_event_loop_exit (state->loop, 0); +} + +int +main (int argc, + char **argv) +{ + state_t state = { 0 }; + bool should_help, should_be_verbose; + bool is_connected; + int exit_code; + + exit_code = 0; + + signal (SIGPIPE, SIG_IGN); + + state.loop = ply_event_loop_new (); + state.client = ply_boot_client_new (); + state.command_parser = ply_command_parser_new ("plymouth-upstart-bridge", "Upstart job state bridge"); + + ply_command_parser_add_options (state.command_parser, + "help", "This help message", PLY_COMMAND_OPTION_TYPE_FLAG, + "debug", "Enable verbose debug logging", PLY_COMMAND_OPTION_TYPE_FLAG, + NULL); + + if (!ply_command_parser_parse_arguments (state.command_parser, state.loop, argv, argc)) + { + char *help_string; + + help_string = ply_command_parser_get_help_string (state.command_parser); + + ply_error ("%s", help_string); + + free (help_string); + return 1; + } + + ply_command_parser_get_options (state.command_parser, + "help", &should_help, + "debug", &should_be_verbose, + NULL); + + if (should_help) + { + char *help_string; + + help_string = ply_command_parser_get_help_string (state.command_parser); + + puts (help_string); + + free (help_string); + return 0; + } + + if (should_be_verbose && !ply_is_tracing ()) + ply_toggle_tracing (); + + setupterm (NULL, STDOUT_FILENO, NULL); + + is_connected = ply_boot_client_connect (state.client, + (ply_boot_client_disconnect_handler_t) + on_disconnect, &state); + if (!is_connected) + { + ply_trace ("daemon not running"); + return 1; + } + + ply_boot_client_attach_to_event_loop (state.client, state.loop); + state.upstart = ply_upstart_monitor_new (state.loop); + if (!state.upstart) + return 1; + ply_upstart_monitor_add_state_changed_handler (state.upstart, + (ply_upstart_monitor_state_changed_handler_t) + on_state_changed, &state); + ply_upstart_monitor_add_failed_handler (state.upstart, on_failed, &state); + + exit_code = ply_event_loop_run (state.loop); + + ply_upstart_monitor_free (state.upstart); + ply_boot_client_free (state.client); + + ply_event_loop_free (state.loop); + + return exit_code; +} +/* vim: set ts=4 sw=4 expandtab autoindent cindent cino={.5s,(0: */