Export SMART data for drives using the ATA protocol
authorDavid Zeuthen <davidz@redhat.com>
Sun, 7 Aug 2011 15:39:58 +0000 (11:39 -0400)
committerDavid Zeuthen <davidz@redhat.com>
Sun, 7 Aug 2011 15:39:58 +0000 (11:39 -0400)
Signed-off-by: David Zeuthen <davidz@redhat.com>
configure.ac
data/org.freedesktop.UDisks2.xml
doc/udisks2-docs.xml
doc/udisks2-sections.txt
doc/udisks2.types
src/Makefile.am
src/udiskslinuxdrive.c
src/udiskslinuxdrive.h
src/udiskslinuxprovider.c
udisks/udisksenums.h
udisks/udiskserror.c

index 04c13c0..0bd86dc 100644 (file)
@@ -53,6 +53,10 @@ PKG_CHECK_MODULES(POLKIT_AGENT_1, [polkit-agent-1 >= 0.92])
 AC_SUBST(POLKIT_AGENT_1_CFLAGS)
 AC_SUBST(POLKIT_AGENT_1_LIBS)
 
+PKG_CHECK_MODULES(LIBATASMART, [libatasmart >= 0.17])
+AC_SUBST(LIBATASMART_CFLAGS)
+AC_SUBST(LIBATASMART_LIBS)
+
 # Internationalization
 #
 
index eac12f6..f019b4a 100644 (file)
     </method>
   </interface>
 
+  <!--
+    org.freedesktop.UDisks2.Drive.Ata:
+    @short_description: Disk drive using the ATA command-set
+
+    Objects implementing this interface also implement the
+    #org.freedesktop.UDisks2.Drive interface.
+  -->
+  <interface name="org.freedesktop.UDisks2.Drive.Ata">
+    <!-- SmartSupported: Whether the drive supports SMART. -->
+    <property name="SmartSupported" type="b" access="read"/>
+
+    <!-- SmartEnabled: Whether SMART is enabled. -->
+    <property name="SmartEnabled" type="b" access="read"/>
+
+    <!-- SmartUpdated:
+         The point in time (seconds since the
+         <ulink url="http://en.wikipedia.org/wiki/Unix_epoch">Unix Epoch</ulink>)
+         that the SMART status was updated or 0 if never updated.
+    -->
+    <property name="SmartUpdated" type="t" access="read"/>
+
+    <!-- SmartFailing:
+         Set to %TRUE if disk is about to fail.
+
+         This value is read from the disk itself and does not include
+         any interpretation.
+
+         This property is only meaningful if the property
+         #org.freedesktop.UDisks2.Drive.Ata:SmartUpdated is non-zero.
+    -->
+    <property name="SmartFailing" type="b" access="read"/>
+
+    <!-- SmartPowerOnSeconds:
+         The amount of time the disk has been powered on (according to SMART data) or 0 if unknown.
+
+         This property is only meaningful if the property
+         #org.freedesktop.UDisks2.Drive.Ata:SmartUpdated is non-zero.
+    -->
+    <property name="SmartPowerOnSeconds" type="t" access="read"/>
+
+    <!-- SmartTemperature:
+         The temperature (in Kelvin) of the disk according to SMART data or 0 if unknown.
+
+         This property is only meaningful if the property
+         #org.freedesktop.UDisks2.Drive.Ata:SmartUpdated is non-zero.
+    -->
+    <property name="SmartTemperature" type="d" access="read"/>
+
+    <!--
+        SmartUpdate:
+        @options: Options - known options (in addition to <link linkend="udisks-std-options">standard options</link>) includes <parameter>nowakeup</parameter> (of type 'b').
+
+        Reads SMART data from the drive and update relevant properties.
+
+        If the option @nowakeup is given and the disk is in a sleeping
+        state, the error
+        <literal>org.freedesktop.UDisks.Error.WouldWakeup</literal> is
+        returned.
+    -->
+    <method name="SmartUpdate">
+      <arg name="options" direction="in" type="a{sv}"/>
+    </method>
+
+    <!-- TODO: self-tests, attributes etc. -->
+  </interface>
+
   <!-- ********************************************************************** -->
 
   <!--
index fc0ca4f..896cbf7 100644 (file)
       <title>D-Bus Interfaces</title>
       <xi:include href="../udisks/udisks-generated-doc-org.freedesktop.UDisks2.Manager.xml"/>
       <xi:include href="../udisks/udisks-generated-doc-org.freedesktop.UDisks2.Drive.xml"/>
+      <xi:include href="../udisks/udisks-generated-doc-org.freedesktop.UDisks2.Drive.Ata.xml"/>
       <xi:include href="../udisks/udisks-generated-doc-org.freedesktop.UDisks2.BlockDevice.xml"/>
       <xi:include href="../udisks/udisks-generated-doc-org.freedesktop.UDisks2.Filesystem.xml"/>
       <xi:include href="../udisks/udisks-generated-doc-org.freedesktop.UDisks2.Swapspace.xml"/>
       <xi:include href="xml/UDisksObjectManagerClient.xml"/>
       <xi:include href="xml/UDisksManager.xml"/>
       <xi:include href="xml/UDisksDrive.xml"/>
+      <xi:include href="xml/UDisksDriveAta.xml"/>
       <xi:include href="xml/UDisksJob.xml"/>
       <xi:include href="xml/UDisksBlockDevice.xml"/>
       <xi:include href="xml/UDisksFilesystem.xml"/>
index 7d766c1..39b2bb1 100644 (file)
@@ -165,6 +165,7 @@ udisks_linux_drive_new
 udisks_linux_drive_uevent
 udisks_linux_drive_get_daemon
 udisks_linux_drive_get_devices
+udisks_linux_drive_housekeeping
 <SUBSECTION Standard>
 UDISKS_TYPE_LINUX_DRIVE
 UDISKS_LINUX_DRIVE
@@ -346,6 +347,7 @@ UDisksObject
 UDisksObjectIface
 udisks_object_get_block_device
 udisks_object_get_drive
+udisks_object_get_drive_ata
 udisks_object_get_filesystem
 udisks_object_get_job
 udisks_object_get_swapspace
@@ -354,6 +356,7 @@ udisks_object_get_loop
 udisks_object_get_manager
 udisks_object_peek_block_device
 udisks_object_peek_drive
+udisks_object_peek_drive_ata
 udisks_object_peek_filesystem
 udisks_object_peek_job
 udisks_object_peek_swapspace
@@ -368,6 +371,7 @@ UDisksObjectSkeletonClass
 udisks_object_skeleton_new
 udisks_object_skeleton_set_block_device
 udisks_object_skeleton_set_drive
+udisks_object_skeleton_set_drive_ata
 udisks_object_skeleton_set_filesystem
 udisks_object_skeleton_set_job
 udisks_object_skeleton_set_swapspace
@@ -488,6 +492,63 @@ udisks_drive_skeleton_get_type
 </SECTION>
 
 <SECTION>
+<FILE>UDisksDriveAta</FILE>
+UDisksDriveAta
+UDisksDriveAtaIface
+udisks_drive_ata_interface_info
+udisks_drive_ata_override_properties
+udisks_drive_ata_call_smart_update
+udisks_drive_ata_call_smart_update_finish
+udisks_drive_ata_call_smart_update_sync
+udisks_drive_ata_complete_smart_update
+udisks_drive_ata_get_smart_supported
+udisks_drive_ata_get_smart_enabled
+udisks_drive_ata_get_smart_updated
+udisks_drive_ata_get_smart_failing
+udisks_drive_ata_get_smart_temperature
+udisks_drive_ata_get_smart_power_on_seconds
+udisks_drive_ata_set_smart_supported
+udisks_drive_ata_set_smart_enabled
+udisks_drive_ata_set_smart_updated
+udisks_drive_ata_set_smart_failing
+udisks_drive_ata_set_smart_temperature
+udisks_drive_ata_set_smart_power_on_seconds
+UDisksDriveAtaProxy
+UDisksDriveAtaProxyClass
+udisks_drive_ata_proxy_new
+udisks_drive_ata_proxy_new_finish
+udisks_drive_ata_proxy_new_sync
+udisks_drive_ata_proxy_new_for_bus
+udisks_drive_ata_proxy_new_for_bus_finish
+udisks_drive_ata_proxy_new_for_bus_sync
+UDisksDriveAtaSkeleton
+UDisksDriveAtaSkeletonClass
+udisks_drive_ata_skeleton_new
+<SUBSECTION Standard>
+UDISKS_TYPE_DRIVE_ATA
+UDISKS_IS_DRIVE_ATA
+UDISKS_DRIVE_ATA
+UDISKS_DRIVE_ATA_GET_IFACE
+UDISKS_TYPE_DRIVE_ATA_PROXY
+UDISKS_IS_DRIVE_ATA_PROXY
+UDISKS_IS_DRIVE_ATA_PROXY_CLASS
+UDISKS_DRIVE_ATA_PROXY
+UDISKS_DRIVE_ATA_PROXY_CLASS
+UDISKS_DRIVE_ATA_PROXY_GET_CLASS
+UDISKS_TYPE_DRIVE_ATA_SKELETON
+UDISKS_IS_DRIVE_ATA_SKELETON
+UDISKS_IS_DRIVE_ATA_SKELETON_CLASS
+UDISKS_DRIVE_ATA_SKELETON
+UDISKS_DRIVE_ATA_SKELETON_CLASS
+UDISKS_DRIVE_ATA_SKELETON_GET_CLASS
+UDisksDriveAtaProxyPrivate
+UDisksDriveAtaSkeletonPrivate
+udisks_drive_ata_get_type
+udisks_drive_ata_proxy_get_type
+udisks_drive_ata_skeleton_get_type
+</SECTION>
+
+<SECTION>
 <FILE>UDisksJob</FILE>
 UDisksJob
 UDisksJobIface
index 2e023c3..7a99f3d 100644 (file)
@@ -22,6 +22,9 @@ udisks_cleanup_get_type
 udisks_drive_get_type
 udisks_drive_proxy_get_type
 udisks_drive_skeleton_get_type
+udisks_drive_ata_get_type
+udisks_drive_ata_proxy_get_type
+udisks_drive_ata_skeleton_get_type
 udisks_block_device_get_type
 udisks_block_device_proxy_get_type
 udisks_block_device_skeleton_get_type
index 3f0e108..63f0d8b 100644 (file)
@@ -97,7 +97,6 @@ udisksd_SOURCES =                                     \
 
 udisksd_CFLAGS =                                       \
        -DG_LOG_DOMAIN=\"udisks2-daemon\"               \
-       $(LIBATASMART_CFLAGS)                           \
        $(NULL)
 
 udisksd_LDADD =                                        \
index 2fded2d..16c54d3 100644 (file)
@@ -25,6 +25,8 @@
 #include <stdlib.h>
 #include <stdio.h>
 
+#include <atasmart.h>
+
 #include "udiskslogging.h"
 #include "udisksdaemon.h"
 #include "udisksdaemonutil.h"
@@ -59,8 +61,17 @@ struct _UDisksLinuxDrive
 
   /* interfaces */
   UDisksDrive *iface_drive;
+  UDisksDriveAta *iface_drive_ata;
+
+  /* ATA Smart */
+  guint64 ata_smart_updated;
+  gboolean ata_smart_failing;
+  gdouble ata_smart_temperature;
+  guint64 ata_smart_power_on_seconds;
 };
 
+G_LOCK_DEFINE_STATIC (drive_lock);
+
 struct _UDisksLinuxDriveClass
 {
   UDisksObjectSkeletonClass parent_class;
@@ -81,12 +92,13 @@ udisks_linux_drive_finalize (GObject *object)
   UDisksLinuxDrive *drive = UDISKS_LINUX_DRIVE (object);
 
   /* note: we don't hold a ref to drive->daemon or drive->mount_monitor */
-
   g_list_foreach (drive->devices, (GFunc) g_object_unref, NULL);
   g_list_free (drive->devices);
 
   if (drive->iface_drive != NULL)
     g_object_unref (drive->iface_drive);
+  if (drive->iface_drive_ata != NULL)
+    g_object_unref (drive->iface_drive_ata);
 
   if (G_OBJECT_CLASS (udisks_linux_drive_parent_class)->finalize != NULL)
     G_OBJECT_CLASS (udisks_linux_drive_parent_class)->finalize (object);
@@ -584,16 +596,18 @@ drive_check (UDisksLinuxDrive *drive)
   return TRUE;
 }
 
+/* TODO: ensure that returned object is for a physical device e.g. not multipath */
 static UDisksObject *
-find_block_object (GDBusObjectManagerServer *object_manager,
-                   UDisksLinuxDrive         *drive)
+find_block_object (UDisksLinuxDrive *drive)
 {
+  GDBusObjectManagerServer *object_manager;
   UDisksObject *ret;
   GList *objects;
   GList *l;
 
   ret = NULL;
 
+  object_manager = udisks_daemon_get_object_manager (udisks_linux_drive_get_daemon (drive));
   objects = g_dbus_object_manager_get_objects (G_DBUS_OBJECT_MANAGER (object_manager));
   for (l = objects; l != NULL; l = l->next)
     {
@@ -646,8 +660,7 @@ on_eject (UDisksDrive           *drive_iface,
   error_message = NULL;
 
   daemon = udisks_linux_drive_get_daemon (drive);
-  block_object = find_block_object (udisks_daemon_get_object_manager (daemon),
-                                    drive);
+  block_object = find_block_object (drive);
   if (block_object == NULL)
     {
       g_dbus_method_invocation_return_error (invocation,
@@ -658,8 +671,6 @@ on_eject (UDisksDrive           *drive_iface,
     }
   block = udisks_object_peek_block_device (block_object);
 
-  /* TODO: ensure it's a physical device e.g. not mpath */
-
   /* TODO: is it a good idea to overload modify-device? */
   action_id = "org.freedesktop.udisks2.modify-device";
   if (udisks_block_device_get_hint_system (block))
@@ -901,6 +912,261 @@ drive_update (UDisksLinuxDrive      *drive,
 
 /* ---------------------------------------------------------------------------------------------------- */
 
+static void drive_ata_smart_update (UDisksLinuxDrive *drive);
+
+static gboolean
+update_smart (UDisksLinuxDrive  *drive,
+              gboolean           nowakeup,
+              GError           **error)
+{
+  gboolean ret;
+  SkDisk *d;
+  SkBool awake;
+  SkBool good;
+  uint64_t temp_mkelvin;
+  uint64_t power_on_msec;
+  GUdevDevice *device;
+
+  d = NULL;
+  ret = FALSE;
+
+  device = G_UDEV_DEVICE (drive->devices->data);
+
+  if (sk_disk_open (g_udev_device_get_device_file (device), &d) != 0)
+    {
+      g_set_error (error,
+                   UDISKS_ERROR,
+                   UDISKS_ERROR_FAILED,
+                   "sk_disk_open: %m");
+      goto out;
+    }
+
+  if (sk_disk_check_sleep_mode (d, &awake) != 0)
+    {
+      g_set_error (error,
+                   UDISKS_ERROR,
+                   UDISKS_ERROR_FAILED,
+                   "sk_disk_check_sleep_mode: %m");
+      goto out;
+    }
+
+  /* don't wake up disk unless specically asked to */
+  if (nowakeup && !awake)
+    {
+      g_set_error (error,
+                   UDISKS_ERROR,
+                   UDISKS_ERROR_WOULD_WAKEUP,
+                   "Disk is in sleep mode and the nowakeup option was passed");
+      goto out;
+    }
+
+  if (sk_disk_smart_read_data (d) != 0)
+    {
+      g_set_error (error,
+                   UDISKS_ERROR,
+                   UDISKS_ERROR_FAILED,
+                   "sk_disk_smart_read_data: %m");
+      goto out;
+    }
+
+  if (sk_disk_smart_status (d, &good) != 0)
+    {
+      g_set_error (error,
+                   UDISKS_ERROR,
+                   UDISKS_ERROR_FAILED,
+                   "sk_disk_smart_status: %m");
+      goto out;
+    }
+
+  /* don't care if these are failing or not */
+  temp_mkelvin = 0;
+  sk_disk_smart_get_temperature (d, &temp_mkelvin);
+  power_on_msec = 0;
+  sk_disk_smart_get_power_on (d, &power_on_msec);
+
+  G_LOCK (drive_lock);
+  drive->ata_smart_updated = time (NULL);
+  drive->ata_smart_failing = !good;
+  drive->ata_smart_temperature = temp_mkelvin / 1000.0;
+  drive->ata_smart_power_on_seconds = power_on_msec / 1000.0;
+  G_UNLOCK (drive_lock);
+
+  drive_ata_smart_update (drive);
+
+  ret = TRUE;
+
+ out:
+  if (d != NULL)
+    sk_disk_free (d);
+  return ret;
+}
+
+static gboolean
+on_smart_update (UDisksDriveAta        *drive_ata_iface,
+                 GDBusMethodInvocation *invocation,
+                 GVariant              *options,
+                 gpointer               user_data)
+{
+  UDisksLinuxDrive *drive = UDISKS_LINUX_DRIVE (user_data);
+  UDisksObject *block_object;
+  UDisksBlockDevice *block;
+  UDisksDaemon *daemon;
+  const gchar *action_id;
+  gboolean nowakeup;
+  GError *error;
+
+  daemon = NULL;
+  block = NULL;
+
+  daemon = udisks_linux_drive_get_daemon (drive);
+  block_object = find_block_object (drive);
+  if (block_object == NULL)
+    {
+      g_dbus_method_invocation_return_error (invocation,
+                                             UDISKS_ERROR,
+                                             UDISKS_ERROR_FAILED,
+                                             "Unable to find physical block device for drive");
+      goto out;
+    }
+  block = udisks_object_peek_block_device (block_object);
+
+  g_variant_lookup (options,
+                    "nowakeup",
+                    "b",
+                    &nowakeup);
+
+  /* TODO: is it a good idea to overload modify-device? */
+  action_id = "org.freedesktop.udisks2.modify-device";
+  if (udisks_block_device_get_hint_system (block))
+    action_id = "org.freedesktop.udisks2.modify-device-system";
+
+  /* Check that the user is actually authorized */
+  if (!udisks_daemon_util_check_authorization_sync (daemon,
+                                                    block_object,
+                                                    action_id,
+                                                    options,
+                                                    N_("Authentication is required to update SMART from $(udisks2.device)"),
+                                                    invocation))
+    goto out;
+
+  if (!udisks_drive_ata_get_smart_supported (drive_ata_iface))
+    {
+      g_dbus_method_invocation_return_error (invocation,
+                                             UDISKS_ERROR,
+                                             UDISKS_ERROR_FAILED,
+                                             "SMART is not supported");
+      goto out;
+    }
+
+  if (!udisks_drive_ata_get_smart_enabled (drive_ata_iface))
+    {
+      g_dbus_method_invocation_return_error (invocation,
+                                             UDISKS_ERROR,
+                                             UDISKS_ERROR_FAILED,
+                                             "SMART is not enabled");
+      goto out;
+    }
+
+  error = NULL;
+  if (!update_smart (drive, nowakeup, &error))
+    {
+      udisks_warning ("Error updating ATA smart for %s: %s (%s, %d)",
+                      g_dbus_object_get_object_path (G_DBUS_OBJECT (drive)),
+                      error->message, g_quark_to_string (error->domain), error->code);
+      g_dbus_method_invocation_take_error (invocation, error);
+      goto out;
+    }
+
+  udisks_drive_ata_complete_smart_update (drive_ata_iface, invocation);
+
+ out:
+  if (block_object != NULL)
+    g_object_unref (block_object);
+  return TRUE; /* returning TRUE means that we handled the method invocation */
+}
+
+static gboolean
+drive_ata_check (UDisksLinuxDrive *drive)
+{
+  gboolean ret;
+  GUdevDevice *device;
+
+  ret = FALSE;
+  if (drive->devices == NULL)
+    goto out;
+
+  device = G_UDEV_DEVICE (drive->devices->data);
+  if (!g_udev_device_get_property_as_boolean (device, "ID_ATA"))
+    goto out;
+
+  ret = TRUE;
+
+ out:
+  return ret;
+}
+
+static void
+drive_ata_connect (UDisksLinuxDrive *drive)
+{
+  g_dbus_interface_skeleton_set_flags (G_DBUS_INTERFACE_SKELETON (drive->iface_drive_ata),
+                                       G_DBUS_INTERFACE_SKELETON_FLAGS_HANDLE_METHOD_INVOCATIONS_IN_THREAD);
+  g_signal_connect (drive->iface_drive_ata,
+                    "handle-smart-update",
+                    G_CALLBACK (on_smart_update),
+                    drive);
+}
+
+/* also called from *any* thread when the SMART data has been updated */
+static void
+drive_ata_smart_update (UDisksLinuxDrive *drive)
+{
+  GUdevDevice *device;
+  gboolean supported;
+  gboolean enabled;
+  guint64 updated;
+  gboolean failing;
+  gdouble temperature;
+  guint64 power_on_seconds;
+
+  device = G_UDEV_DEVICE (drive->devices->data);
+
+  supported = g_udev_device_get_property_as_boolean (device, "ID_ATA_FEATURE_SET_SMART");
+  enabled = g_udev_device_get_property_as_boolean (device, "ID_ATA_FEATURE_SET_SMART_ENABLED");
+  updated = 0;
+  failing = FALSE;
+  temperature = 0.0;
+  power_on_seconds = 0;
+
+  G_LOCK (drive_lock);
+  if (drive->ata_smart_updated > 0)
+    {
+      updated = drive->ata_smart_updated;
+      failing = drive->ata_smart_failing;
+      temperature = drive->ata_smart_temperature;
+      power_on_seconds = drive->ata_smart_power_on_seconds;
+    }
+  G_UNLOCK (drive_lock);
+
+  g_object_freeze_notify (G_OBJECT (drive->iface_drive_ata));
+  udisks_drive_ata_set_smart_supported (drive->iface_drive_ata, supported);
+  udisks_drive_ata_set_smart_enabled (drive->iface_drive_ata, enabled);
+  udisks_drive_ata_set_smart_updated (drive->iface_drive_ata, updated);
+  udisks_drive_ata_set_smart_failing (drive->iface_drive_ata, failing);
+  udisks_drive_ata_set_smart_temperature (drive->iface_drive_ata, temperature);
+  udisks_drive_ata_set_smart_power_on_seconds (drive->iface_drive_ata, power_on_seconds);
+  g_object_thaw_notify (G_OBJECT (drive->iface_drive_ata));
+}
+
+static void
+drive_ata_update (UDisksLinuxDrive      *drive,
+                  const gchar           *uevent_action,
+                  GDBusInterface        *_iface)
+{
+  drive_ata_smart_update (drive);
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
 static GList *
 find_link_for_sysfs_path (UDisksLinuxDrive *drive,
                           const gchar      *sysfs_path)
@@ -968,6 +1234,8 @@ udisks_linux_drive_uevent (UDisksLinuxDrive *drive,
 
   update_iface (drive, action, drive_check, drive_connect, drive_update,
                 UDISKS_TYPE_DRIVE_SKELETON, &drive->iface_drive);
+  update_iface (drive, action, drive_ata_check, drive_ata_connect, drive_ata_update,
+                UDISKS_TYPE_DRIVE_ATA_SKELETON, &drive->iface_drive_ata);
 }
 
 /* ---------------------------------------------------------------------------------------------------- */
@@ -1037,3 +1305,73 @@ udisks_linux_drive_should_include_device (GUdevDevice  *device,
   g_free (vpd);
   return ret;
 }
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+/**
+ * udisks_linux_drive_housekeeping:
+ * @drive: A #UDisksLinuxDrive.
+ * @secs_since_last: Number of seconds sincex the last housekeeping or 0 if the first housekeeping ever.
+ * @cancellable: A %GCancellable or %NULL.
+ * @error: Return location for error or %NULL.
+ *
+ * Called periodically (every ten minutes or so) to perform
+ * housekeeping tasks such as refreshing ATA SMART data.
+ *
+ * The function runs in a dedicated thread and is allowed to perform
+ * blocking I/O.
+ *
+ * Long-running tasks should periodically check @cancellable to see if
+ * they have been cancelled.
+ *
+ * Returns: %TRUE if the operation succeeded, %FALSE if @error is set.
+ */
+gboolean
+udisks_linux_drive_housekeeping (UDisksLinuxDrive  *drive,
+                                 guint              secs_since_last,
+                                 GCancellable      *cancellable,
+                                 GError           **error)
+{
+  gboolean ret;
+
+  ret = FALSE;
+
+  if (drive->iface_drive_ata != NULL &&
+      udisks_drive_ata_get_smart_supported (drive->iface_drive_ata) &&
+      udisks_drive_ata_get_smart_enabled (drive->iface_drive_ata))
+    {
+      GError *local_error;
+      gboolean nowakeup;
+
+      /* Wake-up only on start-up */
+      nowakeup = TRUE;
+      if (secs_since_last == 0)
+        nowakeup = FALSE;
+
+      udisks_info ("Refreshing SMART data on %s (nowakeup=%d)",
+                   g_dbus_object_get_object_path (G_DBUS_OBJECT (drive)),
+                   nowakeup);
+
+      local_error = NULL;
+      if (!update_smart (drive, nowakeup, &local_error))
+        {
+          if (nowakeup && (local_error->domain == UDISKS_ERROR &&
+                           local_error->code == UDISKS_ERROR_WOULD_WAKEUP))
+            {
+              udisks_info ("Drive %s is in a sleep state",
+                           g_dbus_object_get_object_path (G_DBUS_OBJECT (drive)));
+              g_error_free (local_error);
+            }
+          else
+            {
+              g_propagate_prefixed_error (error, local_error, "Error updating SMART data: ");
+              goto out;
+            }
+        }
+    }
+
+  ret = TRUE;
+
+ out:
+  return ret;
+}
index 206696d..f4a1593 100644 (file)
@@ -39,6 +39,11 @@ void              udisks_linux_drive_uevent        (UDisksLinuxDrive  *drive,
 UDisksDaemon     *udisks_linux_drive_get_daemon    (UDisksLinuxDrive  *drive);
 GList            *udisks_linux_drive_get_devices   (UDisksLinuxDrive  *drive);
 
+gboolean          udisks_linux_drive_housekeeping  (UDisksLinuxDrive  *drive,
+                                                    guint              secs_since_last,
+                                                    GCancellable      *cancellable,
+                                                    GError           **error);
+
 gboolean          udisks_linux_drive_should_include_device (GUdevDevice  *device,
                                                             gchar       **out_vpd);
 
index 329bd54..8f29311 100644 (file)
@@ -64,19 +64,26 @@ struct _UDisksLinuxProvider
   GHashTable *vpd_to_drive;
   GHashTable *sysfs_path_to_drive;
 
-  /* maps from sysfs path to UDisksLinuxController objects */
-  GHashTable *sysfs_to_controller;
+  /* set to TRUE only in the coldplug phase */
+  gboolean coldplug;
+
+  guint housekeeping_timeout;
+  guint64 housekeeping_last;
+  gboolean housekeeping_running;
 };
 
+G_LOCK_DEFINE_STATIC (provider_lock);
+
 struct _UDisksLinuxProviderClass
 {
   UDisksProviderClass parent_class;
 };
 
-static void
-udisks_linux_provider_handle_uevent (UDisksLinuxProvider *provider,
-                                     const gchar         *action,
-                                     GUdevDevice         *device);
+static void udisks_linux_provider_handle_uevent (UDisksLinuxProvider *provider,
+                                                 const gchar         *action,
+                                                 GUdevDevice         *device);
+
+static gboolean on_housekeeping_timeout (gpointer user_data);
 
 G_DEFINE_TYPE (UDisksLinuxProvider, udisks_linux_provider, UDISKS_TYPE_PROVIDER);
 
@@ -88,12 +95,14 @@ udisks_linux_provider_finalize (GObject *object)
   g_hash_table_unref (provider->sysfs_to_block);
   g_hash_table_unref (provider->vpd_to_drive);
   g_hash_table_unref (provider->sysfs_path_to_drive);
-  g_hash_table_unref (provider->sysfs_to_controller);
   g_object_unref (provider->gudev_client);
 
   udisks_object_skeleton_set_manager (provider->manager_object, NULL);
   g_object_unref (provider->manager_object);
 
+  if (provider->housekeeping_timeout > 0)
+    g_source_remove (provider->housekeeping_timeout);
+
   if (G_OBJECT_CLASS (udisks_linux_provider_parent_class)->finalize != NULL)
     G_OBJECT_CLASS (udisks_linux_provider_parent_class)->finalize (object);
 }
@@ -131,6 +140,8 @@ udisks_linux_provider_start (UDisksProvider *_provider)
   GList *devices;
   GList *l;
 
+  provider->coldplug = TRUE;
+
   if (UDISKS_PROVIDER_CLASS (udisks_linux_provider_parent_class)->start != NULL)
     UDISKS_PROVIDER_CLASS (udisks_linux_provider_parent_class)->start (_provider);
 
@@ -156,16 +167,21 @@ udisks_linux_provider_start (UDisksProvider *_provider)
                                                          g_str_equal,
                                                          g_free,
                                                          NULL);
-  provider->sysfs_to_controller = g_hash_table_new_full (g_str_hash,
-                                                         g_str_equal,
-                                                         g_free,
-                                                         (GDestroyNotify) g_object_unref);
 
   devices = g_udev_client_query_by_subsystem (provider->gudev_client, "block");
   for (l = devices; l != NULL; l = l->next)
     udisks_linux_provider_handle_uevent (provider, "add", G_UDEV_DEVICE (l->data));
   g_list_foreach (devices, (GFunc) g_object_unref, NULL);
   g_list_free (devices);
+
+  /* schedule housekeeping for every 10 minutes */
+  provider->housekeeping_timeout = g_timeout_add_seconds (10*60,
+                                                          on_housekeeping_timeout,
+                                                          provider);
+  /* ... and also do an initial run */
+  on_housekeeping_timeout (provider);
+
+  provider->coldplug = FALSE;
 }
 
 
@@ -216,6 +232,28 @@ udisks_linux_provider_get_udev_client (UDisksLinuxProvider *provider)
 
 /* ---------------------------------------------------------------------------------------------------- */
 
+static gboolean
+perform_initial_housekeeping_for_drive (GIOSchedulerJob *job,
+                                        GCancellable    *cancellable,
+                                        gpointer         user_data)
+{
+  UDisksLinuxDrive *drive = UDISKS_LINUX_DRIVE (user_data);
+  GError *error;
+
+  error = NULL;
+  if (!udisks_linux_drive_housekeeping (drive, 0,
+                                        NULL, /* TODO: cancellable */
+                                        &error))
+    {
+      udisks_warning ("Error performing initial housekeeping for drive %s: %s (%s, %d)",
+                      g_dbus_object_get_object_path (G_DBUS_OBJECT (drive)),
+                      error->message, g_quark_to_string (error->domain), error->code);
+      g_error_free (error);
+    }
+  return FALSE; /* job is complete */
+}
+
+/* called with lock held */
 static void
 handle_block_uevent_for_drive (UDisksLinuxProvider *provider,
                                const gchar         *action,
@@ -282,6 +320,16 @@ handle_block_uevent_for_drive (UDisksLinuxProvider *provider,
                                                             G_DBUS_OBJECT_SKELETON (drive));
               g_hash_table_insert (provider->vpd_to_drive, g_strdup (vpd), drive);
               g_hash_table_insert (provider->sysfs_path_to_drive, g_strdup (sysfs_path), drive);
+
+              /* schedule initial housekeeping for the drive unless coldplugging */
+              if (!provider->coldplug)
+                {
+                  g_io_scheduler_push_job (perform_initial_housekeeping_for_drive,
+                                           g_object_ref (drive),
+                                           (GDestroyNotify) g_object_unref,
+                                           G_PRIORITY_DEFAULT,
+                                           NULL);
+                }
             }
         }
     }
@@ -290,6 +338,7 @@ handle_block_uevent_for_drive (UDisksLinuxProvider *provider,
   g_free (vpd);
 }
 
+/* called with lock held */
 static void
 handle_block_uevent_for_block (UDisksLinuxProvider *provider,
                                const gchar         *action,
@@ -329,7 +378,7 @@ handle_block_uevent_for_block (UDisksLinuxProvider *provider,
     }
 }
 
-
+/* called with lock held */
 static void
 handle_block_uevent (UDisksLinuxProvider *provider,
                      const gchar         *action,
@@ -357,6 +406,7 @@ handle_block_uevent (UDisksLinuxProvider *provider,
     }
 }
 
+/* called without lock held */
 static void
 udisks_linux_provider_handle_uevent (UDisksLinuxProvider *provider,
                                      const gchar         *action,
@@ -364,6 +414,8 @@ udisks_linux_provider_handle_uevent (UDisksLinuxProvider *provider,
 {
   const gchar *subsystem;
 
+  G_LOCK (provider_lock);
+
   udisks_debug ("uevent %s %s",
                 action,
                 g_udev_device_get_sysfs_path (device));
@@ -373,4 +425,95 @@ udisks_linux_provider_handle_uevent (UDisksLinuxProvider *provider,
     {
       handle_block_uevent (provider, action, device);
     }
+
+  G_UNLOCK (provider_lock);
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+/* Runs in housekeeping thread - called without lock held */
+static void
+housekeeping_all_drives (UDisksLinuxProvider *provider,
+                         guint                secs_since_last)
+{
+  GList *drives;
+  GList *l;
+
+  G_LOCK (provider_lock);
+  drives = g_hash_table_get_values (provider->vpd_to_drive);
+  g_list_foreach (drives, (GFunc) g_object_ref, NULL);
+  G_UNLOCK (provider_lock);
+
+  for (l = drives; l != NULL; l = l->next)
+    {
+      UDisksLinuxDrive *drive = UDISKS_LINUX_DRIVE (l->data);
+      GError *error;
+
+      error = NULL;
+      if (!udisks_linux_drive_housekeeping (drive,
+                                            secs_since_last,
+                                            NULL, /* TODO: cancellable */
+                                            &error))
+        {
+          udisks_warning ("Error performing housekeeping for drive %s: %s (%s, %d)",
+                          g_dbus_object_get_object_path (G_DBUS_OBJECT (drive)),
+                          error->message, g_quark_to_string (error->domain), error->code);
+          g_error_free (error);
+        }
+    }
+
+  g_list_foreach (drives, (GFunc) g_object_unref, NULL);
+  g_list_free (drives);
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static gboolean
+housekeeping_thread_func (GIOSchedulerJob *job,
+                          GCancellable    *cancellable,
+                          gpointer         user_data)
+{
+  UDisksLinuxProvider *provider = UDISKS_LINUX_PROVIDER (user_data);
+  guint secs_since_last;
+  guint64 now;
+
+  /* TODO: probably want some kind of timeout here to avoid faulty devices/drives blocking forever */
+
+  secs_since_last = 0;
+  now = time (NULL);
+  if (provider->housekeeping_last > 0)
+    secs_since_last = now - provider->housekeeping_last;
+  provider->housekeeping_last = now;
+
+  udisks_info ("Housekeeping initiated (%d seconds since last housekeeping)", secs_since_last);
+
+  housekeeping_all_drives (provider, secs_since_last);
+
+  udisks_info ("Housekeeping complete");
+  G_LOCK (provider_lock);
+  provider->housekeeping_running = FALSE;
+  G_UNLOCK (provider_lock);
+
+  return FALSE; /* job is complete */
+}
+
+/* called from the main thread on start-up and every 10 minutes or so */
+static gboolean
+on_housekeeping_timeout (gpointer user_data)
+{
+  UDisksLinuxProvider *provider = UDISKS_LINUX_PROVIDER (user_data);
+
+  G_LOCK (provider_lock);
+  if (provider->housekeeping_running)
+    goto out;
+  provider->housekeeping_running = TRUE;
+  g_io_scheduler_push_job (housekeeping_thread_func,
+                           g_object_ref (provider),
+                           (GDestroyNotify) g_object_unref,
+                           G_PRIORITY_DEFAULT,
+                           NULL);
+ out:
+  G_UNLOCK (provider_lock);
+
+  return TRUE; /* keep timeout around */
 }
index 4d02129..d9afe60 100644 (file)
@@ -44,6 +44,7 @@ G_BEGIN_DECLS
  * @UDISKS_ERROR_ALREADY_UNMOUNTING: The device is already unmounting.
  * @UDISKS_ERROR_NOT_SUPPORTED: The operation is not supported due to missing driver/tool support.
  * @UDISKS_ERROR_TIMED_OUT: The operation timed out.
+ * @UDISKS_ERROR_WOULD_WAKEUP: The operation would wake up a disk that is in a deep-sleep state.
  *
  * Error codes for the #UDISKS_ERROR error domain and the
  * corresponding D-Bus error names.
@@ -62,10 +63,11 @@ typedef enum
   UDISKS_ERROR_MOUNTED_BY_OTHER_USER,      /* org.freedesktop.UDisks.Error.MountedByOtherUser */
   UDISKS_ERROR_ALREADY_UNMOUNTING,         /* org.freedesktop.UDisks.Error.AlreadyUnmounting */
   UDISKS_ERROR_NOT_SUPPORTED,              /* org.freedesktop.UDisks.Error.NotSupported */
-  UDISKS_ERROR_TIMED_OUT                   /* org.freedesktop.UDisks.Error.Timedout */
+  UDISKS_ERROR_TIMED_OUT,                  /* org.freedesktop.UDisks.Error.Timedout */
+  UDISKS_ERROR_WOULD_WAKEUP,               /* org.freedesktop.UDisks.Error.WouldWakeup */
 } UDisksError;
 
-#define UDISKS_ERROR_NUM_ENTRIES  (UDISKS_ERROR_TIMED_OUT + 1)
+#define UDISKS_ERROR_NUM_ENTRIES  (UDISKS_ERROR_WOULD_WAKEUP + 1)
 
 G_END_DECLS
 
index 70cbb12..27e81bd 100644 (file)
@@ -45,7 +45,8 @@ static const GDBusErrorEntry dbus_error_entries[] =
   {UDISKS_ERROR_MOUNTED_BY_OTHER_USER,        "org.freedesktop.UDisks.Error.MountedByOtherUser"},
   {UDISKS_ERROR_ALREADY_UNMOUNTING,           "org.freedesktop.UDisks.Error.AlreadyUnmounting"},
   {UDISKS_ERROR_NOT_SUPPORTED,                "org.freedesktop.UDisks.Error.NotSupported"},
-  {UDISKS_ERROR_TIMED_OUT,                    "org.freedesktop.UDisks.Error.Timedout"}
+  {UDISKS_ERROR_TIMED_OUT,                    "org.freedesktop.UDisks.Error.Timedout"},
+  {UDISKS_ERROR_WOULD_WAKEUP,                 "org.freedesktop.UDisks.Error.WouldWakeup"},
 };
 
 GQuark