[PATCH 3/3] activation: optionally, use systemd for system bus activation

Lennart Poettering mzqohf at 0pointer.de
Wed Jun 23 09:12:07 PDT 2010


Hooks up service activation with systemd. .service files may choose to
define a systemd service to start, but if they don't it's fine, too.

---
 bus/activation.c   |  177 +++++++++++++++++++++++++++++++++++++++++++++++++++-
 bus/activation.h   |    3 +-
 bus/bus.c          |   13 ++++-
 bus/bus.h          |    2 +
 bus/desktop-file.h |    1 +
 bus/driver.c       |    8 +++
 bus/main.c         |    7 ++-
 bus/test.c         |    2 +-
 8 files changed, 208 insertions(+), 5 deletions(-)

diff --git a/bus/activation.c b/bus/activation.c
index 9d454c7..ee5efa8 100644
--- a/bus/activation.c
+++ b/bus/activation.c
@@ -70,6 +70,7 @@ typedef struct
   char *name;
   char *exec;
   char *user;
+  char *systemd_service;
   unsigned long mtime;
   BusServiceDirectory *s_dir;
   char *filename;
@@ -91,6 +92,7 @@ typedef struct
   BusActivation *activation;
   char *service_name;
   char *exec;
+  char *systemd_service;
   DBusList *entries;
   int n_entries;
   DBusBabysitter *babysitter;
@@ -198,6 +200,7 @@ bus_pending_activation_unref (BusPendingActivation *pending_activation)
   
   dbus_free (pending_activation->service_name);
   dbus_free (pending_activation->exec);
+  dbus_free (pending_activation->systemd_service);
 
   link = _dbus_list_get_first_link (&pending_activation->entries);
 
@@ -244,6 +247,7 @@ bus_activation_entry_unref (BusActivationEntry *entry)
   dbus_free (entry->exec);
   dbus_free (entry->user);
   dbus_free (entry->filename);
+  dbus_free (entry->systemd_service);
 
   dbus_free (entry);
 }
@@ -255,7 +259,7 @@ update_desktop_file_entry (BusActivation       *activation,
                            BusDesktopFile      *desktop_file,
                            DBusError           *error)
 {
-  char *name, *exec, *user, *exec_tmp;
+  char *name, *exec, *user, *exec_tmp, *systemd_service;
   BusActivationEntry *entry;
   DBusStat stat_buf;
   DBusString file_path;
@@ -268,6 +272,7 @@ update_desktop_file_entry (BusActivation       *activation,
   user = NULL;
   exec_tmp = NULL;
   entry = NULL;
+  systemd_service = NULL;
   
   dbus_error_init (&tmp_error);
 
@@ -328,6 +333,30 @@ update_desktop_file_entry (BusActivation       *activation,
     }
   _DBUS_ASSERT_ERROR_IS_CLEAR (&tmp_error);
 
+  /* systemd service is never required */
+  if (!bus_desktop_file_get_string (desktop_file,
+                                    DBUS_SERVICE_SECTION,
+                                    DBUS_SERVICE_SYSTEMD_SERVICE,
+                                    &systemd_service, &tmp_error))
+    {
+      _DBUS_ASSERT_ERROR_IS_SET (&tmp_error);
+      /* if we got OOM, then exit */
+      if (dbus_error_has_name (&tmp_error, DBUS_ERROR_NO_MEMORY))
+        {
+          dbus_move_error (&tmp_error, error);
+          goto failed;
+        }
+      else
+        {
+          /* if we have error because we didn't find anything then continue */
+          dbus_error_free (&tmp_error);
+          dbus_free (systemd_service);
+          systemd_service = NULL;
+        }
+    }
+
+  _DBUS_ASSERT_ERROR_IS_CLEAR (&tmp_error);
+
   entry = _dbus_hash_table_lookup_string (s_dir->entries, 
                                           _dbus_string_get_const_data (filename));
 
@@ -355,6 +384,7 @@ update_desktop_file_entry (BusActivation       *activation,
       entry->name = name;
       entry->exec = exec;
       entry->user = user;
+      entry->systemd_service = systemd_service;
       entry->refcount = 1;
     
       entry->s_dir = s_dir;
@@ -396,6 +426,8 @@ update_desktop_file_entry (BusActivation       *activation,
       dbus_free (entry->name);
       dbus_free (entry->exec);
       dbus_free (entry->user);
+      dbus_free (entry->systemd_service);
+      entry->systemd_service = systemd_service;
       entry->name = name;
       entry->exec = exec;
       entry->user = user;
@@ -423,6 +455,7 @@ failed:
   dbus_free (name);
   dbus_free (exec_tmp);
   dbus_free (user);
+  dbus_free (systemd_service);
   _dbus_string_free (&file_path);
 
   if (entry)
@@ -1770,6 +1803,19 @@ bus_activation_activate_service (BusActivation  *activation,
           return FALSE;
         }
 
+      if (entry->systemd_service)
+        {
+          pending_activation->systemd_service = _dbus_strdup (entry->systemd_service);
+          if (!pending_activation->systemd_service)
+            {
+              _dbus_verbose ("Failed to copy systemd service for pending activation\n");
+              BUS_SET_OOM (error);
+              bus_pending_activation_unref (pending_activation);
+              bus_pending_activation_entry_free (pending_activation_entry);
+              return FALSE;
+            }
+        }
+
       pending_activation->timeout =
         _dbus_timeout_new (bus_context_get_activation_timeout (activation->context),
                            pending_activation_timed_out,
@@ -1852,6 +1898,95 @@ bus_activation_activate_service (BusActivation  *activation,
   if (activated)
     return TRUE;
 
+  if (bus_context_get_systemd_activation (activation->context))
+    {
+      if (strcmp (service_name, "org.freedesktop.systemd1") == 0)
+          /* systemd itself is missing apparently. That can happen
+             only during early startup. Let's just wait until systemd
+             connects to us and do nothing. */
+        return TRUE;
+
+      if (entry->systemd_service)
+        {
+          BusTransaction *activation_transaction;
+          DBusString service_string;
+          BusService *service;
+          BusRegistry *registry;
+
+          /* OK, we have a systemd service configured for this entry,
+             hence let's enqueue an activation request message. This
+             is implemented as a directed signal, not a method call,
+             for three reasons: 1) we don't expect a response on
+             success, where we just expect a name appearing on the
+             bus; 2) at this time the systemd service might not yet
+             have connected, so we wouldn't know the message serial at
+             this point to set up a pending call; 3) it is ugly if the
+             bus suddenly becomes the caller of a remote method. */
+
+          message = dbus_message_new_signal (DBUS_PATH_DBUS,
+                                             "org.freedesktop.systemd1.Activator",
+                                             "ActivationRequest");
+          if (!message)
+            {
+              _dbus_verbose ("No memory to create activation message\n");
+              BUS_SET_OOM (error);
+              return FALSE;
+            }
+
+          if (!dbus_message_set_sender (message, DBUS_SERVICE_DBUS) ||
+              !dbus_message_set_destination (message, "org.freedesktop.systemd1") ||
+              !dbus_message_append_args (message,
+                                         DBUS_TYPE_STRING, &entry->systemd_service,
+                                         DBUS_TYPE_INVALID))
+            {
+              _dbus_verbose ("No memory to set args of activation message\n");
+              dbus_message_unref (message);
+              BUS_SET_OOM (error);
+              return FALSE;
+            }
+
+          /* Create our transaction */
+          activation_transaction = bus_transaction_new (activation->context);
+          if (activation_transaction == NULL)
+            {
+              _dbus_verbose ("No memory to create activation transaction\n");
+              dbus_message_unref (message);
+              BUS_SET_OOM (error);
+              return FALSE;
+            }
+
+          /* Check whether systemd is already connected */
+          registry = bus_connection_get_registry (connection);
+          _dbus_string_init_const (&service_string, "org.freedesktop.systemd1");
+          service = bus_registry_lookup (registry, &service_string);
+
+          if (service != NULL)
+            /* Wonderful, systemd is connected, let's just send the msg */
+            retval = bus_dispatch_matches (activation_transaction, NULL, bus_service_get_primary_owners_connection (service),
+                                           message, error);
+          else
+            /* systemd is not around, let's "activate" it. */
+            retval = bus_activation_activate_service (activation, connection, activation_transaction, TRUE,
+                                                      message, "org.freedesktop.systemd1", error);
+
+          dbus_message_unref (message);
+
+          if (!retval)
+            {
+              _DBUS_ASSERT_ERROR_IS_SET (error);
+              _dbus_verbose ("failed to send activation message: %s\n", error->name);
+              bus_transaction_cancel_and_free (activation_transaction);
+              return FALSE;
+            }
+
+          bus_transaction_execute_and_free (activation_transaction);
+          return TRUE;
+        }
+
+      /* OK, we have no configured systemd service, hence let's
+         proceed with traditional activation. */
+    }
+
   /* use command as system and session different */
   if (!_dbus_string_init (&command))
     {
@@ -2010,6 +2145,46 @@ bus_activation_list_services (BusActivation *activation,
   return FALSE;
 }
   
+dbus_bool_t
+dbus_activation_systemd_failure (BusActivation *activation,
+                                 DBusMessage   *message)
+{
+  DBusError error;
+  const char *code, *str, *unit = NULL;
+
+  dbus_error_init(&error);
+
+  /* This is called whenever the systemd activator sent us a
+     response. We'll invalidate all pending activations that match the
+     unit name. */
+
+  if (dbus_message_get_args (message, &error,
+                             DBUS_TYPE_STRING, &unit,
+                             DBUS_TYPE_STRING, &code,
+                             DBUS_TYPE_STRING, &str,
+                             DBUS_TYPE_INVALID))
+    dbus_set_error(&error, code, str);
+
+  if (unit)
+    {
+      DBusHashIter iter;
+
+      _dbus_hash_iter_init (activation->pending_activations,
+                            &iter);
+
+      while (_dbus_hash_iter_next (&iter))
+        {
+          BusPendingActivation *p = _dbus_hash_iter_get_value (&iter);
+
+          if (p->systemd_service && strcmp (p->systemd_service, unit) == 0)
+            pending_activation_failed(p, &error);
+        }
+    }
+
+  dbus_error_free(&error);
+
+  return TRUE;
+}
 
 #ifdef DBUS_BUILD_TESTS
 
diff --git a/bus/activation.h b/bus/activation.h
index 03bfed2..97f25b1 100644
--- a/bus/activation.h
+++ b/bus/activation.h
@@ -57,6 +57,8 @@ dbus_bool_t    bus_activation_service_created  (BusActivation     *activation,
 dbus_bool_t    bus_activation_list_services    (BusActivation     *registry,
 						char            ***listp,
 						int               *array_len);
+dbus_bool_t    dbus_activation_systemd_failure (BusActivation     *activation,
+                                                DBusMessage       *message);
 
 dbus_bool_t    bus_activation_send_pending_auto_activation_messages (BusActivation     *activation,
 								     BusService        *service,
@@ -64,5 +66,4 @@ dbus_bool_t    bus_activation_send_pending_auto_activation_messages (BusActivati
 								     DBusError         *error);
 
 
-
 #endif /* BUS_ACTIVATION_H */
diff --git a/bus/bus.c b/bus/bus.c
index 8687fb6..1f8994c 100644
--- a/bus/bus.c
+++ b/bus/bus.c
@@ -60,6 +60,7 @@ struct BusContext
   unsigned int syslog : 1;
   unsigned int keep_umask : 1;
   unsigned int allow_anonymous : 1;
+  unsigned int systemd_activation : 1;
 };
 
 static dbus_int32_t server_data_slot = -1;
@@ -276,6 +277,7 @@ static dbus_bool_t
 process_config_first_time_only (BusContext       *context,
 				BusConfigParser  *parser,
                                 const DBusString *address,
+                                dbus_bool_t      systemd_activation,
 				DBusError        *error)
 {
   DBusString log_prefix;
@@ -292,6 +294,8 @@ process_config_first_time_only (BusContext       *context,
   retval = FALSE;
   auth_mechanisms = NULL;
 
+  context->systemd_activation = systemd_activation;
+
   /* Check for an existing pid file. Of course this is a race;
    * we'd have to use fcntl() locks on the pid file to
    * avoid that. But we want to check for the pid file
@@ -652,6 +656,7 @@ bus_context_new (const DBusString *config_file,
                  DBusPipe         *print_addr_pipe,
                  DBusPipe         *print_pid_pipe,
                  const DBusString *address,
+                 dbus_bool_t      systemd_activation,
                  DBusError        *error)
 {
   DBusString log_prefix;
@@ -706,7 +711,7 @@ bus_context_new (const DBusString *config_file,
       goto failed;
     }
 
-  if (!process_config_first_time_only (context, parser, address, error))
+  if (!process_config_first_time_only (context, parser, address, systemd_activation, error))
     {
       _DBUS_ASSERT_ERROR_IS_SET (error);
       goto failed;
@@ -1084,6 +1089,12 @@ bus_context_get_servicehelper (BusContext *context)
   return context->servicehelper;
 }
 
+dbus_bool_t
+bus_context_get_systemd_activation (BusContext *context)
+{
+  return context->systemd_activation;
+}
+
 BusRegistry*
 bus_context_get_registry (BusContext  *context)
 {
diff --git a/bus/bus.h b/bus/bus.h
index b5c7c20..ebef17c 100644
--- a/bus/bus.h
+++ b/bus/bus.h
@@ -76,6 +76,7 @@ BusContext*       bus_context_new                                (const DBusStri
                                                                   DBusPipe         *print_addr_pipe,
                                                                   DBusPipe         *print_pid_pipe,
                                                                   const DBusString *address,
+                                                                  dbus_bool_t      systemd_activation,
                                                                   DBusError        *error);
 dbus_bool_t       bus_context_reload_config                      (BusContext       *context,
 								  DBusError        *error);
@@ -87,6 +88,7 @@ dbus_bool_t       bus_context_get_id                             (BusContext
 const char*       bus_context_get_type                           (BusContext       *context);
 const char*       bus_context_get_address                        (BusContext       *context);
 const char*       bus_context_get_servicehelper                  (BusContext       *context);
+dbus_bool_t       bus_context_get_systemd_activation             (BusContext       *context);
 BusRegistry*      bus_context_get_registry                       (BusContext       *context);
 BusConnections*   bus_context_get_connections                    (BusContext       *context);
 BusActivation*    bus_context_get_activation                     (BusContext       *context);
diff --git a/bus/desktop-file.h b/bus/desktop-file.h
index 7f43458..58e78e8 100644
--- a/bus/desktop-file.h
+++ b/bus/desktop-file.h
@@ -35,6 +35,7 @@
 #define DBUS_SERVICE_EXEC     "Exec"
 #define DBUS_SERVICE_USER     "User"
 #define DBUS_SERVICE_GROUP    "Group"
+#define DBUS_SERVICE_SYSTEMD_SERVICE "SystemdService"
 
 typedef struct BusDesktopFile BusDesktopFile;
 
diff --git a/bus/driver.c b/bus/driver.c
index 0dcfdaa..cc8d1f2 100644
--- a/bus/driver.c
+++ b/bus/driver.c
@@ -1930,6 +1930,14 @@ bus_driver_handle_message (DBusConnection *connection,
 
   _DBUS_ASSERT_ERROR_IS_CLEAR (error);
 
+  if (dbus_message_is_signal (message, "org.freedesktop.systemd1.Activator", "ActivationFailure"))
+    {
+      BusContext *context;
+
+      context = bus_connection_get_context (connection);
+      return dbus_activation_systemd_failure(bus_context_get_activation(context), message);
+    }
+
   if (dbus_message_get_type (message) != DBUS_MESSAGE_TYPE_METHOD_CALL)
     {
       _dbus_verbose ("Driver got a non-method-call message, ignoring\n");
diff --git a/bus/main.c b/bus/main.c
index 603e1f8..73a2fe4 100644
--- a/bus/main.c
+++ b/bus/main.c
@@ -75,7 +75,7 @@ signal_handler (int sig)
 static void
 usage (void)
 {
-  fprintf (stderr, DBUS_DAEMON_NAME " [--version] [--session] [--system] [--config-file=FILE] [--print-address[=DESCRIPTOR]] [--print-pid[=DESCRIPTOR]] [--fork] [--nofork] [--introspect] [--address=ADDRESS]\n");
+  fprintf (stderr, DBUS_DAEMON_NAME " [--version] [--session] [--system] [--config-file=FILE] [--print-address[=DESCRIPTOR]] [--print-pid[=DESCRIPTOR]] [--fork] [--nofork] [--introspect] [--address=ADDRESS] [--systemd-activation]\n");
   exit (1);
 }
 
@@ -274,6 +274,7 @@ main (int argc, char **argv)
   dbus_bool_t print_pid;
   dbus_bool_t is_session_bus;
   int force_fork;
+  dbus_bool_t systemd_activation;
 
   if (!_dbus_string_init (&config_file))
     return 1;
@@ -291,6 +292,7 @@ main (int argc, char **argv)
   print_pid = FALSE;
   is_session_bus = FALSE;
   force_fork = FORK_FOLLOW_CONFIG_FILE;
+  systemd_activation = FALSE;
 
   prev_arg = NULL;
   i = 1;
@@ -310,6 +312,8 @@ main (int argc, char **argv)
         force_fork = FORK_NEVER;
       else if (strcmp (arg, "--fork") == 0)
         force_fork = FORK_ALWAYS;
+      else if (strcmp (arg, "--systemd-activation") == 0)
+        systemd_activation = TRUE;
       else if (strcmp (arg, "--system") == 0)
         {
           check_two_config_files (&config_file, "system");
@@ -488,6 +492,7 @@ main (int argc, char **argv)
   context = bus_context_new (&config_file, force_fork,
                              &print_addr_pipe, &print_pid_pipe,
                              _dbus_string_get_length(&address) > 0 ? &address : NULL,
+                             systemd_activation,
                              &error);
   _dbus_string_free (&config_file);
   if (context == NULL)
diff --git a/bus/test.c b/bus/test.c
index 7af143b..6efad6e 100644
--- a/bus/test.c
+++ b/bus/test.c
@@ -323,7 +323,7 @@ bus_context_new_test (const DBusString *test_data_dir,
     }
 
   dbus_error_init (&error);
-  context = bus_context_new (&config_file, FALSE, NULL, NULL, NULL, &error);
+  context = bus_context_new (&config_file, FALSE, NULL, NULL, NULL, FALSE, &error);
   if (context == NULL)
     {
       _DBUS_ASSERT_ERROR_IS_SET (&error);
-- 
1.7.0.1



Lennart

-- 
Lennart Poettering                        Red Hat, Inc.
lennart [at] poettering [dot] net
http://0pointer.net/lennart/           GnuPG 0x1A015CC4


More information about the dbus mailing list