AC_SUBST(ZLIB_CFLAGS)
AC_SUBST(ZLIB_LIBS)
+have_sgutils="false"
+AC_CHECK_LIB([sgutils2], [sg_ll_inquiry], have_sgutils="true")
+if test x$have_sgutils != "xtrue"; then
+ AC_MSG_ERROR([libsgutils2 is needed])
+fi
+SGUTILS_CFLAGS=""
+SGUTILS_LIBS="-lsgutils2"
+AC_SUBST(SGUTILS_CFLAGS)
+AC_SUBST(SGUTILS_LIBS)
+
+
PKG_CHECK_MODULES(SQLITE3, [sqlite3])
AC_SUBST(SQLITE3_CFLAGS)
AC_SUBST(SQLITE3_LIBS)
<varlistentry>
<term>
+ <option>--detach</option>
+ <arg choice="plain"><replaceable>device_file</replaceable></arg>
+ <arg><option>--detach-options</option><arg choice="plain"><replaceable>options</replaceable></arg></arg>
+ </term>
+ <listitem>
+ <para>
+ Detaches (e.g. powering down the physical port the device
+ is connected to) the device represented
+ by <replaceable>device_file</replaceable> using a
+ comma-separated list
+ of <replaceable>options</replaceable>.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term>
<option>--ata-smart-refresh</option>
<arg choice="plain"><replaceable>device_file</replaceable></arg>
<arg><option>--ata-smart-wakeup</option></arg>
</action>
<action id="org.freedesktop.devicekit.disks.drive-eject">
- <_description>Eject a device</_description>
+ <_description>Eject media from a device</_description>
<_message>Authentication is required to eject media from the device</_message>
<defaults>
<allow_any>no</allow_any>
</defaults>
</action>
+ <action id="org.freedesktop.devicekit.disks.drive-detach">
+ <_description>Detach a drive</_description>
+ <_message>Authentication is required to detach the drive</_message>
+ <defaults>
+ <allow_any>no</allow_any>
+ <allow_inactive>no</allow_inactive>
+ <allow_active>yes</allow_active>
+ </defaults>
+ </action>
+
<action id="org.freedesktop.devicekit.disks.change">
<_description>Modify a device</_description>
<_message>Authentication is required to modify the device</_message>
devkit-disks-helper-fstab-mounter \
devkit-disks-helper-ata-smart-collect \
devkit-disks-helper-ata-smart-selftest \
+ devkit-disks-helper-drive-detach \
$(NULL)
libexec_SCRIPTS = devkit-disks-helper-change-luks-password
devkit_disks_helper_fstab_mounter_CPPFLAGS = $(AM_CPPFLAGS)
devkit_disks_helper_fstab_mounter_LDADD = $(GLIB_LIBS)
+devkit_disks_helper_drive_detach_SOURCES = job-shared.h job-drive-detach.c
+devkit_disks_helper_drive_detach_CPPFLAGS = $(AM_CPPFLAGS) $(LIBUDEV_CFLAGS) $(SGUTILS_CFLAGS)
+devkit_disks_helper_drive_detach_LDADD = $(LIBUDEV_LIBS) $(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
}
void
+devkit_disks_device_set_drive_can_detach (DevkitDisksDevice *device, gboolean value)
+{
+ if (G_UNLIKELY (device->priv->drive_can_detach != value))
+ {
+ device->priv->drive_can_detach = value;
+ emit_changed (device, "drive_can_detach");
+ }
+}
+
+void
devkit_disks_device_set_optical_disc_is_blank (DevkitDisksDevice *device, gboolean value)
{
if (G_UNLIKELY (device->priv->optical_disc_is_blank != value))
char *drive_media;
gboolean drive_is_media_ejectable;
gboolean drive_requires_eject;
+ gboolean drive_can_detach;
gboolean optical_disc_is_blank;
gboolean optical_disc_is_appendable;
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_requires_eject (DevkitDisksDevice *device, gboolean value);
+void devkit_disks_device_set_drive_can_detach (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);
PROP_DRIVE_MEDIA,
PROP_DRIVE_IS_MEDIA_EJECTABLE,
PROP_DRIVE_REQUIRES_EJECT,
+ PROP_DRIVE_CAN_DETACH,
PROP_OPTICAL_DISC_IS_BLANK,
PROP_OPTICAL_DISC_IS_APPENDABLE,
case PROP_DRIVE_REQUIRES_EJECT:
g_value_set_boolean (value, device->priv->drive_requires_eject);
break;
+ case PROP_DRIVE_CAN_DETACH:
+ g_value_set_boolean (value, device->priv->drive_can_detach);
+ break;
case PROP_OPTICAL_DISC_IS_BLANK:
g_value_set_boolean (value, device->priv->optical_disc_is_blank);
object_class,
PROP_DRIVE_REQUIRES_EJECT,
g_param_spec_boolean ("drive-requires-eject", NULL, NULL, FALSE, G_PARAM_READABLE));
+ g_object_class_install_property (
+ 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,
const gchar *media_in_drive;
gboolean drive_is_ejectable;
gboolean drive_requires_eject;
+ gboolean drive_can_detach;
gchar *decoded_string;
guint n;
g_ptr_array_free (media_compat_array, TRUE);
+ /* right now, we only offer to detach USB devices */
+ drive_can_detach = FALSE;
+ if (g_strcmp0 (device->priv->drive_connection_interface, "usb") == 0) {
+ drive_can_detach = TRUE;
+ }
+ devkit_disks_device_set_drive_can_detach (device, drive_can_detach);
+
return TRUE;
}
/*--------------------------------------------------------------------------------------------------------------*/
static void
+drive_detach_completed_cb (DBusGMethodInvocation *context,
+ DevkitDisksDevice *device,
+ gboolean job_was_cancelled,
+ int status,
+ const char *stderr,
+ const char *stdout,
+ gpointer user_data)
+{
+ if (WEXITSTATUS (status) == 0 && !job_was_cancelled) {
+ /* TODO: probably wait for has_media to change to FALSE */
+ dbus_g_method_return (context);
+ } else {
+ if (job_was_cancelled) {
+ throw_error (context,
+ DEVKIT_DISKS_ERROR_CANCELLED,
+ "Job was cancelled");
+ } else {
+ throw_error (context,
+ DEVKIT_DISKS_ERROR_FAILED,
+ "Error detaching: helper exited with exit code %d: %s",
+ WEXITSTATUS (status),
+ stderr);
+ }
+ }
+}
+
+static void
+devkit_disks_device_drive_detach_authorized_cb (DevkitDisksDaemon *daemon,
+ DevkitDisksDevice *device,
+ DBusGMethodInvocation *context,
+ const gchar *action_id,
+ guint num_user_data,
+ gpointer *user_data_elements)
+{
+ gchar **options = user_data_elements[0];
+ int n;
+ char *argv[16];
+ GError *error;
+ char *mount_path;
+
+ error = NULL;
+ mount_path = NULL;
+
+ if (!device->priv->device_is_drive) {
+ throw_error (context, DEVKIT_DISKS_ERROR_FAILED,
+ "Device is not a drive");
+ goto out;
+ }
+
+ if (!device->priv->device_is_media_available) {
+ throw_error (context, DEVKIT_DISKS_ERROR_FAILED,
+ "No media in drive");
+ goto out;
+ }
+
+ if (devkit_disks_device_local_is_busy (device, TRUE, &error)) {
+ dbus_g_method_return_error (context, error);
+ g_error_free (error);
+ 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;
+ }
+
+ n = 0;
+ argv[n++] = PACKAGE_LIBEXEC_DIR "/devkit-disks-helper-drive-detach";
+ argv[n++] = device->priv->device_file;
+ argv[n++] = device->priv->native_path;
+ argv[n++] = NULL;
+
+ if (!job_new (context,
+ "DriveDetach",
+ FALSE,
+ device,
+ argv,
+ NULL,
+ drive_detach_completed_cb,
+ NULL,
+ NULL)) {
+ goto out;
+ }
+
+out:
+ ;
+}
+
+gboolean
+devkit_disks_device_drive_detach (DevkitDisksDevice *device,
+ 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->device_is_media_available) {
+ throw_error (context, DEVKIT_DISKS_ERROR_FAILED,
+ "No media in drive");
+ goto out;
+ }
+
+ devkit_disks_daemon_local_check_auth (device->priv->daemon,
+ device,
+ "org.freedesktop.devicekit.disks.drive-detach",
+ "DriveDetach",
+ devkit_disks_device_drive_detach_authorized_cb,
+ context,
+ 1,
+ g_strdupv (options), g_strfreev);
+
+ out:
+ return TRUE;
+}
+
+/*--------------------------------------------------------------------------------------------------------------*/
+
+static void
filesystem_check_completed_cb (DBusGMethodInvocation *context,
DevkitDisksDevice *device,
gboolean job_was_cancelled,
gboolean devkit_disks_device_drive_poll_media (DevkitDisksDevice *device,
DBusGMethodInvocation *context);
+gboolean devkit_disks_device_drive_detach (DevkitDisksDevice *device,
+ char **options,
+ DBusGMethodInvocation *context);
+
G_END_DECLS
#endif /* __DEVKIT_DISKS_DEVICE_H__ */
--- /dev/null
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ *
+ * 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>
+
+#define LIBUDEV_I_KNOW_THE_API_IS_SUBJECT_TO_CHANGE
+#include <libudev.h>
+
+#include <glib.h>
+
+static void
+usage (void)
+{
+ g_printerr ("incorrect usage\n");
+}
+
+int
+main (int argc, char *argv[])
+{
+ int ret;
+ int sg_fd;
+ const gchar *device;
+ const gchar *sysfs_path;
+ struct udev *udev;
+ struct udev_device *udevice;
+ struct udev_device *udevice_usb;
+ gchar *unbind_path;
+ gchar *usb_interface_name;
+ size_t usb_interface_name_len;
+ FILE *f;
+
+ udev = NULL;
+ udevice = NULL;
+ udevice_usb = NULL;
+ usb_interface_name = NULL;
+ unbind_path = NULL;
+
+ ret = 1;
+ sg_fd = -1;
+
+ if (argc != 3)
+ {
+ usage ();
+ goto out;
+ }
+
+ device = argv[1];
+ sysfs_path = argv[2];
+
+ 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 (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: %m\n", device);
+ /* 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: %m\n", device);
+ goto out;
+ }
+
+ /* OK, close the device */
+ sg_cmds_close_device (sg_fd);
+ sg_fd = -1;
+
+ /* Now unbind the usb-storage driver from the usb interface */
+ udev = udev_new ();
+ if (udev == NULL)
+ {
+ g_printerr ("Error initializing libudev: %m\n");
+ goto out;
+ }
+
+ udevice = udev_device_new_from_syspath (udev, sysfs_path);
+ if (udevice == NULL)
+ {
+ g_printerr ("No udev device for %s: %m\n", sysfs_path);
+ goto out;
+ }
+
+ udevice_usb = udev_device_get_parent_with_subsystem_devtype (udevice, "usb", "usb_interface");
+ if (udevice_usb == NULL)
+ {
+ g_printerr ("No usb parent interface for %s: %m\n", sysfs_path);
+ goto out;
+ }
+
+ usb_interface_name = g_path_get_basename (udev_device_get_devpath (udevice_usb));
+ usb_interface_name_len = strlen (usb_interface_name);
+
+ unbind_path = g_strdup_printf ("%s/driver/unbind", udev_device_get_syspath (udevice_usb));
+ f = fopen (unbind_path, "w");
+ if (f == NULL)
+ {
+ g_printerr ("Cannot open %s for writing: %m\n", unbind_path);
+ goto out;
+ }
+ if (fwrite (usb_interface_name, sizeof (char), usb_interface_name_len, f) < usb_interface_name_len)
+ {
+ g_printerr ("Error writing %s to %s: %m\n", unbind_path, usb_interface_name);
+ fclose (f);
+ goto out;
+ }
+ fclose (f);
+
+ ret = 0;
+
+ out:
+ g_free (usb_interface_name);
+ g_free (unbind_path);
+ if (sg_fd > 0)
+ sg_cmds_close_device (sg_fd);
+ if (udevice != NULL)
+ udev_device_unref (udevice);
+ if (udev != NULL)
+ udev_unref (udev);
+ return ret;
+}
<!-- ************************************************************ -->
+ <method name="DriveDetach">
+ <annotation name="org.freedesktop.DBus.GLib.Async" value=""/>
+ <arg name="options" direction="in" type="as">
+ <doc:doc><doc:summary>Detach options. Currently no options are recognized.</doc:summary></doc:doc>
+ </arg>
+
+ <doc:doc>
+ <doc:description>
+ <doc:para>
+ Detachs the device by e.g. powering down the physical port
+ it is connected to. Note that not all devices or ports are
+ capable of this. Check the
+ <doc:ref type="property" to="Device:drive-can-detach">drive-can-detach</doc:ref>
+ property before attempting to invoke this method.
+ </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-detach</doc:term>
+ <doc:definition>To detach a device</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_BUSY;">if the device or a dependent device (e.g. partition or cleartext luks device) is busy (e.g. mounted)</doc:error>
+ <doc:error name="&ERROR_FAILED;">if the operation failed</doc:error>
+ <doc:error name="&ERROR_CANCELLED;">if the job was cancelled</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="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-detach" type="b" access="read">
+ <doc:doc><doc:description><doc:para>
+ TRUE only if the drive is capable of being detached by
+ e.g. powering down the port it is connected to.
+ 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>
COMPREPLY=($(compgen -W "$(devkit-disks --enumerate-device-files)" -- $cur))
elif [ "${COMP_WORDS[$(($COMP_CWORD - 1))]}" = "--unmount" ] ; then
COMPREPLY=($(compgen -W "$(devkit-disks --enumerate-device-files)" -- $cur))
+ elif [ "${COMP_WORDS[$(($COMP_CWORD - 1))]}" = "--detach" ] ; then
+ COMPREPLY=($(compgen -W "$(devkit-disks --enumerate-device-files)" -- $cur))
elif [ "${COMP_WORDS[$(($COMP_CWORD - 1))]}" = "--ata-smart-refresh" ] ; then
COMPREPLY=($(compgen -W "$(devkit-disks --enumerate-device-files)" -- $cur))
elif [ "${COMP_WORDS[$(($COMP_CWORD - 1))]}" = "--ata-smart-simulate" ] ; then
_filedir || return 0
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:--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" -- $cur))
fi
}
static char *opt_mount_options = NULL;
static char *opt_unmount = NULL;
static char *opt_unmount_options = NULL;
+static char *opt_detach = NULL;
+static char *opt_detach_options = NULL;
static char *opt_ata_smart_refresh = NULL;
static gboolean opt_ata_smart_wakeup = FALSE;
static char *opt_ata_smart_simulate = NULL;
}
static void
+do_detach (const char *object_path,
+ const char *options)
+{
+ DBusGProxy *proxy;
+ GError *error;
+ char **unmount_options;
+
+ unmount_options = NULL;
+ if (options != NULL)
+ unmount_options = g_strsplit (options, ",", 0);
+
+ 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_detach (proxy,
+ (const char **) options,
+ &error)) {
+ g_print ("Detach failed: %s\n", error->message);
+ g_error_free (error);
+ }
+
+ g_strfreev (unmount_options);
+}
+
+static void
device_added_signal_handler (DBusGProxy *proxy, const char *object_path, gpointer user_data)
{
g_print ("added: %s\n", object_path);
char *drive_media;
gboolean drive_is_media_ejectable;
gboolean drive_requires_eject;
+ gboolean drive_can_detach;
gboolean optical_disc_is_blank;
gboolean optical_disc_is_appendable;
props->drive_is_media_ejectable = g_value_get_boolean (value);
else if (strcmp (key, "drive-requires-eject") == 0)
props->drive_requires_eject = 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, "optical-disc-is-blank") == 0)
props->optical_disc_is_blank = g_value_get_boolean (value);
g_print (" model: %s\n", props->drive_model);
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 (" ejectable: %d\n", props->drive_is_media_ejectable);
g_print (" require eject: %d\n", props->drive_requires_eject);
g_print (" media: %s\n", props->drive_media);
{ "inhibit-all-polling", 0, 0, G_OPTION_ARG_NONE, &opt_inhibit_all_polling, "Inhibit all polling", 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 device given by the object path", NULL },
+ { "mount", 0, 0, G_OPTION_ARG_STRING, &opt_mount, "Mount the given device", NULL },
{ "mount-fstype", 0, 0, G_OPTION_ARG_STRING, &opt_mount_fstype, "Specify file system type", NULL },
{ "mount-options", 0, 0, G_OPTION_ARG_STRING, &opt_mount_options, "Mount options separated by comma", NULL },
- { "unmount", 0, 0, G_OPTION_ARG_STRING, &opt_unmount, "Unmount the device given by the object path", NULL },
+ { "unmount", 0, 0, G_OPTION_ARG_STRING, &opt_unmount, "Unmount the given device", NULL },
{ "unmount-options", 0, 0, G_OPTION_ARG_STRING, &opt_unmount_options, "Unmount options separated by comma", NULL },
+ { "detach", 0, 0, G_OPTION_ARG_STRING, &opt_detach, "Detach the given device", NULL },
+ { "detach-options", 0, 0, G_OPTION_ARG_STRING, &opt_detach_options, "Detach options separated by comma", NULL },
{ "ata-smart-refresh", 0, 0, G_OPTION_ARG_STRING, &opt_ata_smart_refresh, "Refresh ATA SMART data", NULL },
{ "ata-smart-wakeup", 0, 0, G_OPTION_ARG_NONE, &opt_ata_smart_wakeup, "Wake up the disk if it is not awake", NULL },
{ "ata-smart-simulate", 0, 0, G_OPTION_ARG_STRING, &opt_ata_smart_simulate, "Inject libatasmart BLOB for testing", NULL },
if (device_file == NULL)
goto out;
do_unmount (device_file, opt_unmount_options);
+ } else if (opt_detach != NULL) {
+ device_file = device_file_to_object_path (opt_detach);
+ if (device_file == NULL)
+ goto out;
+ do_detach (device_file, opt_detach_options);
} else if (opt_ata_smart_refresh != NULL) {
device_file = device_file_to_object_path (opt_ata_smart_refresh);
if (device_file == NULL)