Add a way to spin down drives
authorDavid Zeuthen <davidz@redhat.com>
Sun, 21 Jun 2009 23:07:20 +0000 (19:07 -0400)
committerDavid Zeuthen <davidz@redhat.com>
Sun, 21 Jun 2009 23:07:20 +0000 (19:07 -0400)
18 files changed:
doc/man/DeviceKit-disks.xml
doc/man/devkit-disks.xml
policy/org.freedesktop.devicekit.disks.policy.in
src/Makefile.am
src/devkit-disks-daemon.c
src/devkit-disks-daemon.h
src/devkit-disks-device-private.c
src/devkit-disks-device-private.h
src/devkit-disks-device.c
src/devkit-disks-device.h
src/devkit-disks-poller.c
src/devkit-disks-poller.h
src/job-drive-standby.c [new file with mode: 0644]
src/org.freedesktop.DeviceKit.Disks.Device.xml
src/org.freedesktop.DeviceKit.Disks.xml
tools/devkit-disks-bash-completion.sh
tools/devkit-disks.c
tools/umount-devkit.c

index d903336..3dba0e4 100644 (file)
         </para></listitem>
       </varlistentry>
       <varlistentry>
+        <term><option>ID_DRIVE_CAN_SPINDOWN</option></term>
+        <listitem><para> Whether the device can spin down. It is only meaningful
+            to set this to 0 (to avoid marking a device as being capable of spinning down)
+            since the code for spinning down the device is part of DeviceKit-disks itself.
+            If this property is not set, a heuristic will be used to determine if the
+            drive can spin down (currently only ATA devices, including those USB
+            devices with a SAT layer) can be spun down).
+        </para></listitem>
+      </varlistentry>
+      <varlistentry>
         <term><option>ID_DRIVE_FLASH</option></term>
         <listitem><para>The device is compatible with flash.
         </para></listitem>
index c55b2df..aa38687 100644 (file)
       </varlistentry>
 
       <varlistentry>
+        <term>
+          <option>--set-spindown</option>
+          <arg choice="plain"><replaceable>device_file</replaceable></arg>
+          <option>--spindown-timeout</option>
+          <arg choice="plain"><replaceable>seconds</replaceable></arg>
+          <arg><option>-- program arg ...</option></arg>
+        </term>
+        <listitem>
+          <para>
+            Configures disk spindown timeout on <replaceable>device_file</replaceable> to
+            <replaceable>seconds</replaceable>.
+            See <xref linkend="devkit-disks-spindown"/> for important information before using this option.
+          </para>
+          <para>
+            If no program is given, the spindown time will be used until Ctrl+C is pressed. Otherwise the program is
+            spawned and the the spindown timeout will only be used until the program terminates.
+          </para>
+        </listitem>
+      </varlistentry>
+
+      <varlistentry>
+        <term>
+          <option>--set-spindown-all</option>
+          <option>--spindown-timeout</option>
+          <arg choice="plain"><replaceable>seconds</replaceable></arg>
+          <arg><option>-- program arg ...</option></arg>
+        </term>
+        <listitem>
+          <para>
+            Configures disk spindown timeout on all disks that can be spun down to <replaceable>seconds</replaceable>.
+            See <xref linkend="devkit-disks-spindown"/> for important information before using this option.
+          </para>
+          <para>
+            If no program is given, the spindown time will be used until Ctrl+C is pressed. Otherwise the program is
+            spawned and the the spindown timeout will only be used until the program terminates.
+          </para>
+        </listitem>
+      </varlistentry>
+
+      <varlistentry>
         <term><option>--help</option></term>
         <listitem>
           <para>
     </variablelist>
   </refsect1>
 
+  <refsect1 id="devkit-disks-spindown"><title>SPINNING DOWN DISKS</title>
+    <para>
+      Caution should be exercised when configuring disk spin down.
+    </para>
+    <para>
+      Note that every time a disk is spun down,
+      the <quote>start-stop-count</quote> ATA SMART attribute will
+      increase by 1 and most disks are only good for a limited number
+      (typically 50,000 but it varies by manufacturer and model). In
+      addition, the drive may take as long as 30 seconds to respond to
+      subsequent disk access – most drives are typically faster, see
+      the <quote>spin-up-time</quote> ATA SMART attribute.
+    </para>
+    <para>
+      For maximum compatibility (since disks from WD and others won't
+      spin down using the <quote>STANDBY</quote> ATA command),
+      <citerefentry>
+        <refentrytitle>devkit-disks-daemon</refentrytitle><manvolnum>8</manvolnum>
+      </citerefentry>
+      does all timeout handling on the host side. If a device appears
+      to be idle (this is determined by looking
+      at <filename>/sys/block/sdX/stat</filename> from time to time)
+      for the requested amount of time, the daemon will spin the disk
+      down using the <quote>STANDBY IMMEDIATE</quote> ATA COMMAND or
+      similar.
+    </para>
+    <para>
+      On the other hand, cautious use (e.g. using conservative
+      timeouts) of the ability to spin down disks, can be a good way
+      to trade power consumption (typically 8 vs 1 Watts for 3.5&#34;
+      drives) and heat emission for a slightly higher latency.
+    </para>
+  </refsect1>
+
   <refsect1><title>AUTHOR</title>
     <para>
       Written by David Zeuthen <email>david@fubar.dk</email> with
index 0227342..446fe7a 100644 (file)
     </defaults>
   </action>
 
+  <action id="org.freedesktop.devicekit.disks.drive-set-spindown">
+    <_description>Set drive spindown timeout</_description>
+    <_message>Authentication is required to configure drive spindown timeout</_message>
+    <defaults>
+      <allow_any>no</allow_any>
+      <allow_inactive>no</allow_inactive>
+      <allow_active>yes</allow_active>
+    </defaults>
+  </action>
+
 </policyconfig>
index 1a604d2..0e905ef 100644 (file)
@@ -95,6 +95,7 @@ libexec_PROGRAMS += devkit-disks-helper-mkfs                          \
                    devkit-disks-helper-ata-smart-selftest              \
                    devkit-disks-helper-drive-detach                    \
                    devkit-disks-helper-linux-md-check                  \
+                   devkit-disks-helper-drive-standby                   \
                    $(NULL)
 
 libexec_SCRIPTS = devkit-disks-helper-change-luks-password
@@ -147,6 +148,10 @@ devkit_disks_helper_linux_md_check_SOURCES = job-shared.h job-linux-md-check.c
 devkit_disks_helper_linux_md_check_CPPFLAGS = $(AM_CPPFLAGS)
 devkit_disks_helper_linux_md_check_LDADD = $(GLIB_LIBS)
 
+devkit_disks_helper_drive_standby_SOURCES = job-shared.h job-drive-standby.c
+devkit_disks_helper_drive_standby_CPPFLAGS = $(AM_CPPFLAGS) $(SGUTILS_CFLAGS)
+devkit_disks_helper_drive_standby_LDADD = $(SGUTILS_LIBS) $(GLIB_LIBS)
+
 # TODO: move to udev
 udevhelperdir = $(slashlibdir)/udev
 udevhelper_PROGRAMS = devkit-disks-part-id devkit-disks-dm-export devkit-disks-probe-ata-smart
index ac098f2..4873955 100644 (file)
@@ -36,6 +36,9 @@
 /* delete entries older than five days */
 #define ATA_SMART_KEEP_ENTRIES_SECONDS (5*24*60*60)
 
+/* the poll frequency for IO activity when clients wants to spin down drives */
+#define SPINDOWN_POLL_FREQ_SECONDS 5
+
 /* ---------------------------------------------------------------------------------------------------- */
 
 #include <stdlib.h>
@@ -125,6 +128,9 @@ struct DevkitDisksDaemonPrivate
         GList *polling_inhibitors;
 
         GList *inhibitors;
+
+        guint spindown_timeout_id;
+        GList *spindown_inhibitors;
 };
 
 static void     devkit_disks_daemon_class_init  (DevkitDisksDaemonClass *klass);
@@ -601,6 +607,10 @@ devkit_disks_daemon_finalize (GObject *object)
                 g_source_remove (daemon->priv->ata_smart_refresh_timer_id);
         }
 
+        if (daemon->priv->spindown_timeout_id > 0) {
+                g_source_remove (daemon->priv->spindown_timeout_id);
+        }
+
         for (l = daemon->priv->polling_inhibitors; l != NULL; l = l->next) {
                 DevkitDisksInhibitor *inhibitor = DEVKIT_DISKS_INHIBITOR (l->data);
                 g_signal_handlers_disconnect_by_func (inhibitor, daemon_polling_inhibitor_disconnected_cb, daemon);
@@ -657,6 +667,7 @@ device_changed (DevkitDisksDaemon *daemon, GUdevDevice *d, gboolean synthesized)
                 } else {
                         g_print ("**** CHANGED %s\n", native_path);
                         devkit_disks_daemon_local_update_poller (daemon);
+                        devkit_disks_daemon_local_update_spindown (daemon);
                 }
         } else {
                 g_print ("**** TREATING CHANGE AS ADD %s\n", native_path);
@@ -722,6 +733,7 @@ device_add (DevkitDisksDaemon *daemon, GUdevDevice *d, gboolean emit_event)
                                 g_signal_emit (daemon, signals[DEVICE_ADDED_SIGNAL], 0, object_path);
                         }
                         devkit_disks_daemon_local_update_poller (daemon);
+                        devkit_disks_daemon_local_update_spindown (daemon);
                 } else {
                         g_print ("**** IGNORING ADD %s\n", native_path);
                 }
@@ -761,6 +773,7 @@ device_remove (DevkitDisksDaemon *daemon, GUdevDevice *d)
                 g_object_unref (device);
 
                 devkit_disks_daemon_local_update_poller (daemon);
+                devkit_disks_daemon_local_update_spindown (daemon);
         }
 }
 
@@ -1435,6 +1448,207 @@ devkit_disks_daemon_local_check_auth (DevkitDisksDaemon            *daemon,
 }
 
 /*--------------------------------------------------------------------------------------------------------------*/
+
+#define SYSFS_BLOCK_STAT_MAX_SIZE 256
+
+static void
+issue_standby_child_watch_cb (GPid pid, int status, gpointer user_data)
+{
+        DevkitDisksDevice *device = DEVKIT_DISKS_DEVICE (user_data);
+
+        if (WIFEXITED (status) && WEXITSTATUS (status) == 0) {
+                //g_print ("**** NOTE: standby helper for %s completed successfully\n", device->priv->device_file);
+        } else {
+                g_warning ("standby helper for %s failed with exit code %d (if_exited=%d)\n",
+                           device->priv->device_file,
+                           WEXITSTATUS (status),
+                           WIFEXITED (status));
+        }
+
+        g_object_unref (device);
+}
+
+static void
+issue_standby_to_drive (DevkitDisksDevice *device)
+{
+        GError *error;
+        GPid pid;
+        gchar *argv[3] = {PACKAGE_LIBEXEC_DIR "/devkit-disks-helper-drive-standby",
+                          NULL, /* device_file */
+                          NULL};
+
+        argv[1] = device->priv->device_file;
+
+        error = NULL;
+        if (!g_spawn_async_with_pipes (NULL,
+                                       argv,
+                                       NULL,
+                                       G_SPAWN_SEARCH_PATH | G_SPAWN_DO_NOT_REAP_CHILD,
+                                       NULL,
+                                       NULL,
+                                       &pid,
+                                       NULL,
+                                       NULL,
+                                       NULL,
+                                       &error)) {
+                g_warning ("Error launching %s: %s", argv[0], error->message);
+                g_error_free (error);
+                goto out;
+        }
+
+        g_child_watch_add (pid, issue_standby_child_watch_cb, g_object_ref (device));
+
+ out:
+        ;
+}
+
+static gboolean
+on_spindown_timeout (gpointer data)
+{
+        DevkitDisksDaemon *daemon = DEVKIT_DISKS_DAEMON (data);
+        GHashTableIter hash_iter;
+        DevkitDisksDevice *device;
+        gchar stat_file_path[PATH_MAX];
+        gchar buf[SYSFS_BLOCK_STAT_MAX_SIZE];
+        int fd;
+        time_t now;
+
+        now = time (NULL);
+
+        /* avoid allocating memory since this happens on a timer, e.g. all the FRAKKING time */
+
+        g_hash_table_iter_init (&hash_iter, daemon->priv->map_object_path_to_device);
+        while (g_hash_table_iter_next (&hash_iter, NULL, (gpointer) &device)) {
+
+                if (!device->priv->device_is_drive ||
+                    !device->priv->drive_can_spindown ||
+                    device->priv->spindown_timeout == 0)
+                        continue;
+
+                g_snprintf (stat_file_path,
+                            sizeof stat_file_path,
+                            "%s/stat",
+                            device->priv->native_path);
+                fd = open (stat_file_path, O_RDONLY);
+                if (fd == -1) {
+                        g_warning ("Error opening %s: %m", stat_file_path);
+                } else {
+                        ssize_t bytes_read;
+                        bytes_read = read (fd, buf, sizeof buf - 1);
+                        close (fd);
+                        if (bytes_read < 0) {
+                                g_warning ("Error reading from %s: %m", stat_file_path);
+                        } else {
+                                buf[bytes_read] = '\0';
+                                if (device->priv->spindown_last_stat == NULL) {
+                                        /* handle initial time this is set */
+                                        device->priv->spindown_last_stat = g_new0 (gchar, SYSFS_BLOCK_STAT_MAX_SIZE);
+                                        memcpy (device->priv->spindown_last_stat, buf, bytes_read + 1);
+                                        device->priv->spindown_last_stat_time = now;
+                                        device->priv->spindown_have_issued_standby = FALSE;
+                                } else {
+                                        if (g_strcmp0 (buf, device->priv->spindown_last_stat) == 0) {
+                                                gint64 idle_secs;
+
+                                                idle_secs = now - device->priv->spindown_last_stat_time;
+                                                /* same */
+                                                if (idle_secs > device->priv->spindown_timeout &&
+                                                    !device->priv->spindown_have_issued_standby) {
+                                                        device->priv->spindown_have_issued_standby = TRUE;
+                                                        g_print ("*** NOTE: issuing STANDBY IMMEDIATE for %s\n",
+                                                                 device->priv->device_file);
+
+                                                        /* and, now, DO IT! */
+                                                        issue_standby_to_drive (device);
+                                                }
+                                        } else {
+                                                /* differ */
+                                                memcpy (device->priv->spindown_last_stat, buf, bytes_read + 1);
+                                                device->priv->spindown_last_stat_time = now;
+                                                device->priv->spindown_have_issued_standby = FALSE;
+                                                //g_print ("*** NOTE: resetting spindown timeout on %s due "
+                                                //         "to IO activity\n",
+                                                //         device->priv->device_file);
+                                        }
+                                }
+                        }
+                }
+
+        }
+
+        /* keep timeout */
+        return TRUE;
+}
+
+void
+devkit_disks_daemon_local_update_spindown (DevkitDisksDaemon *daemon)
+{
+        GHashTableIter hash_iter;
+        DevkitDisksDevice *device;
+        GList *l;
+        gboolean watch_for_spindown;
+
+        watch_for_spindown = FALSE;
+        g_hash_table_iter_init (&hash_iter, daemon->priv->map_object_path_to_device);
+        while (g_hash_table_iter_next (&hash_iter, NULL, (gpointer) &device)) {
+                gint spindown_timeout;
+
+                if (!device->priv->device_is_drive || !device->priv->drive_can_spindown)
+                        continue;
+
+                spindown_timeout = G_MAXINT;
+                if (device->priv->spindown_inhibitors == NULL && daemon->priv->spindown_inhibitors == NULL) {
+                        /* no inhibitors */
+                        device->priv->spindown_timeout = 0;
+                        g_free (device->priv->spindown_last_stat);
+                        device->priv->spindown_last_stat = NULL;
+                        device->priv->spindown_last_stat_time = 0;
+                        device->priv->spindown_have_issued_standby = FALSE;
+                } else {
+                        /* first go through all inhibitors on the device */
+                        for (l = device->priv->spindown_inhibitors; l != NULL; l = l->next) {
+                                DevkitDisksInhibitor *inhibitor = DEVKIT_DISKS_INHIBITOR (l->data);
+                                gint spindown_timeout_inhibitor;
+
+                                spindown_timeout_inhibitor = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (inhibitor),
+                                                                                                 "spindown-timeout-seconds"));
+
+                                if (spindown_timeout_inhibitor < spindown_timeout)
+                                        spindown_timeout = spindown_timeout_inhibitor;
+                        }
+
+                        /* the all inhibitors on the daemon */
+                        for (l = daemon->priv->spindown_inhibitors; l != NULL; l = l->next) {
+                                DevkitDisksInhibitor *inhibitor = DEVKIT_DISKS_INHIBITOR (l->data);
+                                gint spindown_timeout_inhibitor;
+
+                                spindown_timeout_inhibitor = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (inhibitor),
+                                                                                                 "spindown-timeout-seconds"));
+
+                                if (spindown_timeout_inhibitor < spindown_timeout)
+                                        spindown_timeout = spindown_timeout_inhibitor;
+                        }
+
+                        device->priv->spindown_timeout = spindown_timeout;
+                        watch_for_spindown = TRUE;
+                }
+        }
+
+        if (watch_for_spindown) {
+                if (daemon->priv->spindown_timeout_id == 0) {
+                        daemon->priv->spindown_timeout_id = g_timeout_add_seconds (SPINDOWN_POLL_FREQ_SECONDS,
+                                                                                   on_spindown_timeout,
+                                                                                   daemon);
+                }
+        } else {
+                if (daemon->priv->spindown_timeout_id > 0) {
+                        g_source_remove (daemon->priv->spindown_timeout_id);
+                        daemon->priv->spindown_timeout_id = 0;
+                }
+        }
+}
+
+/*--------------------------------------------------------------------------------------------------------------*/
 /* exported methods */
 
 static void
@@ -1758,3 +1972,126 @@ devkit_disks_daemon_uninhibit (DevkitDisksDaemon     *daemon,
 }
 
 /*--------------------------------------------------------------------------------------------------------------*/
+
+
+static void
+daemon_spindown_inhibitor_disconnected_cb (DevkitDisksInhibitor *inhibitor,
+                                           DevkitDisksDaemon     *daemon)
+{
+        daemon->priv->spindown_inhibitors = g_list_remove (daemon->priv->spindown_inhibitors, inhibitor);
+        g_signal_handlers_disconnect_by_func (inhibitor, daemon_spindown_inhibitor_disconnected_cb, daemon);
+        g_object_unref (inhibitor);
+
+        devkit_disks_daemon_local_update_spindown (daemon);
+}
+
+static void
+devkit_disks_daemon_drive_set_all_spindown_timeouts_authorized_cb (DevkitDisksDaemon     *daemon,
+                                                                   DevkitDisksDevice     *device,
+                                                                   DBusGMethodInvocation *context,
+                                                                   const gchar           *action_id,
+                                                                   guint                  num_user_data,
+                                                                   gpointer              *user_data_elements)
+{
+        gint timeout_seconds = GPOINTER_TO_INT (user_data_elements[0]);
+        gchar **options = user_data_elements[1];
+        DevkitDisksInhibitor *inhibitor;
+        guint n;
+
+        if (timeout_seconds < 1) {
+                throw_error (context, DEVKIT_DISKS_ERROR_FAILED,
+                             "Timeout seconds must be at least 1");
+                goto out;
+        }
+
+        for (n = 0; options[n] != NULL; n++) {
+                const char *option = options[n];
+                throw_error (context,
+                             DEVKIT_DISKS_ERROR_INVALID_OPTION,
+                             "Unknown option %s", option);
+                goto out;
+        }
+
+        inhibitor = devkit_disks_inhibitor_new (context);
+
+        g_object_set_data (G_OBJECT (inhibitor), "spindown-timeout-seconds", GINT_TO_POINTER (timeout_seconds));
+
+        daemon->priv->spindown_inhibitors = g_list_prepend (daemon->priv->spindown_inhibitors, inhibitor);
+        g_signal_connect (inhibitor, "disconnected", G_CALLBACK (daemon_spindown_inhibitor_disconnected_cb), daemon);
+
+        devkit_disks_daemon_local_update_spindown (daemon);
+
+        dbus_g_method_return (context, devkit_disks_inhibitor_get_cookie (inhibitor));
+
+out:
+        ;
+}
+
+gboolean
+devkit_disks_daemon_drive_set_all_spindown_timeouts (DevkitDisksDaemon     *daemon,
+                                                     int                    timeout_seconds,
+                                                     char                 **options,
+                                                     DBusGMethodInvocation *context)
+{
+        if (timeout_seconds < 1) {
+                throw_error (context, DEVKIT_DISKS_ERROR_FAILED,
+                             "Timeout seconds must be at least 1");
+                goto out;
+        }
+
+        devkit_disks_daemon_local_check_auth (daemon,
+                                              NULL,
+                                              "org.freedesktop.devicekit.disks.drive-set-spindown",
+                                              "DriveSetAllSpindownTimeouts",
+                                              devkit_disks_daemon_drive_set_all_spindown_timeouts_authorized_cb,
+                                              context,
+                                              2,
+                                              GINT_TO_POINTER (timeout_seconds), NULL,
+                                              g_strdupv (options), g_strfreev);
+
+
+ out:
+        return TRUE;
+}
+
+gboolean
+devkit_disks_daemon_drive_unset_all_spindown_timeouts (DevkitDisksDaemon     *daemon,
+                                                       char                  *cookie,
+                                                       DBusGMethodInvocation *context)
+{
+        const gchar *sender;
+        DevkitDisksInhibitor *inhibitor;
+        GList *l;
+
+        sender = dbus_g_method_get_sender (context);
+
+        inhibitor = NULL;
+        for (l = daemon->priv->spindown_inhibitors; l != NULL; l = l->next) {
+                DevkitDisksInhibitor *i = DEVKIT_DISKS_INHIBITOR (l->data);
+
+                if (g_strcmp0 (devkit_disks_inhibitor_get_unique_dbus_name (i), sender) == 0 &&
+                    g_strcmp0 (devkit_disks_inhibitor_get_cookie (i), cookie) == 0) {
+                        inhibitor = i;
+                        break;
+                }
+        }
+
+        if (inhibitor == NULL) {
+                throw_error (context,
+                             DEVKIT_DISKS_ERROR_FAILED,
+                             "No such spindown configurator");
+                goto out;
+        }
+
+        daemon->priv->spindown_inhibitors = g_list_remove (daemon->priv->spindown_inhibitors, inhibitor);
+        g_object_unref (inhibitor);
+
+        devkit_disks_daemon_local_update_spindown (daemon);
+
+        dbus_g_method_return (context);
+
+ out:
+        return TRUE;
+}
+
+/*--------------------------------------------------------------------------------------------------------------*/
index 687a662..0ef00dd 100644 (file)
@@ -121,6 +121,8 @@ void               devkit_disks_daemon_local_synthesize_changed  (DevkitDisksDae
 
 void               devkit_disks_daemon_local_update_poller       (DevkitDisksDaemon       *daemon);
 
+void               devkit_disks_daemon_local_update_spindown     (DevkitDisksDaemon       *daemon);
+
 gboolean           devkit_disks_daemon_local_has_polling_inhibitors (DevkitDisksDaemon       *daemon);
 
 gboolean           devkit_disks_daemon_local_has_inhibitors (DevkitDisksDaemon       *daemon);
@@ -184,6 +186,15 @@ gboolean devkit_disks_daemon_uninhibit (DevkitDisksDaemon     *daemon,
                                         char                  *cookie,
                                         DBusGMethodInvocation *context);
 
+gboolean devkit_disks_daemon_drive_set_all_spindown_timeouts (DevkitDisksDaemon     *daemon,
+                                                              int                    timeout_seconds,
+                                                              char                 **options,
+                                                              DBusGMethodInvocation *context);
+
+gboolean devkit_disks_daemon_drive_unset_all_spindown_timeouts (DevkitDisksDaemon     *daemon,
+                                                                char                  *cookie,
+                                                                DBusGMethodInvocation *context);
+
 G_END_DECLS
 
 #endif /* __DEVKIT_DISKS_DAEMON_H__ */
index d78c1b2..351d817 100644 (file)
@@ -733,6 +733,16 @@ devkit_disks_device_set_drive_can_detach (DevkitDisksDevice *device, gboolean va
 }
 
 void
+devkit_disks_device_set_drive_can_spindown (DevkitDisksDevice *device, gboolean value)
+{
+  if (G_UNLIKELY (device->priv->drive_can_spindown != value))
+    {
+      device->priv->drive_can_spindown = value;
+      emit_changed (device, "drive_can_spindown");
+    }
+}
+
+void
 devkit_disks_device_set_optical_disc_is_blank (DevkitDisksDevice *device, gboolean value)
 {
   if (G_UNLIKELY (device->priv->optical_disc_is_blank != value))
index acfdfbf..7706910 100644 (file)
@@ -87,9 +87,18 @@ struct DevkitDisksDevicePrivate
         /* A list of current polling inhibitors (DevkitDisksInhibitor objects) */
         GList *polling_inhibitors;
 
+        /* A list of current spindown configurators (DevkitDisksInhibitor objects) */
+        GList *spindown_inhibitors;
+
         /* if non-zero, the id of the idle for emitting a 'change' signal */
         guint emit_changed_idle_id;
 
+        /* used for spindown */
+        gint spindown_timeout;
+        gchar *spindown_last_stat;
+        time_t spindown_last_stat_time;
+        gboolean spindown_have_issued_standby;
+
         /**************/
         /* properties */
         /*************/
@@ -152,6 +161,7 @@ struct DevkitDisksDevicePrivate
         char *drive_media;
         gboolean drive_is_media_ejectable;
         gboolean drive_can_detach;
+        gboolean drive_can_spindown;
 
         gboolean optical_disc_is_blank;
         gboolean optical_disc_is_appendable;
@@ -282,6 +292,7 @@ void devkit_disks_device_set_drive_media_compatibility (DevkitDisksDevice *devic
 void devkit_disks_device_set_drive_media (DevkitDisksDevice *device, const gchar *value);
 void devkit_disks_device_set_drive_is_media_ejectable (DevkitDisksDevice *device, gboolean value);
 void devkit_disks_device_set_drive_can_detach (DevkitDisksDevice *device, gboolean value);
+void devkit_disks_device_set_drive_can_spindown (DevkitDisksDevice *device, gboolean value);
 
 void devkit_disks_device_set_optical_disc_is_blank (DevkitDisksDevice *device, gboolean value);
 void devkit_disks_device_set_optical_disc_is_appendable (DevkitDisksDevice *device, gboolean value);
index 6c787a6..4594f31 100644 (file)
@@ -70,6 +70,9 @@ static void     devkit_disks_device_finalize    (GObject     *object);
 static void     polling_inhibitor_disconnected_cb (DevkitDisksInhibitor *inhibitor,
                                                    DevkitDisksDevice   *device);
 
+static void     spindown_inhibitor_disconnected_cb (DevkitDisksInhibitor *inhibitor,
+                                                    DevkitDisksDevice   *device);
+
 static gboolean update_info                (DevkitDisksDevice *device);
 
 static void     drain_pending_changes (DevkitDisksDevice *device, gboolean force_update);
@@ -206,6 +209,7 @@ enum
         PROP_DRIVE_MEDIA,
         PROP_DRIVE_IS_MEDIA_EJECTABLE,
         PROP_DRIVE_CAN_DETACH,
+        PROP_DRIVE_CAN_SPINDOWN,
 
         PROP_OPTICAL_DISC_IS_BLANK,
         PROP_OPTICAL_DISC_IS_APPENDABLE,
@@ -512,6 +516,9 @@ get_property (GObject         *object,
        case PROP_DRIVE_CAN_DETACH:
                g_value_set_boolean (value, device->priv->drive_can_detach);
                break;
+       case PROP_DRIVE_CAN_SPINDOWN:
+               g_value_set_boolean (value, device->priv->drive_can_spindown);
+               break;
 
        case PROP_OPTICAL_DISC_IS_BLANK:
                g_value_set_boolean (value, device->priv->optical_disc_is_blank);
@@ -982,6 +989,10 @@ devkit_disks_device_class_init (DevkitDisksDeviceClass *klass)
                 object_class,
                 PROP_DRIVE_CAN_DETACH,
                 g_param_spec_boolean ("drive-can-detach", NULL, NULL, FALSE, G_PARAM_READABLE));
+        g_object_class_install_property (
+                object_class,
+                PROP_DRIVE_CAN_SPINDOWN,
+                g_param_spec_boolean ("drive-can-spindown", NULL, NULL, FALSE, G_PARAM_READABLE));
 
         g_object_class_install_property (
                 object_class,
@@ -1227,12 +1238,21 @@ devkit_disks_device_finalize (GObject *object)
         }
         g_list_free (device->priv->polling_inhibitors);
 
+        for (l = device->priv->spindown_inhibitors; l != NULL; l = l->next) {
+                DevkitDisksInhibitor *inhibitor = DEVKIT_DISKS_INHIBITOR (l->data);
+                g_signal_handlers_disconnect_by_func (inhibitor, spindown_inhibitor_disconnected_cb, device);
+                g_object_unref (inhibitor);
+        }
+        g_list_free (device->priv->spindown_inhibitors);
+
         if (device->priv->linux_md_poll_timeout_id > 0)
                 g_source_remove (device->priv->linux_md_poll_timeout_id);
 
         if (device->priv->emit_changed_idle_id > 0)
                 g_source_remove (device->priv->emit_changed_idle_id);
 
+        g_free (device->priv->spindown_last_stat);
+
         /* free properties */
         g_free (device->priv->device_file);
         g_ptr_array_foreach (device->priv->device_file_by_id, (GFunc) g_free, NULL);
@@ -2159,6 +2179,34 @@ update_info_drive (DevkitDisksDevice *device)
 
 /* ---------------------------------------------------------------------------------------------------- */
 
+/* update drive_can_spindown property */
+static gboolean
+update_info_drive_can_spindown (DevkitDisksDevice *device)
+{
+        gboolean drive_can_spindown;
+
+        /* Right now we only know how to spin down ATA devices (including those USB devices
+         * that can do ATA SMART)
+         *
+         * This would probably also work for SCSI devices (since the helper is doing SCSI
+         * STOP (which translated in libata to ATA's STANDBY IMMEDIATE) - but that needs
+         * testing...
+         */
+        drive_can_spindown = FALSE;
+        if (g_strcmp0 (device->priv->drive_connection_interface, "ata") == 0 ||
+            device->priv->drive_ata_smart_is_available) {
+                drive_can_spindown = TRUE;
+        }
+        if (g_udev_device_has_property (device->priv->d, "ID_DRIVE_CAN_SPINDOWN")) {
+                drive_can_spindown = g_udev_device_get_property_as_boolean (device->priv->d, "ID_DRIVE_CAN_SPINDOWN");
+        }
+        devkit_disks_device_set_drive_can_spindown (device, drive_can_spindown);
+
+        return TRUE;
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
 /* update device_is_optical_disc and optical_disc_* properties */
 static gboolean
 update_info_optical_disc (DevkitDisksDevice *device)
@@ -3176,6 +3224,10 @@ update_info (DevkitDisksDevice *device)
         if (!update_info_drive_ata_smart (device))
                 goto out;
 
+        /* drive_can_spindown property */
+        if (!update_info_drive_can_spindown (device))
+                goto out;
+
         /* device_is_system_internal property */
         if (!update_info_is_system_internal (device))
                 goto out;
@@ -9495,3 +9547,160 @@ devkit_disks_device_drive_poll_media (DevkitDisksDevice     *device,
  out:
         return TRUE;
 }
+
+/*--------------------------------------------------------------------------------------------------------------*/
+
+static void
+spindown_inhibitor_disconnected_cb (DevkitDisksInhibitor *inhibitor,
+                                    DevkitDisksDevice   *device)
+{
+        device->priv->spindown_inhibitors = g_list_remove (device->priv->spindown_inhibitors, inhibitor);
+        g_signal_handlers_disconnect_by_func (inhibitor, spindown_inhibitor_disconnected_cb, device);
+        g_object_unref (inhibitor);
+
+        update_info (device);
+        drain_pending_changes (device, FALSE);
+        devkit_disks_daemon_local_update_spindown (device->priv->daemon);
+}
+
+static void
+devkit_disks_device_drive_set_spindown_timeout_authorized_cb (DevkitDisksDaemon     *daemon,
+                                                              DevkitDisksDevice     *device,
+                                                              DBusGMethodInvocation *context,
+                                                              const gchar           *action_id,
+                                                              guint                  num_user_data,
+                                                              gpointer              *user_data_elements)
+{
+        gint timeout_seconds = GPOINTER_TO_INT (user_data_elements[0]);
+        gchar **options = user_data_elements[1];
+        DevkitDisksInhibitor *inhibitor;
+        guint n;
+
+        if (!device->priv->device_is_drive) {
+                throw_error (context, DEVKIT_DISKS_ERROR_FAILED,
+                             "Device is not a drive");
+                goto out;
+        }
+
+        if (!device->priv->drive_can_spindown) {
+                throw_error (context, DEVKIT_DISKS_ERROR_FAILED,
+                             "Cannot spindown device");
+                goto out;
+        }
+
+        if (timeout_seconds < 1) {
+                throw_error (context, DEVKIT_DISKS_ERROR_FAILED,
+                             "Timeout seconds must be at least 1");
+                goto out;
+        }
+
+        for (n = 0; options[n] != NULL; n++) {
+                const char *option = options[n];
+                throw_error (context,
+                             DEVKIT_DISKS_ERROR_INVALID_OPTION,
+                             "Unknown option %s", option);
+                goto out;
+        }
+
+        inhibitor = devkit_disks_inhibitor_new (context);
+
+        g_object_set_data (G_OBJECT (inhibitor), "spindown-timeout-seconds", GINT_TO_POINTER (timeout_seconds));
+
+        device->priv->spindown_inhibitors = g_list_prepend (device->priv->spindown_inhibitors, inhibitor);
+        g_signal_connect (inhibitor, "disconnected", G_CALLBACK (spindown_inhibitor_disconnected_cb), device);
+
+        update_info (device);
+        drain_pending_changes (device, FALSE);
+        devkit_disks_daemon_local_update_spindown (device->priv->daemon);
+
+        dbus_g_method_return (context, devkit_disks_inhibitor_get_cookie (inhibitor));
+
+out:
+        ;
+}
+
+gboolean
+devkit_disks_device_drive_set_spindown_timeout (DevkitDisksDevice     *device,
+                                                int                    timeout_seconds,
+                                                char                 **options,
+                                                DBusGMethodInvocation *context)
+{
+        if (!device->priv->device_is_drive) {
+                throw_error (context, DEVKIT_DISKS_ERROR_FAILED,
+                             "Device is not a drive");
+                goto out;
+        }
+
+        if (!device->priv->drive_can_spindown) {
+                throw_error (context, DEVKIT_DISKS_ERROR_FAILED,
+                             "Cannot spindown device");
+                goto out;
+        }
+
+        if (timeout_seconds < 1) {
+                throw_error (context, DEVKIT_DISKS_ERROR_FAILED,
+                             "Timeout seconds must be at least 1");
+                goto out;
+        }
+
+        devkit_disks_daemon_local_check_auth (device->priv->daemon,
+                                              device,
+                                              "org.freedesktop.devicekit.disks.drive-set-spindown",
+                                              "DriveSetSpindownTimeout",
+                                              devkit_disks_device_drive_set_spindown_timeout_authorized_cb,
+                                              context,
+                                              2,
+                                              GINT_TO_POINTER (timeout_seconds), NULL,
+                                              g_strdupv (options), g_strfreev);
+
+
+ out:
+        return TRUE;
+}
+
+
+/*--------------------------------------------------------------------------------------------------------------*/
+
+gboolean
+devkit_disks_device_drive_unset_spindown_timeout (DevkitDisksDevice     *device,
+                                                  char                  *cookie,
+                                                  DBusGMethodInvocation *context)
+{
+        const gchar *sender;
+        DevkitDisksInhibitor *inhibitor;
+        GList *l;
+
+        sender = dbus_g_method_get_sender (context);
+
+        inhibitor = NULL;
+        for (l = device->priv->spindown_inhibitors; l != NULL; l = l->next) {
+                DevkitDisksInhibitor *i = DEVKIT_DISKS_INHIBITOR (l->data);
+
+                if (g_strcmp0 (devkit_disks_inhibitor_get_unique_dbus_name (i), sender) == 0 &&
+                    g_strcmp0 (devkit_disks_inhibitor_get_cookie (i), cookie) == 0) {
+                        inhibitor = i;
+                        break;
+                }
+        }
+
+        if (inhibitor == NULL) {
+                throw_error (context,
+                             DEVKIT_DISKS_ERROR_FAILED,
+                             "No such spindown configurator");
+                goto out;
+        }
+
+        device->priv->spindown_inhibitors = g_list_remove (device->priv->spindown_inhibitors, inhibitor);
+        g_object_unref (inhibitor);
+
+        update_info (device);
+        drain_pending_changes (device, FALSE);
+        devkit_disks_daemon_local_update_spindown (device->priv->daemon);
+
+        dbus_g_method_return (context);
+
+ out:
+        return TRUE;
+}
+
+/*--------------------------------------------------------------------------------------------------------------*/
index 2700f69..4bf3393 100644 (file)
@@ -191,6 +191,15 @@ gboolean devkit_disks_device_drive_detach (DevkitDisksDevice     *device,
                                            char                 **options,
                                            DBusGMethodInvocation *context);
 
+gboolean devkit_disks_device_drive_set_spindown_timeout (DevkitDisksDevice     *device,
+                                                         int                    timeout_seconds,
+                                                         char                 **options,
+                                                         DBusGMethodInvocation *context);
+
+gboolean devkit_disks_device_drive_unset_spindown_timeout (DevkitDisksDevice     *device,
+                                                           char                  *cookie,
+                                                           DBusGMethodInvocation *context);
+
 G_END_DECLS
 
 #endif /* __DEVKIT_DISKS_DEVICE_H__ */
index fe89ad3..b2049b9 100644 (file)
@@ -41,6 +41,8 @@ static char **argv_buffer = NULL;
 static size_t argv_size = 0;
 #endif
 
+#define POLL_SHOW_DEBUG
+
 static void
 set_proc_title_init (int argc, char *argv[])
 {
@@ -110,7 +112,9 @@ devkit_disks_poller_poll_device (const gchar *device_file)
         /* the device file is the canonical device file from udev */
         is_cdrom = (g_str_has_prefix (device_file, "/dev/sr") || g_str_has_prefix (device_file, "/dev/scd"));
 
-        //g_debug ("polling '%s'", device_file);
+#ifdef POLL_SHOW_DEBUG
+        g_print ("**** POLLER (%d): polling %s\n", getpid (), device_file);
+#endif
 
         if (is_cdrom) {
                 /* optical drives need special care
@@ -167,7 +171,7 @@ poller_have_data (GIOChannel    *channel,
                 exit (1);
         }
 
- again:
+read_more:
         status = g_io_channel_read_line (channel,
                                          &line,
                                          &line_length,
@@ -178,20 +182,21 @@ poller_have_data (GIOChannel    *channel,
                 g_error_free (error);
                 goto out;
         }
-        if (status == G_IO_STATUS_AGAIN) {
-                goto again;
+        if (status == G_IO_STATUS_EOF || status == G_IO_STATUS_AGAIN) {
+                goto out;
         }
 
-        //g_debug ("polling process read '%s'", line);
-
-        if (line[line_length - 1] == '\n')
-                line[line_length - 1] = '\0';
-
-        if (line[line_length - 2] == ' ')
-                line[line_length - 2] = '\0';
+        g_strstrip (line);
 
-        g_strfreev (poller_devices_to_poll);
-        poller_devices_to_poll = g_strsplit (line, " ", 0);
+#ifdef POLL_SHOW_DEBUG
+        g_print ("**** POLLER (%d): polling process read '%s'\n", getpid (), line);
+#endif
+        if (g_str_has_prefix (line, "set-poll:")) {
+                g_strfreev (poller_devices_to_poll);
+                poller_devices_to_poll = g_strsplit (line + strlen ("set-poll:"), " ", 0);
+        } else {
+                g_printerr ("**** POLLER (%d): unknown command '%s'\n", getpid (), line);
+        }
 
         if (g_strv_length (poller_devices_to_poll) == 0) {
                 if (poller_timeout_id > 0) {
@@ -209,6 +214,7 @@ poller_have_data (GIOChannel    *channel,
         }
 
         g_free (line);
+        goto read_more;
 
  out:
         /* keep the IOChannel around */
@@ -283,6 +289,7 @@ devkit_disks_poller_set_devices (GList *devices)
         GList *l;
         gchar **device_array;
         guint n;
+        gchar *joined;
         gchar *devices_to_poll;
         static gchar *devices_currently_polled = NULL;
 
@@ -298,16 +305,22 @@ devkit_disks_poller_set_devices (GList *devices)
 
         device_array[n] = "\n";
 
-        devices_to_poll = g_strjoinv (" ", device_array);
+        joined = g_strjoinv (" ", device_array);
         g_free (device_array);
+        devices_to_poll = g_strconcat ("set-poll:",
+                                       joined,
+                                       NULL);
+        g_free (joined);
 
         /* only poke the polling process if the list of currently polled devices change */
         if (g_strcmp0 (devices_to_poll, devices_currently_polled) != 0) {
                 g_free (devices_currently_polled);
                 devices_currently_polled = devices_to_poll;
 
+#ifdef POLL_SHOW_DEBUG
+                g_print ("**** POLLER (%d): Sending poll command: '%s'\n", getpid (), devices_currently_polled);
+#endif
                 write (poller_daemon_write_end_fd, devices_currently_polled, strlen (devices_currently_polled));
-                //g_debug ("Want to poll: '%s'", devices_currently_polled);
         } else {
                 g_free (devices_to_poll);
         }
index 9d80a38..110ded4 100644 (file)
@@ -29,7 +29,4 @@ gboolean devkit_disks_poller_setup (int argc, char *argv[]);
 void     devkit_disks_poller_set_devices (GList *devices);
 void     devkit_disks_poller_poll_device (const gchar *device_file);
 
-/* ATA smart polling */
-void     devkit_disks_poller_set_ata_smart_devices (GList *devices);
-
 #endif /* __DEVKIT_DISKS_POLLER_H */
diff --git a/src/job-drive-standby.c b/src/job-drive-standby.c
new file mode 100644 (file)
index 0000000..56997f9
--- /dev/null
@@ -0,0 +1,103 @@
+/* -*- Mode: C; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ *
+ * Copyright (C) 2009 David Zeuthen <david@fubar.dk>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+ *
+ */
+
+#include <stdio.h>
+#include <string.h>
+#include <errno.h>
+#include <stdlib.h>
+#include <time.h>
+
+#include <scsi/sg_lib.h>
+#include <scsi/sg_cmds.h>
+
+#include <glib.h>
+
+static void
+usage (void)
+{
+  g_printerr ("incorrect usage\n");
+}
+
+int
+main (int argc, char *argv[])
+{
+  int rc;
+  int ret;
+  int sg_fd;
+  const gchar *device;
+
+  ret = 1;
+  sg_fd = -1;
+
+  if (argc != 2)
+    {
+      usage ();
+      goto out;
+    }
+
+  device = argv[1];
+
+  sg_fd = sg_cmds_open_device (device, 1 /* read_only */, 1);
+  if (sg_fd < 0)
+    {
+      g_printerr ("Cannot open %s: %m\n", device);
+      goto out;
+    }
+
+  if ((rc = sg_ll_sync_cache_10 (sg_fd,
+                                 0,  /* sync_nv */
+                                 0,  /* immed */
+                                 0,  /* group */
+                                 0,  /* lba */
+                                 0,  /* count */
+                                 1,  /* noisy */
+                                 0   /* verbose */
+                                 )) != 0)
+    {
+      g_printerr ("Error SYNCHRONIZE CACHE for %s: %s\n", device, safe_strerror (rc));
+      /* this is not a catastrophe, carry on */
+    }
+
+  if (sg_ll_start_stop_unit (sg_fd,
+                             0,  /* immed */
+                             0,  /* pc_mod__fl_num */
+                             0,  /* power_cond */
+                             0,  /* noflush__fl */
+                             0,  /* loej */
+                             0,  /* start */
+                             1,  /* noisy */
+                             0   /* verbose */
+                             ) != 0)
+    {
+      g_printerr ("Error STOP UNIT for %s: %s\n", device, safe_strerror (rc));
+      goto out;
+    }
+
+  /* OK, close the device */
+  sg_cmds_close_device (sg_fd);
+  sg_fd = -1;
+
+  ret = 0;
+
+ out:
+  if (sg_fd > 0)
+    sg_cmds_close_device (sg_fd);
+  return ret;
+}
index bd4585c..73e4361 100644 (file)
 
     <!-- ************************************************************ -->
 
+    <method name="DriveSetSpindownTimeout">
+      <annotation name="org.freedesktop.DBus.GLib.Async" value=""/>
+      <arg name="timeout_seconds" direction="in" type="i">
+        <doc:doc><doc:summary>
+            Number of seconds before the drive should be spun down.
+        </doc:summary></doc:doc>
+      </arg>
+
+      <arg name="options" direction="in" type="as">
+        <doc:doc><doc:summary>
+            Options related to setting spindown timeout. Currently no options are recognized.
+        </doc:summary></doc:doc>
+      </arg>
+
+      <arg name="cookie" direction="out" type="s">
+        <doc:doc><doc:summary>
+            A cookie that can be used in the
+            <doc:ref type="method" to="Device.DriveUnsetSpindownTimeout">DriveUnsetSpindownTimeout()</doc:ref> method
+            to unset the spindown timeout of the device.
+        </doc:summary></doc:doc>
+      </arg>
+
+      <doc:doc>
+        <doc:description>
+          <doc:para>
+            Configures spindown timeout for the drive.
+            Check the
+            <doc:ref type="property" to="Device:drive-can-spindown">drive-can-spindown</doc:ref>
+            property before attempting to invoke this method.
+            Caution should be exercised when using this method, see
+            the SPINNING DOWN DISKS section in the
+            devkit-disks<doc:tt>(1)</doc:tt> man page before using it.
+          </doc:para>
+        </doc:description>
+        <doc:permission>
+          The caller will need one of the following PolicyKit authorizations:
+          <doc:list>
+            <doc:item>
+              <doc:term>org.freedesktop.devicekit.disks.drive-set-spindown</doc:term>
+              <doc:definition>To set spindown timeouts</doc:definition>
+            </doc:item>
+          </doc:list>
+        </doc:permission>
+        <doc:errors>
+          <doc:error name="&ERROR_NOT_AUTHORIZED;">if the caller lacks the appropriate PolicyKit authorization</doc:error>
+          <doc:error name="&ERROR_FAILED;">if the operation failed</doc:error>
+          <doc:error name="&ERROR_INVALID_OPTION;">if an invalid or malformed option was given</doc:error>
+        </doc:errors>
+      </doc:doc>
+    </method>
+
+    <!-- ************************************************************ -->
+
+    <method name="DriveUnsetSpindownTimeout">
+      <annotation name="org.freedesktop.DBus.GLib.Async" value=""/>
+
+      <arg name="cookie" direction="in" type="s">
+        <doc:doc><doc:summary>
+            A cookie obtained from the
+            <doc:ref type="method" to="Device.DriveSetSpindownTimeout">DriveSetSpindownTimeout()</doc:ref> method.
+        </doc:summary></doc:doc>
+      </arg>
+
+      <doc:doc>
+        <doc:description>
+          <doc:para>
+            Unsets spindown timeout for the drive.
+          </doc:para>
+        </doc:description>
+        <doc:permission>
+          The caller will need one of the following PolicyKit authorizations:
+          <doc:list>
+            <doc:item>
+              <doc:term>org.freedesktop.devicekit.disks.drive-set-spindown</doc:term>
+              <doc:definition>To set spindown timeouts</doc:definition>
+            </doc:item>
+          </doc:list>
+        </doc:permission>
+        <doc:errors>
+          <doc:error name="&ERROR_NOT_AUTHORIZED;">if the caller lacks the appropriate PolicyKit authorization</doc:error>
+          <doc:error name="&ERROR_FAILED;">if the operation failed</doc:error>
+        </doc:errors>
+      </doc:doc>
+    </method>
+
+    <!-- ************************************************************ -->
+
     <method name="DriveAtaSmartRefreshData">
       <annotation name="org.freedesktop.DBus.GLib.Async" value=""/>
       <arg name="options" direction="in" type="as">
             is TRUE.
       </doc:para></doc:description></doc:doc>
     </property>
+    <property name="drive-can-spindown" type="b" access="read">
+      <doc:doc><doc:description><doc:para>
+            TRUE only if the drive is capable of being put into
+            a standby mode (typically powering down the spindle motor).
+            This property is only valid if
+            <doc:ref type="property" to="Device:device-is-drive">device-is-drive</doc:ref>
+            is TRUE.
+      </doc:para></doc:description></doc:doc>
+    </property>
 
     <property name="optical-disc-is-blank" type="b" access="read">
       <doc:doc><doc:description><doc:para>
index ba635ff..780d738 100644 (file)
 
     <!-- ************************************************************ -->
 
+    <method name="DriveSetAllSpindownTimeouts">
+      <annotation name="org.freedesktop.DBus.GLib.Async" value=""/>
+      <arg name="timeout_seconds" direction="in" type="i">
+        <doc:doc><doc:summary>
+            Number of seconds before drives should be spun down.
+        </doc:summary></doc:doc>
+      </arg>
+
+      <arg name="options" direction="in" type="as">
+        <doc:doc><doc:summary>
+            Options related to setting spindown timeouts. Currently no options are recognized.
+        </doc:summary></doc:doc>
+      </arg>
+
+      <arg name="cookie" direction="out" type="s">
+        <doc:doc><doc:summary>
+            A cookie that can be used in the
+            <doc:ref type="method" to="Drive.DriveUnsetAllSpindownTimeouts">DriveUnsetAllSpindownTimeouts()</doc:ref> method
+            to unset the spindown timeout for drives.
+        </doc:summary></doc:doc>
+      </arg>
+
+      <doc:doc>
+        <doc:description>
+          <doc:para>
+            Configures spindown timeout for all drives capable of being spun down.
+            Caution should be exercised when using this method, see
+            the SPINNING DOWN DISKS section in the
+            devkit-disks<doc:tt>(1)</doc:tt> man page before using it.
+          </doc:para>
+        </doc:description>
+        <doc:permission>
+          The caller will need one of the following PolicyKit authorizations:
+          <doc:list>
+            <doc:item>
+              <doc:term>org.freedesktop.devicekit.disks.drive-set-spindown</doc:term>
+              <doc:definition>To set spindown timeouts</doc:definition>
+            </doc:item>
+          </doc:list>
+        </doc:permission>
+        <doc:errors>
+          <doc:error name="&ERROR_NOT_AUTHORIZED;">if the caller lacks the appropriate PolicyKit authorization</doc:error>
+          <doc:error name="&ERROR_FAILED;">if the operation failed</doc:error>
+          <doc:error name="&ERROR_INVALID_OPTION;">if an invalid or malformed option was given</doc:error>
+        </doc:errors>
+      </doc:doc>
+    </method>
+
+    <!-- ************************************************************ -->
+
+    <method name="DriveUnsetAllSpindownTimeouts">
+      <annotation name="org.freedesktop.DBus.GLib.Async" value=""/>
+
+      <arg name="cookie" direction="in" type="s">
+        <doc:doc><doc:summary>
+            A cookie obtained from the
+            <doc:ref type="method" to="Device.DriveSetSpindownTimeout">DriveSetSpindownTimeout()</doc:ref> method.
+        </doc:summary></doc:doc>
+      </arg>
+
+      <doc:doc>
+        <doc:description>
+          <doc:para>
+            Unsets spindown timeout for the drive.
+          </doc:para>
+        </doc:description>
+        <doc:permission>
+          The caller will need one of the following PolicyKit authorizations:
+          <doc:list>
+            <doc:item>
+              <doc:term>org.freedesktop.devicekit.disks.drive-set-spindown</doc:term>
+              <doc:definition>To set spindown timeouts</doc:definition>
+            </doc:item>
+          </doc:list>
+        </doc:permission>
+        <doc:errors>
+          <doc:error name="&ERROR_NOT_AUTHORIZED;">if the caller lacks the appropriate PolicyKit authorization</doc:error>
+          <doc:error name="&ERROR_FAILED;">if the operation failed</doc:error>
+        </doc:errors>
+      </doc:doc>
+    </method>
+
+    <!-- ************************************************************ -->
+
     <method name="LinuxMdStart">
       <annotation name="org.freedesktop.DBus.GLib.Async" value=""/>
       <arg name="components" direction="in" type="ao">
index 662501e..57af088 100644 (file)
@@ -22,8 +22,10 @@ __devkit_disks() {
         COMPREPLY=($(compgen -W "$(devkit-disks --enumerate-device-files)" -- $cur))
     elif [ "${COMP_WORDS[$(($COMP_CWORD - 1))]}" = "--ata-smart-simulate" ] ; then
         _filedir || return 0
+    elif [ "${COMP_WORDS[$(($COMP_CWORD - 1))]}" = "--set-spindown" ] ; then
+        COMPREPLY=($(compgen -W "$(devkit-disks --enumerate-device-files)" -- $cur))
     else
-        COMPREPLY=($(IFS=: compgen -W "--dump:--inhibit-polling:--inhibit-all-polling:--enumerate:--enumerate-device-files:--monitor:--monitor-detail:--show-info:--help:--mount:--mount-fstype:--mount-options:--unmount:--unmount-options:--detach:--detach-options:--ata-smart-refresh:--ata-smart-wakeup:--ata-smart-simulate" -- $cur))
+        COMPREPLY=($(IFS=: compgen -W "--dump:--inhibit-polling:--inhibit-all-polling:--enumerate:--enumerate-device-files:--monitor:--monitor-detail:--show-info:--help:--mount:--mount-fstype:--mount-options:--unmount:--unmount-options:--detach:--detach-options:--ata-smart-refresh:--ata-smart-wakeup:--ata-smart-simulate:--set-spindown:--set-spindown-all:--spindown-timeout" -- $cur))
     fi
 }
 
index 836b6e0..5b23520 100644 (file)
@@ -60,6 +60,9 @@ static char         *opt_show_info              = NULL;
 static char         *opt_inhibit_polling        = NULL;
 static gboolean      opt_inhibit                = FALSE;
 static gboolean      opt_inhibit_all_polling    = FALSE;
+static char         *opt_drive_spindown         = NULL;
+static gboolean      opt_drive_spindown_all     = FALSE;
+static gint          opt_spindown_seconds       = 0;
 static char         *opt_mount                  = NULL;
 static char         *opt_mount_fstype           = NULL;
 static char         *opt_mount_options          = NULL;
@@ -382,6 +385,7 @@ typedef struct
         char    *drive_media;
         gboolean drive_is_media_ejectable;
         gboolean drive_can_detach;
+        gboolean drive_can_spindown;
 
         gboolean optical_disc_is_blank;
         gboolean optical_disc_is_appendable;
@@ -579,6 +583,8 @@ collect_props (const char *key, const GValue *value, DeviceProperties *props)
                 props->drive_is_media_ejectable = g_value_get_boolean (value);
         else if (strcmp (key, "drive-can-detach") == 0)
                 props->drive_can_detach = g_value_get_boolean (value);
+        else if (strcmp (key, "drive-can-spindown") == 0)
+                props->drive_can_spindown = g_value_get_boolean (value);
 
         else if (strcmp (key, "optical-disc-is-blank") == 0)
                 props->optical_disc_is_blank = g_value_get_boolean (value);
@@ -1089,6 +1095,7 @@ do_show_info (const char *object_path)
                 g_print ("    revision:              %s\n", props->drive_revision);
                 g_print ("    serial:                %s\n", props->drive_serial);
                 g_print ("    detachable:            %d\n", props->drive_can_detach);
+                g_print ("    can spindown:          %d\n", props->drive_can_spindown);
                 g_print ("    ejectable:             %d\n", props->drive_is_media_ejectable);
                 g_print ("    media:                 %s\n", props->drive_media);
                 g_print ("      compat:             ");
@@ -1438,6 +1445,155 @@ out:
 /* ---------------------------------------------------------------------------------------------------- */
 
 static gint
+do_set_spindown (const char *object_path,
+                 gint         argc,
+                 gchar       *argv[])
+{
+        char *cookie;
+        DBusGProxy *proxy;
+        GError *error;
+        char **options;
+        gint ret;
+
+        options = NULL;
+        cookie = NULL;
+        ret = 127;
+
+       if (argc > 0 && strcmp (argv[0], "--") == 0) {
+               argv++;
+               argc--;
+       }
+
+       proxy = dbus_g_proxy_new_for_name (bus,
+                                           "org.freedesktop.DeviceKit.Disks",
+                                           object_path,
+                                           "org.freedesktop.DeviceKit.Disks.Device");
+
+        error = NULL;
+        if (!org_freedesktop_DeviceKit_Disks_Device_drive_set_spindown_timeout (proxy,
+                                                                                opt_spindown_seconds,
+                                                                                (const char **) options,
+                                                                                &cookie,
+                                                                                &error)) {
+                g_print ("Setting spindown failed: %s\n", error->message);
+                g_error_free (error);
+                goto out;
+        }
+
+        if (argc == 0) {
+                g_print ("Set spindown on %s to %d seconds. Press Ctrl+C to exit.\n",
+                         object_path, opt_spindown_seconds);
+                while (TRUE)
+                        sleep (100000000);
+        } else {
+                GError *error;
+                gint exit_status;
+
+                error = NULL;
+                if (!g_spawn_sync (NULL,  /* working dir */
+                                   argv,
+                                   NULL,  /* envp */
+                                   G_SPAWN_SEARCH_PATH,
+                                   NULL, /* child_setup */
+                                   NULL, /* user_data */
+                                   NULL, /* standard_output */
+                                   NULL, /* standard_error */
+                                   &exit_status, /* exit_status */
+                                   &error)) {
+                        g_printerr ("Error launching program: %s\n", error->message);
+                        g_error_free (error);
+                        ret = 126;
+                        goto out;
+                }
+
+                if (WIFEXITED (exit_status))
+                        ret = WEXITSTATUS (exit_status);
+                else
+                        ret = 125;
+        }
+
+out:
+        g_free (cookie);
+        return ret;
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static gint
+do_set_spindown_all (gint         argc,
+                     gchar       *argv[])
+{
+        char *cookie;
+        DBusGProxy *proxy;
+        GError *error;
+        char **options;
+        gint ret;
+
+        options = NULL;
+        cookie = NULL;
+        ret = 127;
+
+       if (argc > 0 && strcmp (argv[0], "--") == 0) {
+               argv++;
+               argc--;
+       }
+
+       proxy = dbus_g_proxy_new_for_name (bus,
+                                           "org.freedesktop.DeviceKit.Disks",
+                                           "/org/freedesktop/DeviceKit/Disks",
+                                           "org.freedesktop.DeviceKit.Disks");
+
+        error = NULL;
+        if (!org_freedesktop_DeviceKit_Disks_drive_set_all_spindown_timeouts (proxy,
+                                                                              opt_spindown_seconds,
+                                                                              (const char **) options,
+                                                                              &cookie,
+                                                                              &error)) {
+                g_print ("Setting spindown failed: %s\n", error->message);
+                g_error_free (error);
+                goto out;
+        }
+
+        if (argc == 0) {
+                g_print ("Set spindown for all drives to %d seconds. Press Ctrl+C to exit.\n",
+                         opt_spindown_seconds);
+                while (TRUE)
+                        sleep (100000000);
+        } else {
+                GError *error;
+                gint exit_status;
+
+                error = NULL;
+                if (!g_spawn_sync (NULL,  /* working dir */
+                                   argv,
+                                   NULL,  /* envp */
+                                   G_SPAWN_SEARCH_PATH,
+                                   NULL, /* child_setup */
+                                   NULL, /* user_data */
+                                   NULL, /* standard_output */
+                                   NULL, /* standard_error */
+                                   &exit_status, /* exit_status */
+                                   &error)) {
+                        g_printerr ("Error launching program: %s\n", error->message);
+                        g_error_free (error);
+                        ret = 126;
+                        goto out;
+                }
+
+                if (WIFEXITED (exit_status))
+                        ret = WEXITSTATUS (exit_status);
+                else
+                        ret = 125;
+        }
+
+out:
+        g_free (cookie);
+        return ret;
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static gint
 do_inhibit (gint         argc,
             gchar       *argv[])
 {
@@ -1574,6 +1730,9 @@ main (int argc, char **argv)
                 { "show-info", 0, 0, G_OPTION_ARG_STRING, &opt_show_info, "Show information about a device file", NULL },
                 { "inhibit-polling", 0, 0, G_OPTION_ARG_STRING, &opt_inhibit_polling, "Inhibit polling", NULL },
                 { "inhibit-all-polling", 0, 0, G_OPTION_ARG_NONE, &opt_inhibit_all_polling, "Inhibit all polling", NULL },
+                { "set-spindown", 0, 0, G_OPTION_ARG_STRING, &opt_drive_spindown, "Set spindown timeout for drive", NULL },
+                { "set-spindown-all", 0, 0, G_OPTION_ARG_NONE, &opt_drive_spindown_all, "Set spindown timeout for all drives", NULL },
+                { "spindown-timeout", 0, 0, G_OPTION_ARG_INT, &opt_spindown_seconds, "Spindown timeout in seconds", NULL },
                 { "inhibit", 0, 0, G_OPTION_ARG_NONE, &opt_inhibit, "Inhibit the daemon", NULL },
 
                 { "mount", 0, 0, G_OPTION_ARG_STRING, &opt_mount, "Mount the given device", NULL },
@@ -1697,6 +1856,15 @@ main (int argc, char **argv)
         } else if (opt_inhibit_all_polling) {
                 ret = do_inhibit_all_polling (argc - 1, argv + 1);
                 goto out;
+        } else if (opt_drive_spindown != NULL) {
+                device_file = device_file_to_object_path (opt_drive_spindown);
+                if (device_file == NULL)
+                        goto out;
+                ret = do_set_spindown (device_file, argc - 1, argv + 1);
+                goto out;
+        } else if (opt_drive_spindown_all) {
+                ret = do_set_spindown_all (argc - 1, argv + 1);
+                goto out;
         } else if (opt_inhibit) {
                 ret = do_inhibit (argc - 1, argv + 1);
                 goto out;
index 730f26f..74c6cf9 100644 (file)
@@ -63,7 +63,6 @@ do_unmount (const char *object_path,
                                            object_path,
                                            "org.freedesktop.DeviceKit.Disks.Device");
 
-try_again:
         error = NULL;
         if (!org_freedesktop_DeviceKit_Disks_Device_filesystem_unmount (proxy,
                                                                         (const char **) unmount_options,