Handle nested arrays
authorDavid Zeuthen <zeuthen@gmail.com>
Thu, 20 Sep 2012 17:13:23 +0000 (13:13 -0400)
committerDavid Zeuthen <zeuthen@gmail.com>
Thu, 20 Sep 2012 17:13:23 +0000 (13:13 -0400)
We actually need mdadm(8) changes for this to work properly otherwise
the MD_* properties end up overwriting each other.

We also have to be careful since a single GUdevDevice instance can be
in two distinct UDisksLinuxMDRaidObject.

http://people.freedesktop.org/~david/gnome-disks-mdraid-20120920-1.png

Signed-off-by: David Zeuthen <zeuthen@gmail.com>
src/udiskslinuxblock.c
src/udiskslinuxmdraid.c
src/udiskslinuxmdraidobject.c
src/udiskslinuxmdraidobject.h
src/udiskslinuxprovider.c

index 5038e78..609098a 100644 (file)
@@ -278,29 +278,35 @@ update_mdraid (UDisksLinuxBlock         *block,
                GDBusObjectManagerServer *object_manager)
 {
   UDisksBlock *iface = UDISKS_BLOCK (block);
-  const gchar *md_uuid;
+  const gchar *uuid;
   const gchar *objpath_mdraid = "/";
   const gchar *objpath_mdraid_member = "/";
   UDisksLinuxMDRaidObject *object = NULL;
 
-  md_uuid = g_udev_device_get_property (device, "MD_UUID");
-  if (md_uuid == NULL || strlen (md_uuid) == 0)
-    goto out;
-
-  object = find_mdraid (object_manager, md_uuid);
-  if (object == NULL)
-    goto out;
+  uuid = g_udev_device_get_property (device, "MD_UUID");
+  if (uuid != NULL && strlen (uuid) > 0)
+    {
+      object = find_mdraid (object_manager, uuid);
+      if (object != NULL)
+        {
+          objpath_mdraid = g_dbus_object_get_object_path (G_DBUS_OBJECT (object));
+          g_clear_object (&object);
+        }
+    }
 
-  /* TODO: find a better way to distinguish member vs array ? */
-  if (g_str_has_prefix (g_udev_device_get_device_file (device), "/dev/md"))
-    objpath_mdraid = g_dbus_object_get_object_path (G_DBUS_OBJECT (object));
-  else
-    objpath_mdraid_member = g_dbus_object_get_object_path (G_DBUS_OBJECT (object));
+  uuid = g_udev_device_get_property (device, "MD_MEMBER_UUID");
+  if (uuid != NULL && strlen (uuid) > 0)
+    {
+      object = find_mdraid (object_manager, uuid);
+      if (object != NULL)
+        {
+          objpath_mdraid_member = g_dbus_object_get_object_path (G_DBUS_OBJECT (object));
+          g_clear_object (&object);
+        }
+    }
 
- out:
   udisks_block_set_mdraid (iface, objpath_mdraid);
   udisks_block_set_mdraid_member (iface, objpath_mdraid_member);
-  g_clear_object (&object);
 }
 
 /* ---------------------------------------------------------------------------------------------------- */
index e71ca3d..fe1e3d1 100644 (file)
@@ -189,6 +189,7 @@ on_polling_timout (gpointer user_data)
 {
   UDisksLinuxMDRaid *mdraid = UDISKS_LINUX_MDRAID (user_data);
   UDisksLinuxMDRaidObject *object = NULL;
+  GUdevDevice *raid_device;
 
   udisks_debug ("polling timeout");
 
@@ -197,7 +198,12 @@ on_polling_timout (gpointer user_data)
     goto out;
 
   /* synthesize uevent */
-  udisks_linux_mdraid_object_uevent (object, "change", NULL);
+  raid_device = udisks_linux_mdraid_object_get_device (object);
+  if (raid_device != NULL)
+    {
+      udisks_linux_mdraid_object_uevent (object, "change", raid_device, FALSE);
+      g_object_unref (raid_device);
+    }
 
  out:
   g_clear_object (&object);
@@ -271,6 +277,8 @@ udisks_linux_mdraid_update (UDisksLinuxMDRaid       *mdraid,
   GList *member_devices = NULL;
   GUdevDevice *device = NULL;
   const gchar *level = NULL;
+  const gchar *uuid = NULL;
+  const gchar *name = NULL;
   gchar *sync_action = NULL;
   gchar *sync_completed = NULL;
   gchar *bitmap_location = NULL;
@@ -300,12 +308,21 @@ udisks_linux_mdraid_update (UDisksLinuxMDRaid       *mdraid,
    * former, if available
    */
   if (member_devices != NULL)
-    device = G_UDEV_DEVICE (member_devices->data);
+    {
+      device = G_UDEV_DEVICE (member_devices->data);
+      num_devices = g_udev_device_get_property_as_int (device, "MD_MEMBER_DEVICES");
+      level = g_udev_device_get_property (device, "MD_MEMBER_LEVEL");
+      uuid = g_udev_device_get_property (device, "MD_MEMBER_UUID");
+      name = g_udev_device_get_property (device, "MD_MEMBER_NAME");
+    }
   else
-    device = G_UDEV_DEVICE (raid_device);
-
-  num_devices = g_udev_device_get_property_as_int (device, "MD_DEVICES");
-  level = g_udev_device_get_property (device, "MD_LEVEL");
+    {
+      device = G_UDEV_DEVICE (raid_device);
+      num_devices = g_udev_device_get_property_as_int (device, "MD_DEVICES");
+      level = g_udev_device_get_property (device, "MD_LEVEL");
+      uuid = g_udev_device_get_property (device, "MD_UUID");
+      name = g_udev_device_get_property (device, "MD_NAME");
+    }
 
   /* figure out size */
   if (raid_device != NULL)
@@ -317,8 +334,8 @@ udisks_linux_mdraid_update (UDisksLinuxMDRaid       *mdraid,
       /* TODO: need MD_ARRAY_SIZE, see https://bugs.freedesktop.org/show_bug.cgi?id=53239#c5 */
     }
 
-  udisks_mdraid_set_uuid (iface, g_udev_device_get_property (device, "MD_UUID"));
-  udisks_mdraid_set_name (iface, g_udev_device_get_property (device, "MD_NAME"));
+  udisks_mdraid_set_uuid (iface, uuid);
+  udisks_mdraid_set_name (iface, name);
   udisks_mdraid_set_level (iface, level);
   udisks_mdraid_set_num_devices (iface, num_devices);
   udisks_mdraid_set_size (iface, size);
@@ -331,6 +348,7 @@ udisks_linux_mdraid_update (UDisksLinuxMDRaid       *mdraid,
    */
   if (num_members >= num_devices)
     can_start = TRUE;
+
   if (g_strcmp0 (level, "raid1") == 0 ||
       g_strcmp0 (level, "raid4") == 0 ||
       g_strcmp0 (level, "raid5") == 0 ||
@@ -349,18 +367,29 @@ udisks_linux_mdraid_update (UDisksLinuxMDRaid       *mdraid,
 
   if (raid_device != NULL)
     {
-      /* Can't use GUdevDevice methods as they cache the result and these variables vary */
-      degraded = read_sysfs_attr_as_int (raid_device, "md/degraded");
-      sync_action = read_sysfs_attr (raid_device, "md/sync_action");
-      if (sync_action != NULL)
-        g_strstrip (sync_action);
-      sync_completed = read_sysfs_attr (raid_device, "md/sync_completed");
-      if (sync_completed != NULL)
-        g_strstrip (sync_completed);
-      bitmap_location = read_sysfs_attr (raid_device, "md/bitmap/location");
-      if (bitmap_location != NULL)
-        g_strstrip (bitmap_location);
-      chunk_size = read_sysfs_attr_as_uint64 (raid_device, "md/chunk_size");
+      if (g_strcmp0 (level, "raid1") == 0 ||
+          g_strcmp0 (level, "raid4") == 0 ||
+          g_strcmp0 (level, "raid5") == 0 ||
+          g_strcmp0 (level, "raid6") == 0 ||
+          g_strcmp0 (level, "raid10") == 0)
+        {
+          /* Can't use GUdevDevice methods as they cache the result and these variables vary */
+          degraded = read_sysfs_attr_as_int (raid_device, "md/degraded");
+          sync_action = read_sysfs_attr (raid_device, "md/sync_action");
+          if (sync_action != NULL)
+            g_strstrip (sync_action);
+          sync_completed = read_sysfs_attr (raid_device, "md/sync_completed");
+          if (sync_completed != NULL)
+            g_strstrip (sync_completed);
+          bitmap_location = read_sysfs_attr (raid_device, "md/bitmap/location");
+          if (bitmap_location != NULL)
+            g_strstrip (bitmap_location);
+          chunk_size = read_sysfs_attr_as_uint64 (raid_device, "md/chunk_size");
+        }
+      else
+        {
+          /* no redundancy, e.g. raid0 or linear */
+        }
     }
   udisks_mdraid_set_degraded (iface, degraded);
   udisks_mdraid_set_sync_action (iface, sync_action);
@@ -414,8 +443,8 @@ udisks_linux_mdraid_update (UDisksLinuxMDRaid       *mdraid,
       if (md_dir != NULL)
         {
           gchar buf[256];
-          const gchar *name;
-          while ((name = g_dir_read_name (md_dir)) != NULL)
+          const gchar *file_name;
+          while ((file_name = g_dir_read_name (md_dir)) != NULL)
             {
               gchar *block_sysfs_path = NULL;
               UDisksObject *member_object = NULL;
@@ -425,10 +454,10 @@ udisks_linux_mdraid_update (UDisksLinuxMDRaid       *mdraid,
               gint member_slot_as_int = -1;
               guint64 member_errors = 0;
 
-              if (!g_str_has_prefix (name, "dev-"))
+              if (!g_str_has_prefix (file_name, "dev-"))
                 goto member_done;
 
-              snprintf (buf, sizeof (buf), "%s/block", name);
+              snprintf (buf, sizeof (buf), "%s/block", file_name);
               block_sysfs_path = udisks_daemon_util_resolve_link (md_dir_name, buf);
               if (block_sysfs_path == NULL)
                 {
@@ -444,7 +473,7 @@ udisks_linux_mdraid_update (UDisksLinuxMDRaid       *mdraid,
                   goto member_done;
                 }
 
-              snprintf (buf, sizeof (buf), "md/%s/state", name);
+              snprintf (buf, sizeof (buf), "md/%s/state", file_name);
               member_state = read_sysfs_attr (raid_device, buf);
               if (member_state != NULL)
                 {
@@ -452,7 +481,7 @@ udisks_linux_mdraid_update (UDisksLinuxMDRaid       *mdraid,
                   member_state_elements = g_strsplit (member_state, ",", 0);
                 }
 
-              snprintf (buf, sizeof (buf), "md/%s/slot", name);
+              snprintf (buf, sizeof (buf), "md/%s/slot", file_name);
               member_slot = read_sysfs_attr (raid_device, buf);
               if (member_slot != NULL)
                 {
@@ -461,7 +490,7 @@ udisks_linux_mdraid_update (UDisksLinuxMDRaid       *mdraid,
                     member_slot_as_int = atoi (member_slot);
                 }
 
-              snprintf (buf, sizeof (buf), "md/%s/errors", name);
+              snprintf (buf, sizeof (buf), "md/%s/errors", file_name);
               member_errors = read_sysfs_attr_as_uint64 (raid_device, buf);
 
               g_ptr_array_add (p,
@@ -496,8 +525,6 @@ udisks_linux_mdraid_update (UDisksLinuxMDRaid       *mdraid,
     }
   udisks_mdraid_set_active_devices (iface, g_variant_builder_end (&builder));
 
-  /* TODO: set other stuff */
-
  out:
   g_free (sync_completed);
   g_free (sync_action);
index 9895c44..a46a4d8 100644 (file)
@@ -55,8 +55,14 @@ struct _UDisksLinuxMDRaidObject
 
   UDisksDaemon *daemon;
 
+  /* The UUID for the object */
+  gchar *uuid;
+
+  /* The GUdevDevice for the RAID device (e.g. /dev/md0), if any */
+  GUdevDevice *raid_device;
+
   /* list of GUdevDevice objects for detected member devices */
-  GList *devices;
+  GList *member_devices;
 
   /* interfaces */
   UDisksMDRaid *iface_mdraid;
@@ -74,8 +80,8 @@ struct _UDisksLinuxMDRaidObjectClass
 enum
 {
   PROP_0,
+  PROP_UUID,
   PROP_DAEMON,
-  PROP_DEVICE
 };
 
 static void
@@ -104,12 +110,16 @@ udisks_linux_mdraid_object_finalize (GObject *_object)
 
   remove_watches (object);
 
-  g_list_foreach (object->devices, (GFunc) g_object_unref, NULL);
-  g_list_free (object->devices);
-
   if (object->iface_mdraid != NULL)
     g_object_unref (object->iface_mdraid);
 
+  g_clear_object (&object->raid_device);
+
+  g_list_foreach (object->member_devices, (GFunc) g_object_unref, NULL);
+  g_list_free (object->member_devices);
+
+  g_free (object->uuid);
+
   if (G_OBJECT_CLASS (udisks_linux_mdraid_object_parent_class)->finalize != NULL)
     G_OBJECT_CLASS (udisks_linux_mdraid_object_parent_class)->finalize (_object);
 }
@@ -150,14 +160,8 @@ udisks_linux_mdraid_object_set_property (GObject      *__object,
       object->daemon = g_value_get_object (value);
       break;
 
-    case PROP_DEVICE:
-      /* initial coldplug */
-      {
-        GUdevDevice *device;
-        g_assert (object->devices == NULL);
-        device = G_UDEV_DEVICE (g_value_get_object (value));
-        udisks_linux_mdraid_object_uevent (object, "add", device);
-      }
+    case PROP_UUID:
+      object->uuid = g_value_dup_string (value);
       break;
 
     default:
@@ -200,7 +204,7 @@ udisks_linux_mdraid_object_constructed (GObject *_object)
   gchar *s;
 
   /* compute the object path */
-  uuid = udisks_mdraid_dup_uuid (object->iface_mdraid);
+  uuid = g_strdup (object->uuid);
   strip_and_replace_with_uscore (uuid);
   s = g_strdup_printf ("/org/freedesktop/UDisks2/mdraid/%s", uuid);
   g_free (uuid);
@@ -239,41 +243,39 @@ udisks_linux_mdraid_object_class_init (UDisksLinuxMDRaidObjectClass *klass)
                                                         G_PARAM_STATIC_STRINGS));
 
   /**
-   * UDisksLinuxMDRaidObject:device:
+   * UDisksLinuxMDRaidObject:uuid:
    *
-   * The #GUdevDevice for the object. Connect to the #GObject::notify
-   * signal to get notified whenever this is updated.
+   * The UUID for the array.
    */
   g_object_class_install_property (gobject_class,
-                                   PROP_DEVICE,
-                                   g_param_spec_object ("device",
-                                                        "Device",
-                                                        "The device for the object",
-                                                        G_UDEV_TYPE_DEVICE,
+                                   PROP_UUID,
+                                   g_param_spec_string ("uuid",
+                                                        "UUID",
+                                                        "The UUID for the array",
+                                                        NULL,
                                                         G_PARAM_WRITABLE |
                                                         G_PARAM_CONSTRUCT_ONLY |
                                                         G_PARAM_STATIC_STRINGS));
-
 }
 
 /**
  * udisks_linux_mdraid_object_new:
  * @daemon: A #UDisksDaemon.
- * @device: The #GUdevDevice for the sysfs block device.
+ * @uuid: The UUID for the array.
  *
- * Create a new mdraid object.
+ * Create a new MDRaid object.
  *
  * Returns: A #UDisksLinuxMDRaidObject object. Free with g_object_unref().
  */
 UDisksLinuxMDRaidObject *
 udisks_linux_mdraid_object_new (UDisksDaemon  *daemon,
-                               GUdevDevice   *device)
+                                const gchar   *uuid)
 {
   g_return_val_if_fail (UDISKS_IS_DAEMON (daemon), NULL);
-  g_return_val_if_fail (G_UDEV_IS_DEVICE (device), NULL);
+  g_return_val_if_fail (uuid != NULL, NULL);
   return UDISKS_LINUX_MDRAID_OBJECT (g_object_new (UDISKS_TYPE_LINUX_MDRAID_OBJECT,
                                                    "daemon", daemon,
-                                                   "device", device,
+                                                   "uuid", uuid,
                                                    NULL));
 }
 
@@ -305,19 +307,12 @@ GList *
 udisks_linux_mdraid_object_get_members (UDisksLinuxMDRaidObject *object)
 {
   GList *ret = NULL;
-  GList *l;
 
   g_return_val_if_fail (UDISKS_IS_LINUX_MDRAID_OBJECT (object), NULL);
 
-  for (l = object->devices; l != NULL; l = l->next)
-    {
-      GUdevDevice *device = G_UDEV_DEVICE (l->data);
-      /* TODO: find a better way to distinguish member vs array ? */
-      if (!g_str_has_prefix (g_udev_device_get_device_file (device), "/dev/md"))
-        {
-          ret = g_list_prepend (ret, g_object_ref (device));
-        }
-    }
+  ret = g_list_copy (object->member_devices);
+  g_list_foreach (ret, (GFunc) g_object_ref, NULL);
+
   return ret;
 }
 
@@ -334,21 +329,11 @@ GUdevDevice *
 udisks_linux_mdraid_object_get_device (UDisksLinuxMDRaidObject   *object)
 {
   GUdevDevice *ret = NULL;
-  GList *l;
 
   g_return_val_if_fail (UDISKS_IS_LINUX_MDRAID_OBJECT (object), NULL);
 
-  for (l = object->devices; l != NULL; l = l->next)
-    {
-      GUdevDevice *device = G_UDEV_DEVICE (l->data);
-      /* TODO: find a better way to distinguish member vs array ? */
-      if (g_str_has_prefix (g_udev_device_get_device_file (device), "/dev/md"))
-        {
-          ret = g_object_ref (device);
-          goto out;
-        }
-    }
- out:
+  ret = object->raid_device != NULL ? g_object_ref (object->raid_device) : NULL;
+
   return ret;
 }
 
@@ -441,13 +426,14 @@ mdraid_update (UDisksLinuxMDRaidObject  *object,
 /* ---------------------------------------------------------------------------------------------------- */
 
 static GList *
-find_link_for_sysfs_path (UDisksLinuxMDRaidObject *object,
-                          const gchar            *sysfs_path)
+find_link_for_sysfs_path_for_member (UDisksLinuxMDRaidObject *object,
+                                     const gchar            *sysfs_path)
 {
   GList *l;
   GList *ret;
   ret = NULL;
-  for (l = object->devices; l != NULL; l = l->next)
+
+  for (l = object->member_devices; l != NULL; l = l->next)
     {
       GUdevDevice *device = G_UDEV_DEVICE (l->data);
       if (g_strcmp0 (g_udev_device_get_sysfs_path (device), sysfs_path) == 0)
@@ -504,6 +490,7 @@ attr_changed (GIOChannel   *channel,
               gpointer      user_data)
 {
   UDisksLinuxMDRaidObject *object = UDISKS_LINUX_MDRAID_OBJECT (user_data);
+  gboolean bail = TRUE;
   GError *error = NULL;
   gchar *str = NULL;
   gsize len = 0;
@@ -513,26 +500,31 @@ attr_changed (GIOChannel   *channel,
 
   if (g_io_channel_seek_position (channel, 0, G_SEEK_SET, &error) != G_IO_STATUS_NORMAL)
     {
-      udisks_warning ("Error seeking in channel: %s (%s, %d)",
-                      error->message, g_quark_to_string (error->domain), error->code);
+      udisks_warning ("Error seeking in channel (uuid %s): %s (%s, %d)",
+                      object->uuid, error->message, g_quark_to_string (error->domain), error->code);
       g_clear_error (&error);
+      bail = TRUE;
       goto out;
     }
 
   if (g_io_channel_read_to_end (channel, &str, &len, &error) != G_IO_STATUS_NORMAL)
     {
-      udisks_warning ("Error reading: %s (%s, %d)",
-                      error->message, g_quark_to_string (error->domain), error->code);
+      udisks_warning ("Error reading (uuid %s): %s (%s, %d)",
+                      object->uuid, error->message, g_quark_to_string (error->domain), error->code);
       g_clear_error (&error);
+      bail = TRUE;
       goto out;
     }
 
   g_free (str);
 
   /* synthesize uevent */
-  udisks_linux_mdraid_object_uevent (object, "change", NULL);
+  if (object->raid_device != NULL)
+    udisks_linux_mdraid_object_uevent (object, "change", object->raid_device, FALSE);
 
  out:
+  if (bail)
+    remove_watches (object);
   return TRUE; /* keep event source around */
 }
 
@@ -548,8 +540,8 @@ attr_changed (GIOChannel   *channel,
  */
 
 static void
-md_device_added (UDisksLinuxMDRaidObject *object,
-                 GUdevDevice             *device)
+raid_device_added (UDisksLinuxMDRaidObject *object,
+                   GUdevDevice             *device)
 {
   g_assert (object->sync_action_source == NULL);
   g_assert (object->degraded_source == NULL);
@@ -566,8 +558,8 @@ md_device_added (UDisksLinuxMDRaidObject *object,
 }
 
 static void
-md_device_removed (UDisksLinuxMDRaidObject *object,
-                   GUdevDevice             *device)
+raid_device_removed (UDisksLinuxMDRaidObject *object,
+                     GUdevDevice             *device)
 {
   /* udisks_debug ("stop watching %s", g_udev_device_get_sysfs_path (device)); */
   remove_watches (object);
@@ -580,73 +572,153 @@ md_device_removed (UDisksLinuxMDRaidObject *object,
  * @object: A #UDisksLinuxMDRaidObject.
  * @action: Uevent action or %NULL
  * @device: A #GUdevDevice device object or %NULL if the device hasn't changed.
+ * @is_member: %TRUE if @device is a member, %FALSE if it's the raid device.
  *
  * Updates all information on interfaces on @mdraid.
  */
 void
 udisks_linux_mdraid_object_uevent (UDisksLinuxMDRaidObject *object,
                                    const gchar             *action,
-                                   GUdevDevice             *device)
+                                   GUdevDevice             *device,
+                                   gboolean                 is_member)
 {
-  GList *link;
   gboolean conf_changed = FALSE;
-  gboolean is_md_device = FALSE;
 
   g_return_if_fail (UDISKS_IS_LINUX_MDRAID_OBJECT (object));
-  g_return_if_fail (device == NULL || G_UDEV_IS_DEVICE (device));
-
-  if (device != NULL &&
-      g_str_has_prefix (g_udev_device_get_name (device), "md")) /* TODO: better heuristic */
-    is_md_device = TRUE;
-
-  /* Skip partitions of md devices */
-  if (is_md_device && device != NULL &&
-      g_strcmp0 (g_udev_device_get_devtype (device), "disk") != 0)
-    goto out;
+  g_return_if_fail (G_UDEV_IS_DEVICE (device));
 
+  udisks_debug ("is_member=%d for uuid %s and device %s", is_member, object->uuid, g_udev_device_get_device_file (device));
 
-  link = NULL;
-  if (device != NULL)
-    link = find_link_for_sysfs_path (object, g_udev_device_get_sysfs_path (device));
-  if (g_strcmp0 (action, "remove") == 0)
+  if (is_member)
     {
-      if (link != NULL)
+      GList *link = NULL;
+      link = NULL;
+      if (device != NULL)
+        link = find_link_for_sysfs_path_for_member (object, g_udev_device_get_sysfs_path (device));
+
+      if (g_strcmp0 (action, "remove") == 0)
         {
-          if (is_md_device)
-            md_device_removed (object, device);
-          g_object_unref (G_UDEV_DEVICE (link->data));
-          object->devices = g_list_delete_link (object->devices, link);
+          if (link != NULL)
+            {
+              g_object_unref (G_UDEV_DEVICE (link->data));
+              object->member_devices = g_list_delete_link (object->member_devices, link);
+            }
+          else
+            {
+              udisks_warning ("MDRaid with UUID %s doesn't have member device with sysfs path %s on remove event",
+                              object->uuid,
+                              g_udev_device_get_sysfs_path (device));
+            }
         }
       else
         {
-          udisks_warning ("MDRaid doesn't have device with sysfs path %s on remove event",
-                          g_udev_device_get_sysfs_path (device));
+          if (link != NULL)
+            {
+              if (device != link->data)
+                {
+                  g_object_unref (G_UDEV_DEVICE (link->data));
+                  link->data = g_object_ref (device);
+                }
+            }
+          else
+            {
+              if (device != NULL)
+                {
+                  object->member_devices = g_list_append (object->member_devices, g_object_ref (device));
+                }
+            }
         }
     }
   else
     {
-      if (link != NULL)
+      /* Skip partitions of raid devices */
+      if (g_strcmp0 (g_udev_device_get_devtype (device), "disk") != 0)
+        goto out;
+
+      if (g_strcmp0 (action, "remove") == 0)
         {
-          g_object_unref (G_UDEV_DEVICE (link->data));
-          link->data = g_object_ref (device);
+          if (object->raid_device != NULL)
+            if (g_strcmp0 (g_udev_device_get_sysfs_path (object->raid_device),
+                           g_udev_device_get_sysfs_path (device)) == 0)
+              {
+                g_clear_object (&object->raid_device);
+                raid_device_removed (object, object->raid_device);
+              }
+            else
+              {
+                udisks_warning ("MDRaid with UUID %s doesn't have raid device with sysfs path %s on remove event (it has %s)",
+                                object->uuid,
+                                g_udev_device_get_sysfs_path (device),
+                                g_udev_device_get_sysfs_path (object->raid_device));
+              }
+          else
+            {
+              udisks_warning ("MDRaid with UUID %s doesn't have raid device with sysfs path %s on remove event",
+                              object->uuid,
+                              g_udev_device_get_sysfs_path (device));
+            }
         }
       else
         {
-          if (device != NULL)
+          if (object->raid_device == NULL)
+            {
+              object->raid_device = g_object_ref (device);
+              raid_device_added (object, object->raid_device);
+            }
+          else
             {
-              object->devices = g_list_append (object->devices, g_object_ref (device));
-              if (is_md_device)
-                md_device_added (object, device);
+              if (device != object->raid_device)
+                {
+                  g_clear_object (&object->raid_device);
+                  object->raid_device = g_object_ref (device);
+                }
             }
         }
     }
 
-  conf_changed = FALSE;
-  conf_changed |= update_iface (object, action, mdraid_check, mdraid_connect, mdraid_update,
-                                UDISKS_TYPE_LINUX_MDRAID, &object->iface_mdraid);
+  /* if we don't have any devices, no point in updating (we should get nuked soon anyway) */
+  if (udisks_linux_mdraid_object_have_devices (object))
+    {
+      conf_changed = FALSE;
+      conf_changed |= update_iface (object, action, mdraid_check, mdraid_connect, mdraid_update,
+                                    UDISKS_TYPE_LINUX_MDRAID, &object->iface_mdraid);
+    }
  out:
   ;
 }
 
 /* ---------------------------------------------------------------------------------------------------- */
 
+/**
+ * udisks_linux_mdraid_object_have_devices:
+ * @object: A #UDisksLinuxMDRaidObject.
+ *
+ * Checks if there are any devices associated with @object at
+ * all. This includes both member devices and the raid device.
+ *
+ * Returns: %TRUE if at least one device is associated with @object, %FALSE otherwise.
+ */
+gboolean
+udisks_linux_mdraid_object_have_devices (UDisksLinuxMDRaidObject   *object)
+{
+  g_return_val_if_fail (UDISKS_IS_LINUX_MDRAID_OBJECT (object), FALSE);
+
+  return g_list_length (object->member_devices) > 0 || object->raid_device != NULL;
+}
+
+/**
+ * udisks_linux_mdraid_object_get_uuid:
+ * @object: A #UDisksLinuxMDRaidObject.
+ *
+ * Gets the UUID for @object.
+ *
+ * Returns: (transfer none): The UUID for object. Do not free, the string belongs to @object.
+ */
+const gchar *
+udisks_linux_mdraid_object_get_uuid (UDisksLinuxMDRaidObject *object)
+{
+  g_return_val_if_fail (UDISKS_IS_LINUX_MDRAID_OBJECT (object), NULL);
+  return object->uuid;
+}
+
+
index b167481..5094863 100644 (file)
@@ -32,14 +32,18 @@ G_BEGIN_DECLS
 
 GType                    udisks_linux_mdraid_object_get_type      (void) G_GNUC_CONST;
 UDisksLinuxMDRaidObject *udisks_linux_mdraid_object_new           (UDisksDaemon              *daemon,
-                                                                   GUdevDevice               *device);
+                                                                   const gchar               *uuid);
 void                     udisks_linux_mdraid_object_uevent        (UDisksLinuxMDRaidObject   *object,
                                                                    const gchar               *action,
-                                                                   GUdevDevice               *device);
+                                                                   GUdevDevice               *device,
+                                                                   gboolean                   is_member);
+const gchar             *udisks_linux_mdraid_object_get_uuid      (UDisksLinuxMDRaidObject   *object);
 UDisksDaemon            *udisks_linux_mdraid_object_get_daemon    (UDisksLinuxMDRaidObject   *object);
 GList                   *udisks_linux_mdraid_object_get_members   (UDisksLinuxMDRaidObject   *object);
 GUdevDevice             *udisks_linux_mdraid_object_get_device    (UDisksLinuxMDRaidObject   *object);
 
+gboolean                 udisks_linux_mdraid_object_have_devices  (UDisksLinuxMDRaidObject   *object);
+
 G_END_DECLS
 
 #endif /* __UDISKS_LINUX_MDRAID_OBJECT_H__ */
index 274e7aa..450ea75 100644 (file)
@@ -68,6 +68,7 @@ struct _UDisksLinuxProvider
   /* maps from array UUID and sysfs_path to UDisksLinuxMDRaidObject instances */
   GHashTable *uuid_to_mdraid;
   GHashTable *sysfs_path_to_mdraid;
+  GHashTable *sysfs_path_to_mdraid_members;
 
   GFileMonitor *etc_udisks2_dir_monitor;
 
@@ -137,6 +138,7 @@ udisks_linux_provider_finalize (GObject *object)
   g_hash_table_unref (provider->sysfs_path_to_drive);
   g_hash_table_unref (provider->uuid_to_mdraid);
   g_hash_table_unref (provider->sysfs_path_to_mdraid);
+  g_hash_table_unref (provider->sysfs_path_to_mdraid_members);
   g_object_unref (provider->gudev_client);
 
   udisks_object_skeleton_set_manager (provider->manager_object, NULL);
@@ -339,6 +341,10 @@ udisks_linux_provider_start (UDisksProvider *_provider)
                                                           g_str_equal,
                                                           g_free,
                                                           NULL);
+  provider->sysfs_path_to_mdraid_members = g_hash_table_new_full (g_str_hash,
+                                                                  g_str_equal,
+                                                                  g_free,
+                                                                  NULL);
 
   devices = g_udev_client_query_by_subsystem (provider->gudev_client, "block");
   /* make sure we process sda before sdz and sdz before sdaa */
@@ -466,46 +472,67 @@ perform_initial_housekeeping_for_drive (GIOSchedulerJob *job,
 /* ---------------------------------------------------------------------------------------------------- */
 
 /* called with lock held */
+
 static void
-handle_block_uevent_for_mdraid (UDisksLinuxProvider *provider,
-                                const gchar         *action,
-                                GUdevDevice         *device)
+maybe_remove_mdraid_object (UDisksLinuxProvider     *provider,
+                            UDisksLinuxMDRaidObject *object)
+{
+  gchar *object_uuid = NULL;
+  UDisksDaemon *daemon = NULL;
+
+  /* remove the object only if there are no devices left */
+  if (udisks_linux_mdraid_object_have_devices (object))
+    goto out;
+
+  daemon = udisks_provider_get_daemon (UDISKS_PROVIDER (provider));
+
+  object_uuid = g_strdup (udisks_linux_mdraid_object_get_uuid (object));
+  g_dbus_object_manager_server_unexport (udisks_daemon_get_object_manager (daemon),
+                                         g_dbus_object_get_object_path (G_DBUS_OBJECT (object)));
+  g_warn_if_fail (g_hash_table_remove (provider->uuid_to_mdraid, object_uuid));
+
+ out:
+  g_free (object_uuid);
+}
+
+static void
+handle_block_uevent_for_mdraid_with_uuid (UDisksLinuxProvider *provider,
+                                          const gchar         *action,
+                                          GUdevDevice         *device,
+                                          const gchar         *uuid,
+                                          gboolean             is_member)
 {
   UDisksLinuxMDRaidObject *object;
   UDisksDaemon *daemon;
   const gchar *sysfs_path;
-  const gchar *uuid;
 
   daemon = udisks_provider_get_daemon (UDISKS_PROVIDER (provider));
   sysfs_path = g_udev_device_get_sysfs_path (device);
-  uuid = g_udev_device_get_property (device, "MD_UUID");
 
-  /* if there's no UUID, treat as remove event */
+  /* if uuid is NULL, consider it a remove event */
   if (uuid == NULL)
     action = "remove";
 
   if (g_strcmp0 (action, "remove") == 0)
     {
-      object = g_hash_table_lookup (provider->sysfs_path_to_mdraid, sysfs_path);
+      /* first check if this device was a member */
+      object = g_hash_table_lookup (provider->sysfs_path_to_mdraid_members, sysfs_path);
       if (object != NULL)
         {
-          GList *devices;
-
-          udisks_linux_mdraid_object_uevent (object, action, device);
+          udisks_linux_mdraid_object_uevent (object, action, device, TRUE /* is_member */);
+          g_warn_if_fail (g_hash_table_remove (provider->sysfs_path_to_mdraid_members, sysfs_path));
+          maybe_remove_mdraid_object (provider, object);
+          udisks_debug ("removed member for %s", sysfs_path);
+        }
 
+      /* then check if the device was the raid device */
+      object = g_hash_table_lookup (provider->sysfs_path_to_mdraid, sysfs_path);
+      if (object != NULL)
+        {
+          udisks_linux_mdraid_object_uevent (object, action, device, FALSE /* is_member */);
           g_warn_if_fail (g_hash_table_remove (provider->sysfs_path_to_mdraid, sysfs_path));
-
-          devices = udisks_linux_mdraid_object_get_members (object);
-          if (devices == NULL)
-            {
-              const gchar *existing_uuid;
-              existing_uuid = g_object_get_data (G_OBJECT (object), "x-uuid");
-              g_dbus_object_manager_server_unexport (udisks_daemon_get_object_manager (daemon),
-                                                     g_dbus_object_get_object_path (G_DBUS_OBJECT (object)));
-              g_warn_if_fail (g_hash_table_remove (provider->uuid_to_mdraid, existing_uuid));
-            }
-          g_list_foreach (devices, (GFunc) g_object_unref, NULL);
-          g_list_free (devices);
+          maybe_remove_mdraid_object (provider, object);
+          udisks_debug ("removed object for %s", sysfs_path);
         }
     }
   else
@@ -516,21 +543,29 @@ handle_block_uevent_for_mdraid (UDisksLinuxProvider *provider,
       object = g_hash_table_lookup (provider->uuid_to_mdraid, uuid);
       if (object != NULL)
         {
-          if (g_hash_table_lookup (provider->sysfs_path_to_mdraid, sysfs_path) == NULL)
-            g_hash_table_insert (provider->sysfs_path_to_mdraid, g_strdup (sysfs_path), object);
-          udisks_linux_mdraid_object_uevent (object, action, device);
+          if (is_member)
+            {
+              if (g_hash_table_lookup (provider->sysfs_path_to_mdraid_members, sysfs_path) == NULL)
+                g_hash_table_insert (provider->sysfs_path_to_mdraid_members, g_strdup (sysfs_path), object);
+            }
+          else
+            {
+              if (g_hash_table_lookup (provider->sysfs_path_to_mdraid, sysfs_path) == NULL)
+                g_hash_table_insert (provider->sysfs_path_to_mdraid, g_strdup (sysfs_path), object);
+            }
+          udisks_linux_mdraid_object_uevent (object, action, device, is_member);
         }
       else
         {
-          object = udisks_linux_mdraid_object_new (daemon, device);
-          if (object != NULL)
-            {
-              g_object_set_data_full (G_OBJECT (object), "x-uuid", g_strdup (uuid), g_free);
-              g_dbus_object_manager_server_export_uniquely (udisks_daemon_get_object_manager (daemon),
-                                                            G_DBUS_OBJECT_SKELETON (object));
-              g_hash_table_insert (provider->uuid_to_mdraid, g_strdup (uuid), object);
-              g_hash_table_insert (provider->sysfs_path_to_mdraid, g_strdup (sysfs_path), object);
-            }
+          object = udisks_linux_mdraid_object_new (daemon, uuid);
+          udisks_linux_mdraid_object_uevent (object, action, device, is_member);
+          g_dbus_object_manager_server_export_uniquely (udisks_daemon_get_object_manager (daemon),
+                                                        G_DBUS_OBJECT_SKELETON (object));
+          g_hash_table_insert (provider->uuid_to_mdraid, g_strdup (uuid), object);
+          if (is_member)
+            g_hash_table_insert (provider->sysfs_path_to_mdraid_members, g_strdup (sysfs_path), object);
+          else
+            g_hash_table_insert (provider->sysfs_path_to_mdraid, g_strdup (sysfs_path), object);
         }
     }
 
@@ -538,6 +573,34 @@ handle_block_uevent_for_mdraid (UDisksLinuxProvider *provider,
   ;
 }
 
+static void
+handle_block_uevent_for_mdraid (UDisksLinuxProvider *provider,
+                                const gchar         *action,
+                                GUdevDevice         *device)
+{
+  const gchar *uuid;
+  const gchar *member_uuid;
+
+  /* For nested RAID levels, a device can be both a member of one
+   * array and the RAID device for another. Therefore we need to
+   * consider both UUIDs.
+   *
+   * For removal, we also need to consider the case where there is no
+   * UUID.
+   */
+  uuid = g_udev_device_get_property (device, "MD_UUID");
+  member_uuid = g_udev_device_get_property (device, "MD_MEMBER_UUID");
+
+  if (uuid != NULL)
+    handle_block_uevent_for_mdraid_with_uuid (provider, action, device, uuid, FALSE);
+
+  if (member_uuid != NULL)
+    handle_block_uevent_for_mdraid_with_uuid (provider, action, device, member_uuid, TRUE);
+
+  if (uuid == NULL && member_uuid == NULL)
+    handle_block_uevent_for_mdraid_with_uuid (provider, action, device, NULL, FALSE);
+}
+
 /* ---------------------------------------------------------------------------------------------------- */
 
 /* called with lock held */