summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorColin Watson <cjwatson@ubuntu.com>2011-03-12 16:31:15 -0500
committerRay Strode <rstrode@redhat.com>2011-03-12 17:54:53 -0500
commit61e58596723efd504078ef3866913a0fa24da70c (patch)
tree20349add64cd1abd0e78c1a72aca0d0226f04c49
parent852c016e0dcbf1214ed43b20a01af058c03d77ff (diff)
upstart-bridge: add new helper program
This commit adds a new program, plymouth-upstart-bridge, the listens for upstart state changes and sends them to plymouth, or prints them out as appropriate.
-rw-r--r--.gitignore1
-rw-r--r--configure.ac18
-rw-r--r--src/Makefile.am3
-rw-r--r--src/upstart-bridge/Makefile.am21
-rw-r--r--src/upstart-bridge/ply-upstart-monitor.c1301
-rw-r--r--src/upstart-bridge/ply-upstart-monitor.h68
-rw-r--r--src/upstart-bridge/plymouth-upstart-bridge.c339
7 files changed, 1751 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore
index 215eb3df..29d2bde4 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 2209df1a..585e6c80 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 67b71dac..c6de8263 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 00000000..628ef206
--- /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 00000000..b19d95af
--- /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 <cjwatson@ubuntu.com>
+ */
+#include "config.h"
+#include "ply-upstart-monitor.h"
+
+#include <assert.h>
+#include <stdbool.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <sys/eventfd.h>
+
+#include <dbus/dbus.h>
+
+#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 00000000..810e57a9
--- /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 <cjwatson@ubuntu.com>
+ */
+#ifndef PLY_UPSTART_H
+#define PLY_UPSTART_H
+
+#include <stdbool.h>
+
+#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 00000000..7fe1f422
--- /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 <cjwatson@ubuntu.com>
+ */
+#include "config.h"
+
+#include <stdbool.h>
+#include <signal.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+
+#if defined(HAVE_NCURSESW_TERM_H)
+#include <ncursesw/term.h>
+#elif defined(HAVE_NCURSES_TERM_H)
+#include <ncurses/term.h>
+#else
+#include <term.h>
+#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: */