I've been working on solving the problem whereby users of Ubuntu server
systems (in particular) get no indication that Upstart jobs are
starting.  Following suggestions from Scott James Remnant and Ray
Strode, I've written a plymouth-upstart-bridge process which connects to
Upstart's D-Bus interface and provides updates to Plymouth as events
change state.  Here's a preliminary patch for review.

This patch depends on
https://code.launchpad.net/~cjwatson/upstart/state-changed and
https://code.launchpad.net/~cjwatson/upstart/goal-changed, which add a
few more features to Upstart's D-Bus interface.

A working Upstart job for this bridge process looks something like this:

  # plymouth-upstart-bridge - Bridge Upstart state changes into Plymouth
  #
  # This helper process receives Upstart state changes over D-Bus and sends
  # corresponding messages to Plymouth.
  
  description     "Bridge Upstart state changes into Plymouth"
  
  start on (started dbus
            or runlevel [06])
  stop on stopping plymouth
  
  console output
  
  exec plymouth-upstart-bridge

(Ideally this would have 'start on (started plymouth and started dbus)',
but since the plymouth job is often started in the initramfs and
imported into Upstart's view of the world by means of a gross hack, that
won't work properly until Upstart grows the concept of states.)

Known flaws, which may or may not be critical:

  * This still crashes now and again for me, and the output is a bit
    messy because it tends to overlap with 'console output' jobs that
    write their own progress messages.

  * This only tells you when a job has started rather than letting you
    know when it's starting, which isn't ideal because you can't see
    what the boot process is currently hung up on, but doing otherwise
    without crazily confusing overlapping messages requires something a
    good deal more sophisticated than just writing to the console.

  * The bridge process has some distribution-specific policy in it (the
    console output format).  Maybe folks can just patch this as
    necessary; I'm not sure.

  * I think probably plymouth-upstart-bridge ought to link against
    libdbus directly rather than having libply.la do so.

  * Since I had to use the low-level libdbus, the code is much longer
    than I'd have liked.

Still, I think this is far enough along to ask for review.  Comments?

Thanks,

-- 
Colin Watson                                       [[email protected]]
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 5bcf38a..5b7acbf 100644
--- a/configure.ac
+++ b/configure.ac
@@ -125,6 +125,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, AS_HELP_STRING([--enable-upstart],[listen for messages on the Upstart D-Bus interface]),enable_upstart=$enableval,enable_upstart=no)
+if test x$enable_upstart = 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, [test "$enable_upstart" = 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])
 
diff --git a/src/client/Makefile.am b/src/client/Makefile.am
index 9487901..518a0e8 100644
--- a/src/client/Makefile.am
+++ b/src/client/Makefile.am
@@ -46,5 +46,20 @@ uninstall-hook:
 	-rmdir -p $(DESTDIR)$(bindir)/rhgb-client >& /dev/null
 endif
 
+if ENABLE_UPSTART
+plymouth_PROGRAMS += plymouth-upstart-bridge
+
+plymouth_upstart_bridge_CFLAGS = $(PLYMOUTH_CFLAGS)
+plymouth_upstart_bridge_LDADD = \
+                      $(PLYMOUTH_LIBS) \
+                      $(CURSES_LIBS) \
+                      ../libply/libply.la
+plymouth_upstart_bridge_SOURCES = \
+                      $(srcdir)/../ply-boot-protocol.h                        \
+                      $(srcdir)/ply-boot-client.h                             \
+                      $(srcdir)/ply-boot-client.c                             \
+                      $(srcdir)/plymouth-upstart-bridge.c
+endif
+
 EXTRA_DIST = ply-boot-client.pc.in
 MAINTAINERCLEANFILES = Makefile.in
diff --git a/src/client/plymouth-upstart-bridge.c b/src/client/plymouth-upstart-bridge.c
new file mode 100644
index 0000000..81e9c51
--- /dev/null
+++ b/src/client/plymouth-upstart-bridge.c
@@ -0,0 +1,271 @@
+/* plymouth-upstart-bridge.c - bridge Upstart job state changes to Plymouth
+ *
+ * Copyright (C) 2010 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 <[email protected]>
+ */
+#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.h"
+
+typedef struct
+{
+  ply_event_loop_t     *loop;
+  ply_boot_client_t    *client;
+  ply_upstart_t        *upstart;
+  ply_command_parser_t *command_parser;
+} state_t;
+
+static void
+dummy_handler (void *user_data, ply_boot_client_t *client)
+{
+}
+
+/* We don't care about the difference between "not a string capability" and
+ * "cancelled or absent".
+ */
+static const char *
+get_string_capability (const char *capname)
+{
+  const char *value;
+
+  value = tigetstr (capname);
+  if (value == (const char *) -1)
+    value = NULL;
+
+  return value;
+}
+
+static void
+update_status (state_t *state,
+               ply_upstart_job_properties_t *job,
+               ply_upstart_instance_properties_t *instance,
+               const char *action, bool ok)
+{
+  int xenl;
+  const char *hpa;
+
+  ply_boot_client_update_daemon (state->client, job->name,
+                                 dummy_handler, NULL, state);
+
+  if (job->description == NULL)
+    return;
+
+  printf (" * %s%s%s",
+          action ? action : "", action ? " " : "", job->description);
+
+  xenl = tigetflag ("xenl");
+  hpa = get_string_capability ("hpa");
+
+  if (xenl > 0 && hpa)
+    {
+      int cols, col;
+      char *hpa_out;
+
+      cols = tigetnum ("cols");
+      if (cols < 6)
+        cols = 80;
+      col = cols - 7;
+
+      hpa_out = tiparm (hpa, col);
+      fputs (hpa_out, stdout);
+
+      if (ok)
+        puts ("[ OK ]");
+      else
+        {
+          const char *setaf, *op;
+          char *setaf_out = NULL;
+
+          setaf = get_string_capability ("setaf");
+          if (setaf)
+            setaf_out = tiparm (setaf, 1); /* red */
+          op = get_string_capability ("op");
+
+          printf ("[%sfail%s]\n", setaf_out ? setaf_out : "", op ? op : "");
+        }
+    }
+  else
+    {
+      if (ok)
+        puts ("   ...done.");
+      else
+        puts ("   ...fail!");
+    }
+}
+
+static void
+on_failed (void *data,
+           ply_upstart_job_properties_t *job,
+           ply_upstart_instance_properties_t *instance,
+           int status)
+{
+  state_t *state = data;
+
+  if (job->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 (void *data, const char *old_state,
+                  ply_upstart_job_properties_t *job,
+                  ply_upstart_instance_properties_t *instance)
+{
+  state_t      *state = data;
+
+  if (instance->failed)
+    return;
+
+  if (job->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_error ("error: unexpectedly disconnected from boot status daemon");
+
+  ply_trace ("disconnect");
+  ply_event_loop_exit (state->loop, 2);
+}
+
+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_new (state.loop);
+  if (!state.upstart)
+    return 1;
+  ply_upstart_add_state_changed_handler (state.upstart, on_state_changed,
+                                         &state);
+  ply_upstart_add_failed_handler (state.upstart, on_failed, &state);
+
+  exit_code = ply_event_loop_run (state.loop);
+
+  ply_upstart_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: */
diff --git a/src/libply/Makefile.am b/src/libply/Makefile.am
index 50b4d06..3e30845 100644
--- a/src/libply/Makefile.am
+++ b/src/libply/Makefile.am
@@ -50,4 +50,11 @@ libply_la_SOURCES = ply-event-loop.c                                          \
 		    ply-trigger.c                                             \
 		    ply-utils.c
 
+if ENABLE_UPSTART
+libply_la_CFLAGS  += $(DBUS_CFLAGS)
+libply_la_LDFLAGS += $(DBUS_LIBS)
+libply_HEADERS    += ply-upstart.h
+libply_la_SOURCES += ply-upstart.c
+endif
+
 MAINTAINERCLEANFILES = Makefile.in
diff --git a/src/libply/ply-event-loop.c b/src/libply/ply-event-loop.c
index 910b0ed..82ca4fc 100644
--- a/src/libply/ply-event-loop.c
+++ b/src/libply/ply-event-loop.c
@@ -104,6 +104,12 @@ typedef struct
   void                             *user_data;
 } ply_event_loop_timeout_watch_t;
 
+typedef struct
+{
+  ply_event_loop_idle_handler_t  handler;
+  void                          *user_data;
+} ply_event_loop_idle_closure_t;
+
 struct _ply_event_loop
 {
   int epoll_fd;
@@ -113,6 +119,7 @@ struct _ply_event_loop
   ply_list_t *sources;
   ply_list_t *exit_closures;
   ply_list_t *timeout_watches;
+  ply_list_t *idle_closures;
 
   ply_signal_dispatcher_t *signal_dispatcher;
 
@@ -498,6 +505,7 @@ ply_event_loop_new (void)
   loop->sources = ply_list_new ();
   loop->exit_closures = ply_list_new ();
   loop->timeout_watches = ply_list_new ();
+  loop->idle_closures = ply_list_new ();
 
   loop->signal_dispatcher = ply_signal_dispatcher_new ();
 
@@ -569,6 +577,48 @@ ply_event_loop_run_exit_closures (ply_event_loop_t *loop)
     }
 }
 
+static void
+ply_event_loop_free_idle_closures (ply_event_loop_t *loop)
+{
+  ply_list_node_t *node;
+
+  node = ply_list_get_first_node (loop->idle_closures);
+  while (node != NULL)
+    {
+      ply_list_node_t *next_node;
+      ply_event_loop_idle_closure_t *closure;
+
+      closure = (ply_event_loop_idle_closure_t *) ply_list_node_get_data (node);
+      next_node = ply_list_get_next_node (loop->idle_closures, node);
+      free (closure);
+
+      node = next_node;
+    }
+  ply_list_free (loop->idle_closures);
+}
+
+static void
+ply_event_loop_run_idle_closures (ply_event_loop_t *loop)
+{
+  ply_list_node_t *node;
+
+  node = ply_list_get_first_node (loop->idle_closures);
+  while (node != NULL)
+    {
+      ply_list_node_t *next_node;
+      ply_event_loop_idle_closure_t *closure;
+
+      closure = (ply_event_loop_idle_closure_t *) ply_list_node_get_data (node);
+
+      assert (closure->handler != NULL);
+      next_node = ply_list_get_next_node (loop->idle_closures, node);
+
+      closure->handler (closure->user_data, loop);
+
+      node = next_node;
+    }
+}
+
 void
 ply_event_loop_free (ply_event_loop_t *loop)
 {
@@ -580,6 +630,7 @@ ply_event_loop_free (ply_event_loop_t *loop)
 
   ply_signal_dispatcher_free (loop->signal_dispatcher);
   ply_event_loop_free_exit_closures (loop);
+  ply_event_loop_free_idle_closures (loop);
 
   ply_list_free (loop->sources);
   ply_list_free (loop->timeout_watches);
@@ -983,6 +1034,50 @@ ply_event_loop_stop_watching_for_timeout (ply_event_loop_t *loop,
     ply_trace ("no matching timeout found for removal");
 }
 
+void
+ply_event_loop_watch_for_idle (ply_event_loop_t              *loop,
+                               ply_event_loop_idle_handler_t  idle_handler,
+                               void                          *user_data)
+{
+  ply_event_loop_idle_closure_t *closure;
+
+  assert (loop != NULL);
+  assert (idle_handler != NULL);
+
+  closure = calloc (1, sizeof (ply_event_loop_idle_closure_t));
+  closure->handler = idle_handler;
+  closure->user_data = user_data;
+
+  ply_list_append_data (loop->idle_closures, closure);
+}
+
+void
+ply_event_loop_stop_watching_for_idle (ply_event_loop_t              *loop,
+                                       ply_event_loop_idle_handler_t  idle_handler,
+                                       void                          *user_data)
+{
+  ply_list_node_t *node;
+
+  node = ply_list_get_first_node (loop->idle_closures);
+  while (node != NULL)
+    {
+      ply_list_node_t *next_node;
+      ply_event_loop_idle_closure_t *closure;
+
+      closure = (ply_event_loop_idle_closure_t *) ply_list_node_get_data (node);
+      next_node = ply_list_get_next_node (loop->idle_closures, node);
+
+      if (closure->handler == idle_handler &&
+          closure->user_data == user_data)
+        {
+          ply_list_remove_node (loop->idle_closures, node);
+          free (closure);
+        }
+
+      node = next_node;
+    }
+}
+
 static ply_event_loop_fd_status_t
 ply_event_loop_get_fd_status_from_poll_mask (uint32_t mask)
 {
@@ -1336,6 +1431,8 @@ ply_event_loop_process_pending_events (ply_event_loop_t *loop)
         break;
     }
 
+  ply_event_loop_run_idle_closures (loop);
+
   /* Finally, kill off any unused sources
    */
   for (i = 0; i < number_of_received_events; i++)
diff --git a/src/libply/ply-event-loop.h b/src/libply/ply-event-loop.h
index ae29d28..2aa0ef3 100644
--- a/src/libply/ply-event-loop.h
+++ b/src/libply/ply-event-loop.h
@@ -44,6 +44,8 @@ typedef void (* ply_event_loop_exit_handler_t) (void *user_data,
                                                 ply_event_loop_t *loop);
 typedef void (* ply_event_loop_timeout_handler_t) (void             *user_data,
                                                    ply_event_loop_t *loop);
+typedef void (* ply_event_loop_idle_handler_t) (void             *user_data,
+                                                ply_event_loop_t *loop);
 
 #ifndef PLY_HIDE_FUNCTION_DECLARATIONS
 ply_event_loop_t *ply_event_loop_new (void);
@@ -79,6 +81,13 @@ void ply_event_loop_stop_watching_for_timeout (ply_event_loop_t    *loop,
                                                ply_event_loop_timeout_handler_t timeout_handler,
                                                void                 *user_data);
 
+void ply_event_loop_watch_for_idle (ply_event_loop_t              *loop,
+                                    ply_event_loop_idle_handler_t  idle_handler,
+                                    void                          *user_data);
+void ply_event_loop_stop_watching_for_idle (ply_event_loop_t              *loop,
+                                            ply_event_loop_idle_handler_t  idle_handler,
+                                            void                          *user_data);
+
 int ply_event_loop_run (ply_event_loop_t *loop);
 void ply_event_loop_exit (ply_event_loop_t *loop,
                           int               exit_code);
diff --git a/src/libply/ply-upstart.c b/src/libply/ply-upstart.c
new file mode 100644
index 0000000..bb1bbd8
--- /dev/null
+++ b/src/libply/ply-upstart.c
@@ -0,0 +1,1041 @@
+/* ply-upstart.c - Upstart D-Bus listener
+ *
+ * Copyright (C) 2010 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 <[email protected]>
+ */
+#include "config.h"
+#include "ply-upstart.h"
+
+#include <assert.h>
+#include <stdbool.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+
+#include <dbus/dbus.h>
+
+#include "ply-logger.h"
+#include "ply-event-loop.h"
+#include "ply-hashtable.h"
+
+typedef struct
+{
+  ply_upstart_t *upstart;
+  DBusTimeout   *timeout;
+} ply_upstart_timeout_t;
+
+struct _ply_upstart
+{
+  DBusConnection                      *connection;
+  char                                *owner;
+  ply_event_loop_t                    *loop;
+  ply_hashtable_t                     *jobs;
+  ply_hashtable_t                     *all_instances;
+  ply_upstart_state_changed_handler_t  state_changed_handler;
+  void                                *state_changed_data;
+  ply_upstart_failed_handler_t         failed_handler;
+  void                                *failed_data;
+};
+
+typedef struct
+{
+  ply_upstart_t                *upstart;
+  ply_upstart_job_properties_t  properties;
+  ply_hashtable_t              *instances;
+} ply_upstart_job_t;
+
+typedef struct
+{
+  ply_upstart_job_t                 *job;
+  ply_upstart_instance_properties_t  properties;
+} ply_upstart_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.
+ * Memory pools would make this so much easier!
+ */
+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;
+}
+
+static void
+instance_properties (DBusPendingCall *pending, void *data)
+{
+  ply_upstart_instance_t *instance = data;
+  DBusMessage *reply;
+  DBusMessageIter iter, arrayiter, dictiter, variantiter;
+  const char *key, *name, *goal, *state;
+
+  assert (pending != NULL);
+  assert (instance != NULL);
+
+  reply = dbus_pending_call_steal_reply (pending);
+  if (!reply)
+    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, &arrayiter);
+
+  while (dbus_message_iter_get_arg_type (&arrayiter) == DBUS_TYPE_DICT_ENTRY)
+    {
+      dbus_message_iter_recurse (&arrayiter, &dictiter);
+
+      if (dbus_message_iter_get_arg_type (&dictiter) != DBUS_TYPE_STRING)
+        goto next_item;
+
+      dbus_message_iter_get_basic (&dictiter, &key);
+      if (!key)
+        goto next_item;
+
+      dbus_message_iter_next (&dictiter);
+      if (dbus_message_iter_get_arg_type (&dictiter) != DBUS_TYPE_VARIANT)
+        goto next_item;
+      dbus_message_iter_recurse (&dictiter, &variantiter);
+      if (dbus_message_iter_get_arg_type (&variantiter) != DBUS_TYPE_STRING)
+        goto next_item;
+
+      if (strcmp (key, "name") == 0)
+        {
+          dbus_message_iter_get_basic (&variantiter, &name);
+          if (name)
+            {
+              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 (&variantiter, &goal);
+          if (goal)
+            {
+              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 (&variantiter, &state);
+          if (state)
+            {
+              ply_trace ("%s: state = '%s'",
+                         instance->job->properties.name, state);
+              instance->properties.state = strdup (state);
+            }
+        }
+
+next_item:
+      dbus_message_iter_next (&arrayiter);
+    }
+
+out:
+  dbus_message_unref (reply);
+}
+
+static void
+job_properties (DBusPendingCall *pending, void *data)
+{
+  ply_upstart_job_t *job = data;
+  DBusMessage *reply;
+  DBusMessageIter iter, arrayiter, dictiter, variantiter;
+  const char *key, *name, *description;
+  dbus_uint32_t task;
+
+  assert (pending != NULL);
+  assert (job != NULL);
+
+  reply = dbus_pending_call_steal_reply (pending);
+  if (!reply)
+    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, &arrayiter);
+
+  while (dbus_message_iter_get_arg_type (&arrayiter) == DBUS_TYPE_DICT_ENTRY)
+    {
+      dbus_message_iter_recurse (&arrayiter, &dictiter);
+
+      if (dbus_message_iter_get_arg_type (&dictiter) != DBUS_TYPE_STRING)
+        goto next_item;
+
+      dbus_message_iter_get_basic (&dictiter, &key);
+      if (!key)
+        goto next_item;
+
+      dbus_message_iter_next (&dictiter);
+      if (dbus_message_iter_get_arg_type (&dictiter) != DBUS_TYPE_VARIANT)
+        goto next_item;
+      dbus_message_iter_recurse (&dictiter, &variantiter);
+
+      if (strcmp (key, "name") == 0)
+        {
+          if (dbus_message_iter_get_arg_type (&variantiter) !=
+              DBUS_TYPE_STRING)
+            goto next_item;
+          dbus_message_iter_get_basic (&variantiter, &name);
+          if (name)
+            {
+              ply_trace ("name = '%s'", name);
+              job->properties.name = strdup (name);
+            }
+        }
+      else if (strcmp (key, "description") == 0)
+        {
+          if (dbus_message_iter_get_arg_type (&variantiter) !=
+              DBUS_TYPE_STRING)
+            goto next_item;
+          dbus_message_iter_get_basic (&variantiter, &description);
+          if (description)
+            {
+              ply_trace ("description = '%s'", description);
+              job->properties.description = strdup (description);
+            }
+        }
+      else if (strcmp (key, "task") == 0)
+        {
+          if (dbus_message_iter_get_arg_type (&variantiter) !=
+              DBUS_TYPE_BOOLEAN)
+            goto next_item;
+          dbus_message_iter_get_basic (&variantiter, &task);
+          ply_trace ("task = %s", task ? "TRUE" : "FALSE");
+          job->properties.task = task ? true : false;
+        }
+
+next_item:
+      dbus_message_iter_next (&arrayiter);
+    }
+
+out:
+  dbus_message_unref (reply);
+}
+
+static void
+remove_instance_internal (ply_upstart_job_t *job, const char *path)
+{
+  ply_upstart_instance_t *instance;
+
+  instance = hashtable_remove_and_free_key (job->instances, path);
+  if (instance == NULL)
+    return;
+  hashtable_remove_and_free_key (job->upstart->all_instances, path);
+  free (instance->properties.name);
+  free (instance->properties.goal);
+  free (instance->properties.state);
+  free (instance);
+}
+
+static void
+add_instance (ply_upstart_job_t *job, const char *path)
+{
+  ply_upstart_instance_t *instance;
+  DBusMessage *message;
+  const char *interface = UPSTART_INTERFACE_0_6_INSTANCE;
+  DBusPendingCall *pending;
+
+  ply_trace ("adding instance: %s", path);
+
+  remove_instance_internal (job, path);
+
+  instance = calloc (1, sizeof (ply_upstart_instance_t));
+  instance->job = job;
+  instance->properties.name = NULL;
+  instance->properties.goal = NULL;
+  instance->properties.state = NULL;
+  instance->properties.failed = 0;
+
+  /* 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->upstart->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->upstart->connection, message,
+                                   &pending, -1);
+  dbus_message_unref (message);
+  if (pending)
+    dbus_pending_call_set_notify (pending, instance_properties,
+                                  instance, NULL);
+}
+
+static void
+remove_instance (ply_upstart_job_t *job, const char *path)
+{
+  ply_trace ("removing instance: %s", path);
+
+  remove_instance_internal (job, path);
+}
+
+static void
+get_all_instances (DBusPendingCall *pending, void *data)
+{
+  ply_upstart_job_t *job = data;
+  DBusMessage *reply;
+  DBusError error;
+  char **instances;
+  int n_instances, i;
+
+  assert (pending != NULL);
+  assert (job != NULL);
+
+  reply = dbus_pending_call_steal_reply (pending);
+  if (!reply)
+    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_instance_t *instance = data;
+  ply_upstart_t *upstart = user_data;
+
+  assert (upstart != NULL);
+
+  if (instance == NULL)
+    return;
+
+  hashtable_remove_and_free_key (upstart->all_instances, path);
+  free (instance->properties.name);
+  free (instance->properties.goal);
+  free (instance->properties.state);
+  free (instance);
+}
+
+static void
+remove_job_internal (ply_upstart_t *upstart, const char *path)
+{
+  ply_upstart_job_t *job;
+
+  job = hashtable_remove_and_free_key (upstart->jobs, path);
+  if (job == NULL)
+    return;
+  free (job->properties.name);
+  free (job->properties.description);
+  ply_hashtable_foreach (job->instances, free_job_instance, upstart);
+  ply_hashtable_free (job->instances);
+  free (job);
+}
+
+static void
+add_job (ply_upstart_t *upstart, const char *path)
+{
+  ply_upstart_job_t *job;
+  DBusMessage *message;
+  const char *interface = UPSTART_INTERFACE_0_6_JOB;
+  DBusPendingCall *pending;
+
+  ply_trace ("adding job: %s", path);
+
+  remove_job_internal (upstart, path);
+
+  job = calloc (1, sizeof (ply_upstart_job_t));
+  job->upstart = upstart;
+  job->properties.name = NULL;
+  job->properties.description = NULL;
+  job->properties.task = false;
+  job->instances = ply_hashtable_new (ply_hashtable_string_hash,
+                                      ply_hashtable_string_compare);
+
+  ply_hashtable_insert (upstart->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 (upstart->connection, message, &pending, -1);
+  dbus_message_unref (message);
+  if (pending)
+    dbus_pending_call_set_notify (pending, job_properties, job, NULL);
+
+  /* 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 (upstart->connection, message, &pending, -1);
+  dbus_message_unref (message);
+  if (pending)
+    dbus_pending_call_set_notify (pending, get_all_instances, job, NULL);
+}
+
+static void
+remove_job (ply_upstart_t *upstart, const char *path)
+{
+  ply_trace ("removing job: %s", path);
+
+  remove_job_internal (upstart, path);
+}
+
+static void
+get_all_jobs (DBusPendingCall *pending, void *data)
+{
+  ply_upstart_t *upstart = data;
+  DBusMessage *reply;
+  DBusError error;
+  char **jobs;
+  int n_jobs, i;
+
+  assert (pending != NULL);
+  assert (upstart != NULL);
+
+  reply = dbus_pending_call_steal_reply (pending);
+  if (!reply)
+    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 (upstart, jobs[i]);
+
+  dbus_free_string_array (jobs);
+
+out:
+  dbus_message_unref (reply);
+}
+
+static void
+get_name_owner (DBusPendingCall *pending, void *data)
+{
+  ply_upstart_t *upstart = data;
+  DBusMessage *reply, *message;
+  DBusError error;
+  const char *owner;
+
+  assert (pending != NULL);
+  assert (upstart != NULL);
+
+  reply = dbus_pending_call_steal_reply (pending);
+  if (!reply)
+    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 (upstart->owner);
+  upstart->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 (upstart->connection, message, &pending, -1);
+  dbus_message_unref (message);
+  if (pending)
+    dbus_pending_call_set_notify (pending, get_all_jobs, upstart, NULL);
+
+out:
+  dbus_message_unref (reply);
+}
+
+static bool
+instance_initialised (ply_upstart_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 DBusHandlerResult
+message_handler (DBusConnection *connection, DBusMessage *message, void *data)
+{
+  ply_upstart_t *upstart = data;
+  DBusError error;
+  const char *path, *name, *old_owner, *new_owner, *signal_path, *goal, *state;
+  dbus_int32_t failed_status;
+  ply_upstart_job_t *job;
+  ply_upstart_instance_t *instance;
+  char *old_goal, *old_state;
+
+  assert (connection != NULL);
+  assert (message != NULL);
+  assert (upstart != 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))
+    {
+      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 (upstart->owner);
+          upstart->owner = new_owner ? strdup (new_owner) : NULL;
+        }
+    }
+
+  if (dbus_message_is_signal (message, UPSTART_INTERFACE_0_6,
+                              "JobAdded"))
+    {
+      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 (upstart, signal_path);
+      dbus_error_free (&error);
+      return DBUS_HANDLER_RESULT_HANDLED;
+    }
+
+  if (dbus_message_is_signal (message, UPSTART_INTERFACE_0_6,
+                              "JobRemoved"))
+    {
+      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 (upstart, signal_path);
+      dbus_error_free (&error);
+      return DBUS_HANDLER_RESULT_HANDLED;
+    }
+
+  if (dbus_message_is_signal (message, UPSTART_INTERFACE_0_6_JOB,
+                              "InstanceAdded"))
+    {
+      ply_trace ("got %s InstanceAdded", path);
+      job = ply_hashtable_lookup (upstart->jobs, (void *) path);
+      if (job)
+        {
+          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;
+    }
+
+  if (dbus_message_is_signal (message, UPSTART_INTERFACE_0_6_JOB,
+                              "InstanceRemoved"))
+    {
+      ply_trace ("got %s InstanceRemoved", path);
+      job = ply_hashtable_lookup (upstart->jobs, (void *) path);
+      if (job)
+        {
+          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;
+    }
+
+  if (dbus_message_is_signal (message, UPSTART_INTERFACE_0_6_INSTANCE,
+                              "GoalChanged"))
+    {
+      ply_trace ("got %s GoalChanged", path);
+      instance = ply_hashtable_lookup (upstart->all_instances, (void *) path);
+      if (instance)
+        {
+          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;
+    }
+
+  if (dbus_message_is_signal (message, UPSTART_INTERFACE_0_6_INSTANCE,
+                              "StateChanged"))
+    {
+      ply_trace ("got %s StateChanged", path);
+      instance = ply_hashtable_lookup (upstart->all_instances, (void *) path);
+      if (instance)
+        {
+          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;
+              if (instance_initialised (instance) &&
+                  upstart->state_changed_handler)
+                upstart->state_changed_handler (upstart->state_changed_data,
+                                                old_state,
+                                                &instance->job->properties,
+                                                &instance->properties);
+              free (old_state);
+            }
+          dbus_error_free (&error);
+        }
+      return DBUS_HANDLER_RESULT_HANDLED;
+    }
+
+  if (dbus_message_is_signal (message, UPSTART_INTERFACE_0_6_INSTANCE,
+                              "Failed"))
+    {
+      ply_trace ("got %s Failed", path);
+      instance = ply_hashtable_lookup (upstart->all_instances, (void *) path);
+      if (instance)
+        {
+          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_initialised (instance) && upstart->failed_handler)
+                upstart->failed_handler (upstart->failed_data,
+                                         &instance->job->properties,
+                                         &instance->properties,
+                                         (int) failed_status);
+            }
+          dbus_error_free (&error);
+        }
+      return DBUS_HANDLER_RESULT_HANDLED;
+    }
+
+  return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
+}
+
+ply_upstart_t *
+ply_upstart_new (ply_event_loop_t *loop)
+{
+  DBusError error;
+  DBusConnection *connection;
+  ply_upstart_t *upstart;
+  char *rule;
+  DBusMessage *message;
+  const char *upstart_service = UPSTART_SERVICE;
+  DBusPendingCall *pending;
+
+  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);
+
+  upstart = calloc (1, sizeof (ply_upstart_t));
+  upstart->connection = connection;
+  upstart->loop = NULL;
+  upstart->jobs = ply_hashtable_new (ply_hashtable_string_hash,
+                                     ply_hashtable_string_compare);
+  upstart->all_instances = ply_hashtable_new (ply_hashtable_string_hash,
+                                              ply_hashtable_string_compare);
+  upstart->state_changed_handler = NULL;
+  upstart->state_changed_data = NULL;
+  upstart->failed_handler = NULL;
+  upstart->failed_data = NULL;
+
+  if (!dbus_connection_add_filter (connection, message_handler, upstart, NULL))
+    {
+      ply_error ("unable to add filter to system bus connection");
+      ply_upstart_free (upstart);
+      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_free (upstart);
+      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_free (upstart);
+      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, &upstart_service,
+                            DBUS_TYPE_INVALID);
+  dbus_connection_send_with_reply (connection, message, &pending, -1);
+  dbus_message_unref (message);
+  if (pending)
+    dbus_pending_call_set_notify (pending, get_name_owner, upstart, NULL);
+
+  if (loop)
+    ply_upstart_connect_to_event_loop (upstart, loop);
+
+  return upstart;
+}
+
+void
+ply_upstart_free (ply_upstart_t *upstart)
+{
+  if (upstart == NULL)
+    return;
+
+  ply_hashtable_free (upstart->all_instances);
+  ply_hashtable_free (upstart->jobs);
+  dbus_connection_unref (upstart->connection);
+  free (upstart);
+}
+
+static void
+watch_handler (void *data, int fd)
+{
+  DBusWatch *watch = data;
+
+  assert (watch != NULL);
+
+  /* ply_event_loop doesn't tell us which flags were set; just asserting all
+   * of them should generally work OK, though.
+   */
+  dbus_watch_handle (watch, DBUS_WATCH_READABLE | DBUS_WATCH_WRITABLE);
+}
+
+static dbus_bool_t
+add_watch (DBusWatch *watch, void *data)
+{
+  ply_upstart_t *upstart = data;
+  unsigned int flags;
+  ply_event_loop_fd_status_t status = 0;
+  ply_fd_watch_t *watch_event;
+
+  assert (upstart != 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);
+  if (flags & DBUS_WATCH_READABLE)
+    status |= PLY_EVENT_LOOP_FD_STATUS_HAS_DATA;
+  if (flags & DBUS_WATCH_WRITABLE)
+    status |= PLY_EVENT_LOOP_FD_STATUS_CAN_TAKE_DATA;
+
+  watch_event = ply_event_loop_watch_fd (upstart->loop,
+                                         dbus_watch_get_unix_fd (watch),
+                                         status,
+                                         watch_handler, NULL,
+                                         watch);
+  if (watch_event == NULL)
+    return FALSE;
+
+  dbus_watch_set_data (watch, watch_event, NULL);
+
+  return TRUE;
+}
+
+static void
+remove_watch (DBusWatch *watch, void *data)
+{
+  ply_upstart_t *upstart = data;
+  ply_fd_watch_t *watch_event;
+
+  assert (upstart != NULL);
+  assert (watch != NULL);
+
+  watch_event = dbus_watch_get_data (watch);
+  if (watch_event == NULL)
+    return;
+
+  ply_event_loop_stop_watching_fd (upstart->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_timeout_t *
+timeout_user_data_new (ply_upstart_t *upstart, DBusTimeout *timeout)
+{
+  ply_upstart_timeout_t *upstart_timeout;
+
+  upstart_timeout = calloc (1, sizeof (ply_upstart_timeout_t));
+  upstart_timeout->upstart = upstart;
+  upstart_timeout->timeout = timeout;
+
+  return upstart_timeout;
+}
+
+static void
+timeout_user_data_free (void *data)
+{
+  ply_upstart_timeout_t *upstart_timeout = data;
+
+  free (upstart_timeout);
+}
+
+static void
+timeout_handler (void *data, ply_event_loop_t *loop)
+{
+  ply_upstart_timeout_t *upstart_timeout = data;
+
+  assert (upstart_timeout != NULL);
+
+  dbus_timeout_handle (upstart_timeout->timeout);
+}
+
+static dbus_bool_t
+add_timeout (DBusTimeout *timeout, void *data)
+{
+  ply_upstart_t *upstart = data;
+  int interval;
+  ply_upstart_timeout_t *upstart_timeout;
+
+  assert (upstart != NULL);
+  assert (timeout != NULL);
+
+  if (!dbus_timeout_get_enabled (timeout))
+    return TRUE;
+
+  interval = dbus_timeout_get_interval (timeout) * 1000;
+
+  upstart_timeout = timeout_user_data_new (upstart, timeout);
+
+  ply_event_loop_watch_for_timeout (upstart->loop, (double) interval,
+                                    timeout_handler, upstart_timeout);
+
+  dbus_timeout_set_data (timeout, upstart_timeout, timeout_user_data_free);
+
+  return TRUE;
+}
+
+static void
+remove_timeout (DBusTimeout *timeout, void *data)
+{
+  ply_upstart_t *upstart = data;
+  ply_upstart_timeout_t *upstart_timeout;
+
+  assert (upstart != NULL);
+  assert (timeout != NULL);
+
+  upstart_timeout = dbus_timeout_get_data (timeout);
+  if (upstart_timeout == NULL)
+    return;
+
+  ply_event_loop_stop_watching_for_timeout (upstart->loop,
+                                            timeout_handler, upstart_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
+idle (void *data, ply_event_loop_t *loop)
+{
+  ply_upstart_t *upstart = data;
+
+  assert (upstart != NULL);
+
+  while (dbus_connection_dispatch (upstart->connection) ==
+         DBUS_DISPATCH_DATA_REMAINS)
+    ;
+}
+
+bool
+ply_upstart_connect_to_event_loop (ply_upstart_t    *upstart,
+                                   ply_event_loop_t *loop)
+{
+  assert (upstart != NULL);
+
+  upstart->loop = loop;
+
+  if (!dbus_connection_set_watch_functions (upstart->connection,
+                                            add_watch,
+                                            remove_watch,
+                                            toggled_watch,
+                                            upstart, NULL))
+    goto err;
+
+  if (!dbus_connection_set_timeout_functions (upstart->connection,
+                                              add_timeout,
+                                              remove_timeout,
+                                              toggled_timeout,
+                                              upstart, NULL))
+    goto err;
+
+  ply_event_loop_watch_for_idle (upstart->loop, idle, upstart);
+
+  return true;
+
+err:
+  dbus_connection_set_watch_functions (upstart->connection,
+                                       NULL, NULL, NULL, NULL, NULL);
+  dbus_connection_set_timeout_functions (upstart->connection,
+                                         NULL, NULL, NULL, NULL, NULL);
+  upstart->loop = NULL;
+  return false;
+}
+
+void
+ply_upstart_add_state_changed_handler (ply_upstart_t                       *upstart,
+                                       ply_upstart_state_changed_handler_t  handler,
+                                       void                                *user_data)
+{
+  upstart->state_changed_handler = handler;
+  upstart->state_changed_data = user_data;
+}
+
+void
+ply_upstart_add_failed_handler (ply_upstart_t                *upstart,
+                                ply_upstart_failed_handler_t  handler,
+                                void                         *user_data)
+{
+  upstart->failed_handler = handler;
+  upstart->failed_data = user_data;
+}
+/* vim: set ts=4 sw=4 expandtab autoindent cindent cino={.5s,(0: */
diff --git a/src/libply/ply-upstart.h b/src/libply/ply-upstart.h
new file mode 100644
index 0000000..bf0df9a
--- /dev/null
+++ b/src/libply/ply-upstart.h
@@ -0,0 +1,68 @@
+/* ply-upstart.h - Upstart D-Bus listener
+ *
+ * Copyright (C) 2010 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 <[email protected]>
+ */
+#ifndef PLY_UPSTART_H
+#define PLY_UPSTART_H
+
+#include <stdbool.h>
+
+#include "ply-event-loop.h"
+
+typedef struct _ply_upstart ply_upstart_t;
+
+typedef struct {
+  char *name;
+  char *description;
+  bool  task;
+} ply_upstart_job_properties_t;
+
+typedef struct {
+  char *name;
+  char *goal;
+  char *state;
+  int   failed;
+} ply_upstart_instance_properties_t;
+
+typedef void (* ply_upstart_state_changed_handler_t) (void                              *user_data,
+                                                      const char                        *old_state,
+                                                      ply_upstart_job_properties_t      *job,
+                                                      ply_upstart_instance_properties_t *instance);
+
+typedef void (* ply_upstart_failed_handler_t) (void                              *user_data,
+                                               ply_upstart_job_properties_t      *job,
+                                               ply_upstart_instance_properties_t *instance,
+                                               int                                status);
+
+#ifndef PLY_HIDE_FUNCTION_DECLARATIONS
+ply_upstart_t *ply_upstart_new (ply_event_loop_t *loop);
+void ply_upstart_free (ply_upstart_t *upstart);
+bool ply_upstart_connect_to_event_loop (ply_upstart_t    *upstart,
+                                        ply_event_loop_t *loop);
+void ply_upstart_add_state_changed_handler (ply_upstart_t                       *upstart,
+                                            ply_upstart_state_changed_handler_t  handler,
+                                            void                                *user_data);
+void ply_upstart_add_failed_handler (ply_upstart_t                *upstart,
+                                     ply_upstart_failed_handler_t  handler,
+                                     void                         *user_data);
+#endif
+
+#endif
+/* vim: set ts=4 sw=4 expandtab autoindent cindent cino={.5s,(0: */
-- 
upstart-devel mailing list
[email protected]
Modify settings or unsubscribe at: 
https://lists.ubuntu.com/mailman/listinfo/upstart-devel

Reply via email to