2003-04-02 Havoc Pennington <hp@redhat.com>
[platform/upstream/dbus.git] / bus / activation.c
index 7f7dd43..0dfce3f 100644 (file)
@@ -2,6 +2,7 @@
 /* activation.c  Activation of services
  *
  * Copyright (C) 2003  CodeFactory AB
+ * Copyright (C) 2003  Red Hat, Inc.
  *
  * Licensed under the Academic Free License version 1.2
  * 
  */
 #include "activation.h"
 #include "desktop-file.h"
+#include "services.h"
 #include "utils.h"
 #include <dbus/dbus-internals.h>
 #include <dbus/dbus-hash.h>
+#include <dbus/dbus-list.h>
 #include <sys/types.h>
 #include <dirent.h>
 #include <errno.h>
 #define DBUS_SERVICE_NAME "Name"
 #define DBUS_SERVICE_EXEC "Exec"
 
-static DBusHashTable *activation_entries = NULL;
+struct BusActivation
+{
+  int refcount;
+  DBusHashTable *entries;
+  DBusHashTable *pending_activations;
+  char *server_address;
+  BusContext *context;
+};
 
 typedef struct
 {
@@ -41,6 +51,57 @@ typedef struct
   char *exec;
 } BusActivationEntry;
 
+typedef struct BusPendingActivationEntry BusPendingActivationEntry;
+
+struct BusPendingActivationEntry
+{
+  DBusMessage *activation_message;
+  DBusConnection *connection;
+};
+
+typedef struct
+{
+  char *service_name;
+  DBusList *entries;
+} BusPendingActivation;
+
+static void
+bus_pending_activation_entry_free (BusPendingActivationEntry *entry)
+{
+  if (entry->activation_message)
+    dbus_message_unref (entry->activation_message);
+  
+  if (entry->connection)
+    dbus_connection_unref (entry->connection);
+
+  dbus_free (entry);
+}
+
+static void
+bus_pending_activation_free (BusPendingActivation *activation)
+{
+  DBusList *link;
+  
+  if (!activation)
+    return;
+
+  dbus_free (activation->service_name);
+
+  link = _dbus_list_get_first_link (&activation->entries);
+
+  while (link != NULL)
+    {
+      BusPendingActivationEntry *entry = link->data;
+
+      bus_pending_activation_entry_free (entry);
+
+      link = _dbus_list_get_next_link (&activation->entries, link);
+    }
+  _dbus_list_clear (&activation->entries);
+  
+  dbus_free (activation);
+}
+
 static void
 bus_activation_entry_free (BusActivationEntry *entry)
 {
@@ -52,18 +113,27 @@ bus_activation_entry_free (BusActivationEntry *entry)
 }
 
 static dbus_bool_t
-add_desktop_file_entry (BusDesktopFile *desktop_file)
+add_desktop_file_entry (BusActivation  *activation,
+                        BusDesktopFile *desktop_file,
+                        DBusError      *error)
 {
   char *name, *exec;
   BusActivationEntry *entry;
+
+  _DBUS_ASSERT_ERROR_IS_CLEAR (error);
+  
+  name = NULL;
+  exec = NULL;
+  entry = NULL;
   
   if (!bus_desktop_file_get_string (desktop_file,
                                    DBUS_SERVICE_SECTION,
                                    DBUS_SERVICE_NAME,
                                    &name))
     {
-      _dbus_verbose ("No \""DBUS_SERVICE_NAME"\" key in .service file\n");
-      return FALSE;
+      dbus_set_error (error, DBUS_ERROR_FAILED,
+                      "No \""DBUS_SERVICE_NAME"\" key in .service file\n");
+      goto failed;
     }
 
   if (!bus_desktop_file_get_string (desktop_file,
@@ -71,146 +141,476 @@ add_desktop_file_entry (BusDesktopFile *desktop_file)
                                    DBUS_SERVICE_EXEC,
                                    &exec))
     {
-      _dbus_verbose ("No \""DBUS_SERVICE_EXEC"\" key in .service file\n");
-      
-      dbus_free (name);
-      return FALSE;
+      dbus_set_error (error, DBUS_ERROR_FAILED,
+                      "No \""DBUS_SERVICE_EXEC"\" key in .service file\n");
+      goto failed;
     }
 
-  if (_dbus_hash_table_lookup_string (activation_entries, name))
+  /* FIXME we need a better-defined algorithm for which service file to
+   * pick than "whichever one is first in the directory listing"
+   */
+  if (_dbus_hash_table_lookup_string (activation->entries, name))
     {
-      _dbus_verbose ("Service %s already exists in activation entry list\n", name);
-      dbus_free (name);
-      dbus_free (exec);
-
-      return FALSE;
+      dbus_set_error (error, DBUS_ERROR_FAILED,
+                      "Service %s already exists in activation entry list\n", name);
+      goto failed;
+    }
+  
+  entry = dbus_new0 (BusActivationEntry, 1);
+  if (entry == NULL)
+    {
+      BUS_SET_OOM (error);
+      goto failed;
     }
   
-  BUS_HANDLE_OOM (entry = dbus_malloc0 (sizeof (BusActivationEntry)));
   entry->name = name;
   entry->exec = exec;
 
-  BUS_HANDLE_OOM (_dbus_hash_table_insert_string (activation_entries, entry->name, entry));
+  if (!_dbus_hash_table_insert_string (activation->entries, entry->name, entry))
+    {
+      BUS_SET_OOM (error);
+      goto failed;
+    }
 
   _dbus_verbose ("Added \"%s\" to list of services\n", entry->name);
   
   return TRUE;
+
+ failed:
+  dbus_free (name);
+  dbus_free (exec);
+  dbus_free (entry);
+  
+  return FALSE;
 }
 
-static void
-load_directory (const char *directory)
+/* warning: this doesn't fully "undo" itself on failure, i.e. doesn't strip
+ * hash entries it already added.
+ */
+static dbus_bool_t
+load_directory (BusActivation *activation,
+                const char    *directory,
+                DBusError     *error)
 {
   DBusDirIter *iter;
   DBusString dir, filename;
-  DBusResultCode result;
+  DBusString full_path;
+  BusDesktopFile *desktop_file;
+  DBusError tmp_error;
 
+  _DBUS_ASSERT_ERROR_IS_CLEAR (error);
+  
   _dbus_string_init_const (&dir, directory);
+
+  iter = NULL;
+  desktop_file = NULL;
   
-  iter = _dbus_directory_open (&dir, &result);
-  if (iter == NULL)
+  if (!_dbus_string_init (&filename))
     {
-      _dbus_verbose ("Failed to open directory %s: &s\n", directory,
-                    result);
-    return;
+      BUS_SET_OOM (error);
+      return FALSE;
     }
 
-  BUS_HANDLE_OOM (_dbus_string_init (&filename, _DBUS_INT_MAX));
+  if (!_dbus_string_init (&full_path))
+    {
+      BUS_SET_OOM (error);
+      _dbus_string_free (&filename);
+      return FALSE;
+    }
+
+  /* from this point it's safe to "goto failed" */
+  
+  iter = _dbus_directory_open (&dir, error);
+  if (iter == NULL)
+    {
+      _dbus_verbose ("Failed to open directory %s: %s\n",
+                     directory, error ? error->message : "unknown");
+      goto failed;
+    }
   
   /* Now read the files */
-  while (_dbus_directory_get_next_file (iter, &filename, &result))
+  dbus_error_init (&tmp_error);
+  while (_dbus_directory_get_next_file (iter, &filename, &tmp_error))
     {
-      DBusString full_path;
-      BusDesktopFile *desktop_file;
-      DBusError error;
+      _dbus_assert (!dbus_error_is_set (&tmp_error));
+      
+      _dbus_string_set_length (&full_path, 0);
+      
+      if (!_dbus_string_append (&full_path, directory) ||
+          !_dbus_concat_dir_and_file (&full_path, &filename))
+        {
+          BUS_SET_OOM (error);
+          goto failed;
+        }
       
       if (!_dbus_string_ends_with_c_str (&filename, ".service"))
        {
-          const char *filename_c;
-          _dbus_string_get_const_data (&filename, &filename_c);
           _dbus_verbose ("Skipping non-.service file %s\n",
-                         filename_c);
-         continue;
+                         _dbus_string_get_const_data (&filename));
+          continue;
        }
       
-      BUS_HANDLE_OOM (_dbus_string_init (&full_path, _DBUS_INT_MAX));
-      BUS_HANDLE_OOM (_dbus_string_append (&full_path, directory));
+      desktop_file = bus_desktop_file_load (&full_path, &tmp_error);
 
-      BUS_HANDLE_OOM (_dbus_concat_dir_and_file (&full_path, &filename));
-
-      desktop_file = bus_desktop_file_load (&full_path, &error);
-
-      if (!desktop_file)
+      if (desktop_file == NULL)
        {
-         const char *full_path_c;
+         _dbus_verbose ("Could not load %s: %s\n",
+                         _dbus_string_get_const_data (&full_path),
+                        tmp_error.message);
 
-          _dbus_string_get_const_data (&full_path, &full_path_c);
-         
-         _dbus_verbose ("Could not load %s: %s\n", full_path_c,
-                        error.message);
-         dbus_error_free (&error);
-         _dbus_string_free (&full_path);
+          if (dbus_error_has_name (&tmp_error, DBUS_ERROR_NO_MEMORY))
+            {
+              dbus_move_error (&tmp_error, error);
+              goto failed;
+            }
+          
+         dbus_error_free (&tmp_error);
          continue;
        }
 
-      if (!add_desktop_file_entry (desktop_file))
+      if (!add_desktop_file_entry (activation, desktop_file, &tmp_error))
        {
-         const char *full_path_c;
-
-          _dbus_string_get_const_data (&full_path, &full_path_c);
+          bus_desktop_file_free (desktop_file);
+          desktop_file = NULL;
          
-         _dbus_verbose ("Could not add %s to activation entry list.\n", full_path_c);
+         _dbus_verbose ("Could not add %s to activation entry list: %s\n",
+                         _dbus_string_get_const_data (&full_path), tmp_error.message);
+
+          if (dbus_error_has_name (&tmp_error, DBUS_ERROR_NO_MEMORY))
+            {
+              dbus_move_error (&tmp_error, error);
+              goto failed;
+            }
+
+          dbus_error_free (&tmp_error);
+         continue;
        }
+      else
+        {
+          bus_desktop_file_free (desktop_file);
+          desktop_file = NULL;
+          continue;
+        }
+    }
+
+  if (dbus_error_is_set (&tmp_error))
+    {
+      dbus_move_error (&tmp_error, error);
+      goto failed;
+    }
+  
+  return TRUE;
+  
+ failed:
+  _DBUS_ASSERT_ERROR_IS_SET (error);
+  
+  if (iter != NULL)
+    _dbus_directory_close (iter);
+  if (desktop_file)
+    bus_desktop_file_free (desktop_file);
+  _dbus_string_free (&filename);
+  _dbus_string_free (&full_path);
+  
+  return FALSE;
+}
+
+BusActivation*
+bus_activation_new (BusContext        *context,
+                   const DBusString  *address,
+                    DBusList         **directories,
+                    DBusError         *error)
+{
+  BusActivation *activation;
+  DBusList *link;
+  
+  _DBUS_ASSERT_ERROR_IS_CLEAR (error);
+  
+  activation = dbus_new0 (BusActivation, 1);
+  if (activation == NULL)
+    {
+      BUS_SET_OOM (error);
+      return NULL;
+    }
+  
+  activation->refcount = 1;
+  activation->context = context;
+  
+  if (!_dbus_string_copy_data (address, &activation->server_address))
+    {
+      BUS_SET_OOM (error);
+      goto failed;
+    }
+  
+  activation->entries = _dbus_hash_table_new (DBUS_HASH_STRING, NULL,
+                                             (DBusFreeFunction)bus_activation_entry_free);
+  if (activation->entries == NULL)
+    {      
+      BUS_SET_OOM (error);
+      goto failed;
+    }
 
-      bus_desktop_file_free (desktop_file);
-      _dbus_string_free (&full_path);
+  activation->pending_activations = _dbus_hash_table_new (DBUS_HASH_STRING, NULL,
+                                                         (DBusFreeFunction)bus_pending_activation_free);
+
+  if (activation->pending_activations == NULL)
+    {
+      BUS_SET_OOM (error);
+      goto failed;
     }
+  
+  /* Load service files */
+  link = _dbus_list_get_first_link (directories);
+  while (link != NULL)
+    {
+      if (!load_directory (activation, link->data, error))
+        goto failed;
+      link = _dbus_list_get_next_link (directories, link);
+    }
+
+  return activation;
+  
+ failed:
+  bus_activation_unref (activation);  
+  return NULL;
+}
+
+void
+bus_activation_ref (BusActivation *activation)
+{
+  _dbus_assert (activation->refcount > 0);
+  
+  activation->refcount += 1;
+}
 
-#if 0
-  while ((directory_entry = readdir (directory_handle)))
+void
+bus_activation_unref (BusActivation *activation)
+{
+  _dbus_assert (activation->refcount > 0);
+
+  activation->refcount -= 1;
+
+  if (activation->refcount == 0)
     {
-      DBusString path, filename;
-      BusDesktopFile *desktop_file;
-      DBusError error;
-      const char *filename_c;
+      dbus_free (activation->server_address);
+      if (activation->entries)
+        _dbus_hash_table_unref (activation->entries);
+      if (activation->pending_activations)
+       _dbus_hash_table_unref (activation->pending_activations);
+      dbus_free (activation);
+    }
+}
+
+static void
+child_setup (void *data)
+{
+  BusActivation *activation = data;
+  
+  /* If no memory, we simply have the child exit, so it won't try
+   * to connect to the wrong thing.
+   */
+  if (!_dbus_setenv ("DBUS_ADDRESS", activation->server_address))
+    _dbus_exit (1);
+}
+
+dbus_bool_t
+bus_activation_service_created (BusActivation  *activation,
+                               const char     *service_name,
+                                BusTransaction *transaction,
+                               DBusError      *error)
+{
+  BusPendingActivation *pending_activation;
+  DBusMessage *message;
+  DBusList *link;
+
+  _DBUS_ASSERT_ERROR_IS_CLEAR (error);
+  
+  /* Check if it's a pending activation */
+  pending_activation = _dbus_hash_table_lookup_string (activation->pending_activations, service_name);
+
+  if (!pending_activation)
+    return TRUE;
 
+  link = _dbus_list_get_first_link (&pending_activation->entries);
+  while (link != NULL)
+    {
+      BusPendingActivationEntry *entry = link->data;
+      DBusList *next = _dbus_list_get_next_link (&pending_activation->entries, link);
+      
+      if (dbus_connection_get_is_connected (entry->connection))
+       {
+         message = dbus_message_new_reply (entry->activation_message);
+         if (!message)
+           {
+             BUS_SET_OOM (error);
+             goto error;
+           }
+
+         if (!dbus_message_set_sender (message, DBUS_SERVICE_DBUS) ||
+              !dbus_message_append_args (message,
+                                        DBUS_TYPE_UINT32, DBUS_ACTIVATION_REPLY_ACTIVATED,
+                                        0))
+           {
+             dbus_message_unref (message);
+             BUS_SET_OOM (error);
+             goto error;
+           }
+          
+         if (!bus_transaction_send_message (transaction, entry->connection, message))
+           {
+             dbus_message_unref (message);
+             BUS_SET_OOM (error);
+             goto error;
+           }
+       }
+
+      bus_pending_activation_entry_free (entry);
       
-      _dbus_string_init_const (&filename, directory_entry->d_name);
+      _dbus_list_remove_link (&pending_activation->entries, link);      
+      link = next;
+    }
+  
+  _dbus_hash_table_remove_string (activation->pending_activations, service_name);
+
+  return TRUE;
 
+ error:
+  _dbus_hash_table_remove_string (activation->pending_activations, service_name);
+  return FALSE;
+}
+
+dbus_bool_t
+bus_activation_activate_service (BusActivation  *activation,
+                                DBusConnection *connection,
+                                 BusTransaction *transaction,
+                                DBusMessage    *activation_message,
+                                 const char     *service_name,
+                                DBusError      *error)
+{
+  BusActivationEntry *entry;
+  BusPendingActivation *pending_activation;
+  BusPendingActivationEntry *pending_activation_entry;
+  DBusMessage *message;
+  DBusString service_str;
+  char *argv[2];
+  dbus_bool_t retval;
+
+  _DBUS_ASSERT_ERROR_IS_CLEAR (error);
+  
+  entry = _dbus_hash_table_lookup_string (activation->entries, service_name);
+
+  if (!entry)
+    {
+      dbus_set_error (error, DBUS_ERROR_ACTIVATE_SERVICE_NOT_FOUND,
+                     "The service %s was not found in the activation entry list",
+                     service_name);
+      return FALSE;
+    }
 
-      _dbus_string_get_const_data (&path, &filename_c);      
+  /* Check if the service is active */
+  _dbus_string_init_const (&service_str, service_name);
+  if (bus_registry_lookup (bus_context_get_registry (activation->context), &service_str) != NULL)
+    {
+      message = dbus_message_new_reply (activation_message);
 
-      if (!desktop_file)
+      if (!message)
        {
-         _dbus_verbose ("Could not load %s: %s\n", filename_c,
-                        error.message);
-         dbus_error_free (&error);
-         _dbus_string_free (&path);
-         continue;
+         BUS_SET_OOM (error);
+         return FALSE;
        }
 
-      if (!add_desktop_file_entry (desktop_file))
+      if (!dbus_message_set_sender (message, DBUS_SERVICE_DBUS) ||
+          !dbus_message_append_args (message,
+                                    DBUS_TYPE_UINT32, DBUS_ACTIVATION_REPLY_ALREADY_ACTIVE, 
+                                    0))
        {
-         _dbus_verbose ("Could not add %s to activation entry list.\n", filename_c);
+         BUS_SET_OOM (error);
+         dbus_message_unref (message);
+         return FALSE;
        }
+
+      retval = bus_transaction_send_message (transaction, connection, message);
+      dbus_message_unref (message);
+      if (!retval)
+       BUS_SET_OOM (error);
+
+      return retval;
     }
-#endif
-}
 
+  pending_activation_entry = dbus_new0 (BusPendingActivationEntry, 1);
+  if (!pending_activation_entry)
+    {
+      BUS_SET_OOM (error);
+      return FALSE;
+    }
 
-void
-bus_activation_init (const char **directories)
-{
-  int i;
+  pending_activation_entry->activation_message = activation_message;
+  dbus_message_ref (activation_message);
+  pending_activation_entry->connection = connection;
+  dbus_connection_ref (connection);
+  
+  /* Check if the service is being activated */
+  pending_activation = _dbus_hash_table_lookup_string (activation->pending_activations, service_name);
+  if (pending_activation)
+    {
+      if (!_dbus_list_append (&pending_activation->entries, pending_activation_entry))
+       {
+         BUS_SET_OOM (error);
+         bus_pending_activation_entry_free (pending_activation_entry);
 
-  BUS_HANDLE_OOM (activation_entries = _dbus_hash_table_new (DBUS_HASH_STRING, NULL,
-                                                            (DBusFreeFunction)bus_activation_entry_free));
+         return FALSE;
+       }
+    }
+  else
+    {
+      pending_activation = dbus_new0 (BusPendingActivation, 1);
+      if (!pending_activation)
+       {
+         BUS_SET_OOM (error);
+         bus_pending_activation_entry_free (pending_activation_entry);   
+         return FALSE;
+       }
+      pending_activation->service_name = _dbus_strdup (service_name);
+      if (!pending_activation->service_name)
+       {
+         BUS_SET_OOM (error);
+         bus_pending_activation_free (pending_activation);
+         bus_pending_activation_entry_free (pending_activation_entry);   
+         return FALSE;
+       }
 
-  i = 0;
+      if (!_dbus_list_append (&pending_activation->entries, pending_activation_entry))
+       {
+         BUS_SET_OOM (error);
+         bus_pending_activation_free (pending_activation);
+         bus_pending_activation_entry_free (pending_activation_entry);   
+         return FALSE;
+       }
+      
+      if (!_dbus_hash_table_insert_string (activation->pending_activations,
+                                          pending_activation->service_name, pending_activation))
+       {
+         BUS_SET_OOM (error);
+         bus_pending_activation_free (pending_activation);
+         return FALSE;
+       }
+    }
+  
+  /* FIXME we need to support a full command line, not just a single
+   * argv[0]
+   */
+  
+  /* Now try to spawn the process */
+  argv[0] = entry->exec;
+  argv[1] = NULL;
 
-  /* Load service files */
-  while (directories[i] != NULL)
+  if (!_dbus_spawn_async (argv,
+                         child_setup, activation, 
+                         error))
     {
-      load_directory (directories[i]);
-      i++;
+      _dbus_hash_table_remove_string (activation->pending_activations,
+                                     pending_activation->service_name);
+      return FALSE;
     }
+  
+  return TRUE;
 }