summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDavid Zeuthen <davidz@redhat.com>2012-02-28 09:55:20 -0500
committerDavid Zeuthen <davidz@redhat.com>2012-02-28 09:55:20 -0500
commit7ecf57035d73c35619e06fa4cbb8334bdfe35630 (patch)
tree8a15f9830255c05f6be09a8405244fa058b49ab2
parent3b277903af128fb632897d8932fe300f216ead38 (diff)
Add iSCSI bits back
Signed-off-by: David Zeuthen <davidz@redhat.com>
-rw-r--r--data/org.freedesktop.UDisks2.xml138
-rw-r--r--data/org.freedesktop.udisks2.policy.in10
-rw-r--r--src/Makefile.am1
-rw-r--r--src/udisksdaemon.c24
-rw-r--r--src/udisksdaemon.h1
-rw-r--r--src/udisksdaemontypes.h3
-rw-r--r--src/udisksiscsiprovider.c1470
-rw-r--r--src/udisksiscsiprovider.h37
-rw-r--r--src/udiskslinuxdrive.c203
-rw-r--r--src/udiskslinuxprovider.c2
10 files changed, 1887 insertions, 2 deletions
diff --git a/data/org.freedesktop.UDisks2.xml b/data/org.freedesktop.UDisks2.xml
index 4e4cb4f..f11c7f0 100644
--- a/data/org.freedesktop.UDisks2.xml
+++ b/data/org.freedesktop.UDisks2.xml
@@ -252,6 +252,16 @@
-->
<property name="SortKey" type="s" access="read"/>
+ <!-- iSCSITarget:
+ If the drive does not belong to an iSCSI target, this
+ property is set to '/'. Otherwise, the property holds the
+ path to an object implementing the
+ #org.freedesktop.UDisks2.iSCSITarget interface.
+ -->
+ <property name="iSCSITarget" type="o" access="read">
+ <annotation name="org.gtk.GDBus.C.Name" value="iSCSI_Target"/>
+ </property>
+
<!--
Eject:
@options: Options (currently unused except for <link linkend="udisks-std-options">standard options</link>).
@@ -1356,4 +1366,132 @@
<!-- ********************************************************************** -->
+ <!-- org.freedesktop.UDisks2.iSCSISource:
+ @short_description: A source of iSCSI targets
+
+ A logical grouping of iSCSI targets - see the
+ #org.freedesktop.UDisks2.iSCSITarget:Source property on
+ each #org.freedesktop.UDisks2.iSCSITarget object.
+ -->
+ <interface name="org.freedesktop.UDisks2.iSCSISource">
+ <annotation name="org.gtk.GDBus.C.Name" value="iSCSI_Source"/>
+
+ <!--
+ Mechanism:
+ The mechanism used to collect the iSCSI targets in the source.
+
+ Known values include <literal>sendtargets</literal>.
+ <literal>isns</literal>, <literal>firmware<literal>
+ and <literal>static</literal>
+ -->
+ <property name="Mechanism" type="s" access="read"/>
+
+ <!--
+ DiscoveryAddress:
+ The address of the server in question - the exact format
+ depends on the value in the
+ #org.freedesktop.UDisks2.iSCSISource:Mechanism property.
+ -->
+ <property name="Address" type="s" access="read"/>
+ </interface>
+
+ <!-- org.freedesktop.UDisks2.iSCSITarget:
+ @short_description: Remote iSCSI Targets
+
+ This interface is used to represent remote iSCSI targets that
+ the system knows about.
+ -->
+ <interface name="org.freedesktop.UDisks2.iSCSITarget">
+ <annotation name="org.gtk.GDBus.C.Name" value="iSCSI_Target"/>
+
+ <!--
+ Source: The #org.freedesktop.UDisks2.iSCSISource that the
+ target stems from.
+ -->
+ <property name="Source" type="o" access="read"/>
+
+ <!-- Name: The name of the iSCSI target
+ Example target names include
+ <literal>iqn.2000-07.dk.fubar:storage.ma.burlington.scratch0</literal> or
+ <literal>eui.02004567A425678D</literal> or
+ <literal>naa.52004567BA64678D</literal>.
+ -->
+ <property name="Name" type="ay" access="read"/>
+
+ <!-- TODO: Include an Alias property cf. RFC 3721 2.2 -->
+
+ <!-- Connections:
+ The various connections to the target.
+
+ This is an array of items
+ <variablelist>
+ <varlistentry>
+ <term>host (type <literal>'s'</literal>)</term>
+ <listitem><para>The hostname or IP address of the portal to connect to</para></listitem>
+ </varlistentry>
+ <varlistentry>
+ <term>port (type <literal>'i'</literal>)</term>
+ <listitem><para>The TCP port number of the portal to connect to</para></listitem>
+ </varlistentry>
+ <varlistentry>
+ <term>tpgt (type <literal>'i'</literal>)</term>
+ <listitem><para>The Target Portal Group Tag to use for the connection</para></listitem>
+ </varlistentry>
+ <varlistentry>
+ <term>iface_name (type <literal>'s'</literal>)</term>
+ <listitem><para>The network interface to use for the connection</para></listitem>
+ </varlistentry>
+ <varlistentry>
+ <term>state (type <literal>'s'</literal>)</term>
+ <listitem><para>The current state of the connection. Known values include the empty string (if there is no connection), <literal>LOGGED_IN</literal> and <literal>FAILED</literal>.</para></listitem>
+ </varlistentry>
+ <varlistentry>
+ <term>expansion (type <literal>'a{sv}'</literal>)</term>
+ <listitem><para>Currently unused. Intended for future expansion.</para></listitem></varlistentry>
+ </varlistentry>
+ </variablelist>
+ -->
+ <property name="Connections" type="a(siissa{sv})" access="read"/>
+
+ <!--
+ Login:
+ @host: The hostname of the portal.
+ @port: The port of the portal.
+ @tpgt: The target portal group tag.
+ @iface_name: The interface to log into.
+ @options: Options (currently unused except for <link linkend="udisks-std-options">standard options</link>).
+
+ Logs into the target for the connection specified by @host,
+ @port, @tpgt and @iface.
+ -->
+ <method name="Login">
+ <arg name="host" direction="in" type="ay"/>
+ <arg name="port" direction="in" type="i"/>
+ <arg name="tpgt" direction="in" type="i"/>
+ <arg name="iface_name" direction="in" type="ay"/>
+ <arg name="options" direction="in" type="a{sv}"/>
+ </method>
+
+ <!--
+ Logout:
+ @host: The hostname of the portal.
+ @port: The port of the portal.
+ @tpgt: The target portal group tag.
+ @iface_name: The interface to log into.
+ @options: Options (currently unused except for <link linkend="udisks-std-options">standard options</link>).
+
+ Logs out of the target for the connection specified by @host,
+ @port, @tpgt and @iface_name.
+ -->
+ <method name="Logout">
+ <arg name="host" direction="in" type="ay"/>
+ <arg name="port" direction="in" type="i"/>
+ <arg name="tpgt" direction="in" type="i"/>
+ <arg name="iface_iname" direction="in" type="ay"/>
+ <arg name="options" direction="in" type="a{sv}"/>
+ </method>
+ </interface>
+
+ <!-- ********************************************************************** -->
+
</node>
diff --git a/data/org.freedesktop.udisks2.policy.in b/data/org.freedesktop.udisks2.policy.in
index 7e674b6..a7db2ae 100644
--- a/data/org.freedesktop.udisks2.policy.in
+++ b/data/org.freedesktop.udisks2.policy.in
@@ -235,4 +235,14 @@
</defaults>
</action>
+ <action id="org.freedesktop.udisks2.iscsi-initiator">
+ <_description>Configure iSCSI initiator settings</_description>
+ <_message>Authentication is required to configure iSCSI initiator settings</_message>
+ <defaults>
+ <allow_any>auth_admin</allow_any>
+ <allow_inactive>auth_admin</allow_inactive>
+ <allow_active>auth_admin_keep</allow_active>
+ </defaults>
+ </action>
+
</policyconfig>
diff --git a/src/Makefile.am b/src/Makefile.am
index 250ed48..60e7b85 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -75,6 +75,7 @@ libudisks_daemon_la_SOURCES = \
udisksfstabmonitor.h udisksfstabmonitor.c \
udiskscrypttabentry.h udiskscrypttabentry.c \
udiskscrypttabmonitor.h udiskscrypttabmonitor.c \
+ udisksiscsiprovider.h udisksiscsiprovider.c \
$(BUILT_SOURCES) \
$(NULL)
diff --git a/src/udisksdaemon.c b/src/udisksdaemon.c
index e2baf3e..582c65a 100644
--- a/src/udisksdaemon.c
+++ b/src/udisksdaemon.c
@@ -37,6 +37,7 @@
#include "udisksfstabentry.h"
#include "udiskscrypttabmonitor.h"
#include "udiskscrypttabentry.h"
+#include "udisksiscsiprovider.h"
/**
* SECTION:udisksdaemon
@@ -66,6 +67,8 @@ struct _UDisksDaemon
UDisksLinuxProvider *linux_provider;
+ UDisksiSCSIProvider *iscsi_provider;
+
PolkitAuthority *authority;
UDisksCleanup *cleanup;
@@ -102,8 +105,9 @@ udisks_daemon_finalize (GObject *object)
g_object_unref (daemon->authority);
g_object_unref (daemon->persistent_store);
- g_object_unref (daemon->object_manager);
g_object_unref (daemon->linux_provider);
+ g_object_unref (daemon->iscsi_provider);
+ g_object_unref (daemon->object_manager);
g_object_unref (daemon->mount_monitor);
g_object_unref (daemon->connection);
g_object_unref (daemon->fstab_monitor);
@@ -232,7 +236,10 @@ udisks_daemon_constructed (GObject *object)
/* now add providers */
daemon->linux_provider = udisks_linux_provider_new (daemon);
+ daemon->iscsi_provider = udisks_iscsi_provider_new (daemon);
+ /* need to start the iSCSI provider before the Drive and Block provider */
+ udisks_provider_start (UDISKS_PROVIDER (daemon->iscsi_provider));
udisks_provider_start (UDISKS_PROVIDER (daemon->linux_provider));
/* Export the ObjectManager */
@@ -412,6 +419,21 @@ udisks_daemon_get_linux_provider (UDisksDaemon *daemon)
}
/**
+ * udisks_daemon_get_iscsi_provider:
+ * @daemon: A #UDisksDaemon.
+ *
+ * Gets the iSCSI Provider, if any.
+ *
+ * Returns: A #UDisksiSCSIProvider or %NULL. Do not free, the object is owned by @daemon.
+ */
+UDisksiSCSIProvider *
+udisks_daemon_get_iscsi_provider (UDisksDaemon *daemon)
+{
+ g_return_val_if_fail (UDISKS_IS_DAEMON (daemon), NULL);
+ return daemon->iscsi_provider;
+}
+
+/**
* udisks_daemon_get_persistent_store:
* @daemon: A #UDisksDaemon.
*
diff --git a/src/udisksdaemon.h b/src/udisksdaemon.h
index 8cda8f0..ded94da 100644
--- a/src/udisksdaemon.h
+++ b/src/udisksdaemon.h
@@ -37,6 +37,7 @@ UDisksMountMonitor *udisks_daemon_get_mount_monitor (UDisksDaemon *
UDisksFstabMonitor *udisks_daemon_get_fstab_monitor (UDisksDaemon *daemon);
UDisksCrypttabMonitor *udisks_daemon_get_crypttab_monitor (UDisksDaemon *daemon);
UDisksLinuxProvider *udisks_daemon_get_linux_provider (UDisksDaemon *daemon);
+UDisksiSCSIProvider *udisks_daemon_get_iscsi_provider (UDisksDaemon *daemon);
UDisksPersistentStore *udisks_daemon_get_persistent_store (UDisksDaemon *daemon);
PolkitAuthority *udisks_daemon_get_authority (UDisksDaemon *daemon);
UDisksCleanup *udisks_daemon_get_cleanup (UDisksDaemon *daemon);
diff --git a/src/udisksdaemontypes.h b/src/udisksdaemontypes.h
index a00e343..2340cef 100644
--- a/src/udisksdaemontypes.h
+++ b/src/udisksdaemontypes.h
@@ -103,6 +103,9 @@ typedef struct _UDisksLinuxPartition UDisksLinuxPartition;
struct _UDisksLinuxPartitionTable;
typedef struct _UDisksLinuxPartitionTable UDisksLinuxPartitionTable;
+struct _UDisksiSCSIProvider;
+typedef struct _UDisksiSCSIProvider UDisksiSCSIProvider;
+
/**
* UDisksThreadedJobFunc:
* @job: A #UDisksThreadedJob.
diff --git a/src/udisksiscsiprovider.c b/src/udisksiscsiprovider.c
new file mode 100644
index 0000000..9e9aca3
--- /dev/null
+++ b/src/udisksiscsiprovider.c
@@ -0,0 +1,1470 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*-
+ *
+ * Copyright (C) 2007-2010 David Zeuthen <zeuthen@gmail.com>
+ *
+ * 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 "config.h"
+#include <glib/gi18n-lib.h>
+
+/* TODO:
+ *
+ * - instead of parsing /var/lib/iscsi, we should probably run the
+ * command 'iscsadm -m node -P 1' and parse the output
+ *
+ * - need to somehow get reliable change notifications when
+ * iscsiadm's database has changed
+ *
+ * - there is currently no way to get/set properties for each
+ * connection/path - this is really needed especially for
+ * e.g. setting up authentication
+ *
+ * - there is no way to add/remove targets and add/remove paths -
+ * this should use a discovery mechanism
+ *
+ * - should we expose node.discovery_address, node.discovery_port and
+ * node.discovery_type somehow so the UI can group targets
+ * discovered from a SendTargets server... ugh..
+ *
+ * - apparently we don't get any uevent when the state sysfs
+ * attribute changes on an iscsi_connection - TODO: file a bug and
+ * poll until this is fixed
+ */
+
+#include <stdio.h>
+#include <mntent.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/wait.h>
+#include <unistd.h>
+
+#include <string.h>
+#include <stdlib.h>
+
+#include <gudev/gudev.h>
+
+#include "udisksdaemon.h"
+#include "udisksprovider.h"
+#include "udisksmount.h"
+#include "udisksmountmonitor.h"
+#include "udisksiscsiprovider.h"
+#include "udiskslinuxprovider.h"
+#include "udisksdaemonutil.h"
+#include "udiskslogging.h"
+
+/**
+ * SECTION:udisksiscsiprovider
+ * @title: UDisksiSCSIProvider
+ * @short_description: Provides UDisksiSCSITarget from the open-iscsi database
+ *
+ * This provider provides #UDisksiSCSITarget objects for iSCSI targets
+ * defined in the open-iscsi database. Additionally, this information
+ * is tied together with information to sysfs in order to convey the
+ * connection state of each iSCSI target.
+ */
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static void load_and_process_iscsi (UDisksiSCSIProvider *provider);
+
+static void connections_init (UDisksiSCSIProvider *provider);
+static void connections_finalize (UDisksiSCSIProvider *provider);
+static const gchar *connections_get_state (UDisksiSCSIProvider *provider,
+ const gchar *target_name,
+ gint tpgt,
+ const gchar *portal_address,
+ gint portal_port,
+ const gchar *iface_name,
+ gint *out_tpgt);
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static void
+diff_sorted_lists (GList *list1,
+ GList *list2,
+ GCompareFunc compare,
+ GList **added,
+ GList **removed)
+{
+ int order;
+
+ *added = *removed = NULL;
+
+ while (list1 != NULL && list2 != NULL)
+ {
+ order = (*compare) (list1->data, list2->data);
+ if (order < 0)
+ {
+ *removed = g_list_prepend (*removed, list1->data);
+ list1 = list1->next;
+ }
+ else if (order > 0)
+ {
+ *added = g_list_prepend (*added, list2->data);
+ list2 = list2->next;
+ }
+ else
+ { /* same item */
+ list1 = list1->next;
+ list2 = list2->next;
+ }
+ }
+
+ while (list1 != NULL)
+ {
+ *removed = g_list_prepend (*removed, list1->data);
+ list1 = list1->next;
+ }
+ while (list2 != NULL)
+ {
+ *added = g_list_prepend (*added, list2->data);
+ list2 = list2->next;
+ }
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static gchar *
+util_compute_object_path (const gchar *base,
+ const gchar *path)
+{
+ const gchar *basename;
+ GString *s;
+ guint n;
+
+ g_return_val_if_fail (path != NULL, NULL);
+
+ basename = strrchr (path, '/');
+ if (basename != NULL)
+ basename++;
+ else
+ basename = path;
+
+ s = g_string_new (base);
+ for (n = 0; basename[n] != '\0'; n++)
+ {
+ gint c = basename[n];
+
+ /* D-Bus spec sez:
+ *
+ * Each element must only contain the ASCII characters "[A-Z][a-z][0-9]_"
+ */
+ if ((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || (c >= '0' && c <= '9'))
+ {
+ g_string_append_c (s, c);
+ }
+ else
+ {
+ /* Escape bytes not in [A-Z][a-z][0-9] as _<hex-with-two-digits> */
+ g_string_append_printf (s, "_%02x", c);
+ }
+ }
+
+ return g_string_free (s, FALSE);
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+typedef struct
+{
+ gchar *name;
+} iSCSIIface;
+
+static void
+iscsi_iface_free (iSCSIIface *iface)
+{
+ g_free (iface->name);
+ g_free (iface);
+}
+
+static gint
+iscsi_iface_compare (const iSCSIIface *a,
+ const iSCSIIface *b)
+{
+ return g_strcmp0 (a->name, b->name);
+}
+
+typedef struct
+{
+ gchar *address;
+ gint port;
+ gint tpgt;
+ GList *ifaces;
+} iSCSIPortal;
+
+static void
+iscsi_portal_free (iSCSIPortal *portal)
+{
+ g_free (portal->address);
+ g_list_foreach (portal->ifaces, (GFunc) iscsi_iface_free, NULL);
+ g_list_free (portal->ifaces);
+ g_free (portal);
+}
+
+static gint
+iscsi_portal_compare (const iSCSIPortal *a,
+ const iSCSIPortal *b)
+{
+ gint ret;
+ GList *l;
+ GList *j;
+
+ ret = g_strcmp0 (a->address, b->address);
+ if (ret != 0)
+ goto out;
+ ret = a->port - b->port;
+ if (ret != 0)
+ goto out;
+ ret = a->tpgt - b->tpgt;
+ if (ret != 0)
+ goto out;
+ ret = g_list_length (a->ifaces) - g_list_length (b->ifaces);
+ if (ret != 0)
+ goto out;
+ for (l = a->ifaces, j = b->ifaces; l != NULL; l = l->next, j = j->next)
+ {
+ iSCSIIface *ia = l->data;
+ iSCSIIface *ib = j->data;
+ ret = iscsi_iface_compare (ia, ib);
+ if (ret != 0)
+ goto out;
+ }
+ ret = 0;
+ out:
+ return ret;
+}
+
+typedef struct
+{
+ volatile gint ref_count;
+
+ gchar *target_name;
+
+ gchar *object_path;
+ UDisksObjectSkeleton *object;
+ UDisksiSCSITarget *iface;
+
+ gchar *source_object_path;
+
+ GList *portals;
+} iSCSITarget;
+
+static iSCSITarget *
+iscsi_target_ref (iSCSITarget *target)
+{
+ g_atomic_int_inc (&target->ref_count);
+ return target;
+}
+
+static void
+iscsi_target_unref (iSCSITarget *target)
+{
+ if (g_atomic_int_dec_and_test (&target->ref_count))
+ {
+ g_free (target->target_name);
+
+ g_free (target->object_path);
+ if (target->object != NULL)
+ g_object_unref (target->object);
+ if (target->iface != NULL)
+ g_object_unref (target->iface);
+
+ g_free (target->source_object_path);
+
+ g_list_foreach (target->portals, (GFunc) iscsi_portal_free, NULL);
+ g_list_free (target->portals);
+
+ g_free (target);
+ }
+}
+
+/* on purpose, this does not take portals/ifaces into account */
+static gint
+iscsi_target_compare (const iSCSITarget *a,
+ const iSCSITarget *b)
+{
+ gint ret;
+
+ ret = g_strcmp0 (a->target_name, b->target_name);
+ if (ret != 0)
+ goto out;
+
+ out:
+ return ret;
+}
+
+typedef struct
+{
+ volatile gint ref_count;
+
+ const gchar *mechanism;
+
+ gchar *object_path;
+ UDisksObjectSkeleton *object;
+ UDisksiSCSISource *iface;
+
+ gchar *discovery_address;
+} iSCSISource;
+
+static iSCSISource *
+iscsi_source_ref (iSCSISource *source)
+{
+ g_atomic_int_inc (&source->ref_count);
+ return source;
+}
+
+static void
+iscsi_source_unref (iSCSISource *source)
+{
+ if (g_atomic_int_dec_and_test (&source->ref_count))
+ {
+ g_free (source->object_path);
+ if (source->object != NULL)
+ g_object_unref (source->object);
+ if (source->iface != NULL)
+ g_object_unref (source->iface);
+
+ g_free (source->discovery_address);
+
+ g_free (source);
+ }
+}
+
+/* on purpose, this does not take targets/portals/ifaces into account */
+static gint
+iscsi_source_compare (const iSCSISource *a,
+ const iSCSISource *b)
+{
+ gint ret;
+
+ ret = g_strcmp0 (a->mechanism, b->mechanism);
+ if (ret != 0)
+ goto out;
+
+ ret = g_strcmp0 (a->discovery_address, b->discovery_address);
+ if (ret != 0)
+ goto out;
+
+ out:
+ return ret;
+}
+
+static void
+iscsi_source_compute_object_path (iSCSISource *source)
+{
+ g_assert (source->object_path == NULL);
+ if (g_strcmp0 (source->mechanism, "static") == 0)
+ {
+ source->object_path = g_strdup ("/org/freedesktop/UDisks2/iSCSI/static");
+ }
+ else if (g_strcmp0 (source->mechanism, "sendtargets") == 0)
+ {
+ source->object_path = util_compute_object_path ("/org/freedesktop/UDisks2/iSCSI/sendtargets/", source->discovery_address);
+ }
+ else if (g_strcmp0 (source->mechanism, "firmware") == 0)
+ {
+ source->object_path = g_strdup ("/org/freedesktop/UDisks2/iSCSI/firmware");
+ }
+ else
+ {
+ g_error ("TODO: support '%s'", source->mechanism);
+ }
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+typedef struct _UDisksiSCSIProviderClass UDisksiSCSIProviderClass;
+
+/**
+ * UDisksiSCSIProvider:
+ *
+ * The #UDisksiSCSIProvider structure contains only private data and
+ * should only be accessed using the provided API.
+ */
+struct _UDisksiSCSIProvider
+{
+ UDisksProvider parent_instance;
+
+ UDisksDaemon *daemon;
+ GUdevClient *udev_client;
+
+ GFileMonitor *file_monitor;
+ guint cool_off_timeout_id;
+
+ GHashTable *sysfs_to_connection;
+ GHashTable *id_to_connection;
+ GHashTable *id_without_tpgt_to_connection;
+
+ GList *targets;
+ GList *sources;
+};
+
+struct _UDisksiSCSIProviderClass
+{
+ UDisksProviderClass parent_class;
+};
+
+static void
+on_file_monitor_changed (GFileMonitor *monitor,
+ GFile *file,
+ GFile *other_file,
+ GFileMonitorEvent event_type,
+ gpointer user_data);
+
+G_DEFINE_TYPE (UDisksiSCSIProvider, udisks_iscsi_provider, UDISKS_TYPE_PROVIDER);
+
+static void
+udisks_iscsi_provider_finalize (GObject *object)
+{
+ UDisksiSCSIProvider *provider = UDISKS_ISCSI_PROVIDER (object);
+ GList *l;
+
+ if (provider->cool_off_timeout_id != 0)
+ g_source_remove (provider->cool_off_timeout_id);
+
+ if (provider->file_monitor == NULL)
+ {
+ g_signal_handlers_disconnect_by_func (provider->file_monitor,
+ G_CALLBACK (on_file_monitor_changed),
+ provider);
+ g_object_unref (provider->file_monitor);
+ }
+
+ for (l = provider->targets; l != NULL; l = l->next)
+ {
+ iSCSITarget *target = l->data;
+ g_assert (target->object_path != NULL);
+ g_dbus_object_manager_server_unexport (udisks_daemon_get_object_manager (udisks_provider_get_daemon (UDISKS_PROVIDER (provider))),
+ target->object_path);
+ iscsi_target_unref (target);
+ }
+ g_list_free (provider->targets);
+
+ for (l = provider->sources; l != NULL; l = l->next)
+ {
+ iSCSISource *source = l->data;
+ g_dbus_object_manager_server_unexport (udisks_daemon_get_object_manager (udisks_provider_get_daemon (UDISKS_PROVIDER (provider))),
+ source->object_path);
+ iscsi_source_unref (source);
+ }
+ g_list_free (provider->sources);
+
+ connections_finalize (provider);
+ g_object_unref (provider->udev_client);
+
+ if (G_OBJECT_CLASS (udisks_iscsi_provider_parent_class)->finalize != NULL)
+ G_OBJECT_CLASS (udisks_iscsi_provider_parent_class)->finalize (object);
+}
+
+static void
+udisks_iscsi_provider_init (UDisksiSCSIProvider *provider)
+{
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static void
+udisks_iscsi_provider_start (UDisksProvider *_provider)
+{
+ UDisksiSCSIProvider *provider = UDISKS_ISCSI_PROVIDER (_provider);
+ GFile *file;
+ GError *error;
+ const gchar *nodes_dir_name;
+ const gchar *subsystems[] = {"iscsi_connection",
+ "iscsi_session",
+ "scsi",
+ NULL};
+
+ if (UDISKS_PROVIDER_CLASS (udisks_iscsi_provider_parent_class)->start != NULL)
+ UDISKS_PROVIDER_CLASS (udisks_iscsi_provider_parent_class)->start (_provider);
+
+ provider->daemon = udisks_provider_get_daemon (UDISKS_PROVIDER (provider));
+ provider->udev_client = g_udev_client_new (subsystems);
+
+ /* TODO: this doesn't catch all changes but it's good enough for now */
+ nodes_dir_name = "/var/lib/iscsi/nodes";
+ file = g_file_new_for_path ("/var/lib/iscsi/nodes");
+ error = NULL;
+ provider->file_monitor = g_file_monitor_directory (file,
+ G_FILE_MONITOR_NONE,
+ NULL,
+ &error);
+ if (provider->file_monitor == NULL)
+ {
+ udisks_warning ("Error monitoring dir %s: %s",
+ nodes_dir_name,
+ error->message);
+ g_error_free (error);
+ }
+ else
+ {
+ g_file_monitor_set_rate_limit (provider->file_monitor, 50 /* msec */);
+ g_signal_connect (provider->file_monitor,
+ "changed",
+ G_CALLBACK (on_file_monitor_changed),
+ provider);
+ }
+ g_object_unref (file);
+
+ connections_init (provider);
+
+ load_and_process_iscsi (provider);
+}
+
+
+static void
+udisks_iscsi_provider_class_init (UDisksiSCSIProviderClass *klass)
+{
+ GObjectClass *gobject_class;
+ UDisksProviderClass *provider_class;
+
+ gobject_class = G_OBJECT_CLASS (klass);
+ gobject_class->finalize = udisks_iscsi_provider_finalize;
+
+ provider_class = UDISKS_PROVIDER_CLASS (klass);
+ provider_class->start = udisks_iscsi_provider_start;
+}
+
+/**
+ * udisks_iscsi_provider_new:
+ * @daemon: A #UDisksDaemon.
+ *
+ * Create a new provider object for iSCSI targets on the system.
+ *
+ * Returns: A #UDisksiSCSIProvider object. Free with g_object_unref().
+ */
+UDisksiSCSIProvider *
+udisks_iscsi_provider_new (UDisksDaemon *daemon)
+{
+ g_return_val_if_fail (UDISKS_IS_DAEMON (daemon), NULL);
+ return UDISKS_ISCSI_PROVIDER (g_object_new (UDISKS_TYPE_ISCSI_PROVIDER,
+ "daemon", daemon,
+ NULL));
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+/* returns a new floating GVariant */
+static GVariant *
+portals_and_ifaces_to_gvariant (UDisksiSCSIProvider *provider,
+ iSCSITarget *target)
+{
+ GVariantBuilder connections_builder;
+ GList *l, *ll;
+
+ target->portals = g_list_sort (target->portals, (GCompareFunc) iscsi_portal_compare);
+
+ g_variant_builder_init (&connections_builder, G_VARIANT_TYPE ("a(siissa{sv})"));
+ for (l = target->portals; l != NULL; l = l->next)
+ {
+ iSCSIPortal *portal = l->data;
+ portal->ifaces = g_list_sort (portal->ifaces, (GCompareFunc) iscsi_iface_compare);
+ for (ll = portal->ifaces; ll != NULL; ll = ll->next)
+ {
+ iSCSIIface *iface = ll->data;
+ const gchar *state;
+ gint connection_tpgt;
+
+ state = connections_get_state (provider,
+ target->target_name,
+ portal->tpgt,
+ portal->address,
+ portal->port,
+ iface->name,
+ &connection_tpgt);
+
+ g_variant_builder_add (&connections_builder, "(siissa{sv})",
+ portal->address,
+ portal->port,
+ portal->tpgt != -1 ? portal->tpgt : connection_tpgt,
+ iface->name,
+ state,
+ NULL); /* expansion */
+ }
+ }
+ return g_variant_builder_end (&connections_builder);
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+/* runs in dedicated thread */
+static gboolean
+on_iscsi_target_handle_login_logout (UDisksiSCSITarget *iface,
+ GDBusMethodInvocation *invocation,
+ const gchar *host,
+ gint port,
+ gint tpgt,
+ const gchar *iface_name,
+ GVariant *options,
+ UDisksiSCSIProvider *provider,
+ gboolean is_login)
+{
+ gint exit_status;
+ gchar *error_message = NULL;
+ GString *command_line = NULL;
+ gchar *s;
+
+ error_message = NULL;
+ command_line = NULL;
+
+ /* TODO: we want nicer authentication message */
+ if (!udisks_daemon_util_check_authorization_sync (provider->daemon,
+ UDISKS_OBJECT (g_dbus_interface_get_object (G_DBUS_INTERFACE (iface))),
+ "org.freedesktop.udisks2.iscsi-initiator",
+ options,
+ is_login ?
+ N_("Authentication is required to login to an iSCSI target") :
+ N_("Authentication is required to logout of an iSCSI target"),
+ invocation))
+ goto out;
+
+ command_line = g_string_new ("iscsiadm --mode node");
+
+ s = g_strescape (udisks_iscsi_target_get_name (iface), NULL);
+ g_string_append_printf (command_line, " --target \"%s\"", s);
+ g_free (s);
+ if (strlen (host) > 0)
+ {
+ s = g_strescape (host, NULL);
+ if (port == 0)
+ port = 3260;
+ g_string_append_printf (command_line, " --portal \"%s\":%d", s, port);
+ g_free (s);
+ }
+ if (strlen (iface_name) > 0)
+ {
+ s = g_strescape (iface_name, NULL);
+ g_string_append_printf (command_line, " --interface \"%s\"", s);
+ g_free (s);
+ }
+
+ if (is_login)
+ g_string_append (command_line, " --login");
+ else
+ g_string_append (command_line, " --logout");
+
+ if (!udisks_daemon_launch_spawned_job_sync (provider->daemon,
+ NULL, /* object */
+ NULL, /* GCancellable */
+ 0, /* uid_t run_as_uid */
+ 0, /* uid_t run_as_euid */
+ &exit_status,
+ &error_message,
+ NULL, /* input_string */
+ "%s",
+ command_line->str))
+ {
+ g_dbus_method_invocation_return_error (invocation,
+ UDISKS_ERROR,
+ UDISKS_ERROR_FAILED,
+ "iscsiadm(8) failed with: %s",
+ error_message);
+ }
+ else
+ {
+ /* sometimes iscsiadm returns 0 when it fails but stderr is set...
+ *
+ * TODO: file a bug against iscsi-initiator-utils
+ */
+ if (error_message != NULL && strlen (error_message) > 0)
+ {
+ g_dbus_method_invocation_return_error (invocation,
+ UDISKS_ERROR,
+ UDISKS_ERROR_FAILED,
+ "iscsiadm(8) failed with: %s",
+ error_message);
+ }
+ else
+ {
+ g_dbus_method_invocation_return_value (invocation, NULL);
+ }
+ }
+
+ out:
+ if (command_line != NULL)
+ g_string_free (command_line, TRUE);
+ g_free (error_message);
+ return TRUE; /* call was handled */
+}
+
+static gboolean
+on_iscsi_target_handle_login (UDisksiSCSITarget *iface,
+ GDBusMethodInvocation *invocation,
+ const gchar *host,
+ gint port,
+ gint tpgt,
+ const gchar *iface_name,
+ GVariant *options,
+ gpointer user_data)
+{
+ UDisksiSCSIProvider *provider = UDISKS_ISCSI_PROVIDER (user_data);
+ return on_iscsi_target_handle_login_logout (iface, invocation, host, port, tpgt, iface_name, options, provider, TRUE);
+}
+
+static gboolean
+on_iscsi_target_handle_logout (UDisksiSCSITarget *iface,
+ GDBusMethodInvocation *invocation,
+ const gchar *host,
+ gint port,
+ gint tpgt,
+ const gchar *iface_name,
+ GVariant *options,
+ gpointer user_data)
+{
+ UDisksiSCSIProvider *provider = UDISKS_ISCSI_PROVIDER (user_data);
+ return on_iscsi_target_handle_login_logout (iface, invocation, host, port, tpgt, iface_name, options, provider, FALSE);
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static void
+add_remove_targets (UDisksiSCSIProvider *provider,
+ GList *parsed_targets)
+{
+ GList *l;
+ GList *added;
+ GList *removed;
+
+ provider->targets = g_list_sort (provider->targets, (GCompareFunc) iscsi_target_compare);
+ diff_sorted_lists (provider->targets,
+ parsed_targets,
+ (GCompareFunc) iscsi_target_compare,
+ &added,
+ &removed);
+ for (l = removed; l != NULL; l = l->next)
+ {
+ iSCSITarget *target = l->data;
+ g_dbus_object_manager_server_unexport (udisks_daemon_get_object_manager (udisks_provider_get_daemon (UDISKS_PROVIDER (provider))),
+ target->object_path);
+ provider->targets = g_list_remove (provider->targets, target);
+ iscsi_target_unref (target);
+ }
+
+ for (l = added; l != NULL; l = l->next)
+ {
+ iSCSITarget *target = l->data;
+ gchar *base;
+ base = g_strconcat (target->source_object_path, "/", NULL);
+ target->object_path = util_compute_object_path (base, target->target_name);
+ g_free (base);
+ target->iface = udisks_iscsi_target_skeleton_new ();
+ g_dbus_interface_skeleton_set_flags (G_DBUS_INTERFACE_SKELETON (target->iface),
+ G_DBUS_INTERFACE_SKELETON_FLAGS_HANDLE_METHOD_INVOCATIONS_IN_THREAD);
+ g_signal_connect (target->iface,
+ "handle-login",
+ G_CALLBACK (on_iscsi_target_handle_login),
+ provider);
+ g_signal_connect (target->iface,
+ "handle-logout",
+ G_CALLBACK (on_iscsi_target_handle_logout),
+ provider);
+ udisks_iscsi_target_set_name (target->iface, target->target_name);
+ udisks_iscsi_target_set_source (target->iface, target->source_object_path);
+ provider->targets = g_list_prepend (provider->targets, iscsi_target_ref (target));
+ }
+
+ /* update all known targets since portals/interfaces might have changed */
+ for (l = provider->targets; l != NULL; l = l->next)
+ {
+ iSCSITarget *target = l->data;
+ udisks_iscsi_target_set_connections (target->iface,
+ portals_and_ifaces_to_gvariant (provider, target));
+ }
+
+ /* finally export added targets */
+ for (l = added; l != NULL; l = l->next)
+ {
+ iSCSITarget *target = l->data;
+ target->object = udisks_object_skeleton_new (target->object_path);
+ udisks_object_skeleton_set_iscsi_target (target->object, target->iface);
+ g_dbus_object_manager_server_export_uniquely (udisks_daemon_get_object_manager (udisks_provider_get_daemon (UDISKS_PROVIDER (provider))),
+ G_DBUS_OBJECT_SKELETON (target->object));
+ }
+
+ g_list_free (removed);
+ g_list_free (added);
+}
+
+static void
+add_remove_sources (UDisksiSCSIProvider *provider,
+ GList *parsed_sources)
+{
+ GList *l;
+ GList *added;
+ GList *removed;
+
+ provider->sources = g_list_sort (provider->sources, (GCompareFunc) iscsi_source_compare);
+ diff_sorted_lists (provider->sources,
+ parsed_sources,
+ (GCompareFunc) iscsi_source_compare,
+ &added,
+ &removed);
+ for (l = removed; l != NULL; l = l->next)
+ {
+ iSCSISource *source = l->data;
+ g_dbus_object_manager_server_unexport (udisks_daemon_get_object_manager (udisks_provider_get_daemon (UDISKS_PROVIDER (provider))),
+ source->object_path);
+ provider->sources = g_list_remove (provider->sources, source);
+ iscsi_source_unref (source);
+ }
+
+ for (l = added; l != NULL; l = l->next)
+ {
+ iSCSISource *source = l->data;
+ source->iface = udisks_iscsi_source_skeleton_new ();
+ g_dbus_interface_skeleton_set_flags (G_DBUS_INTERFACE_SKELETON (source->iface),
+ G_DBUS_INTERFACE_SKELETON_FLAGS_HANDLE_METHOD_INVOCATIONS_IN_THREAD);
+ /* TODO: export methods */
+ udisks_iscsi_source_set_mechanism (source->iface, source->mechanism);
+ udisks_iscsi_source_set_address (source->iface, source->discovery_address);
+ provider->sources = g_list_prepend (provider->sources, iscsi_source_ref (source));
+ }
+
+ /* export added sources */
+ for (l = added; l != NULL; l = l->next)
+ {
+ iSCSISource *source = l->data;
+ source->object = udisks_object_skeleton_new (source->object_path);
+ udisks_object_skeleton_set_iscsi_source (source->object, source->iface);
+ g_dbus_object_manager_server_export_uniquely (udisks_daemon_get_object_manager (udisks_provider_get_daemon (UDISKS_PROVIDER (provider))),
+ G_DBUS_OBJECT_SKELETON (source->object));
+ }
+
+ g_list_free (removed);
+ g_list_free (added);
+}
+
+enum
+{
+ MODE_NOWHERE,
+ MODE_IN_SENDTARGETS,
+ MODE_IN_ISNS,
+ MODE_IN_STATIC,
+ MODE_IN_FIRMWARE
+};
+
+static void
+load_and_process_iscsi (UDisksiSCSIProvider *provider)
+{
+ GError *error;
+ const gchar *command_line;
+ gchar *ia_out;
+ gchar *ia_err;
+ gint exit_status;
+ GList *parsed_targets;
+ GList *parsed_sources;
+ const gchar *s;
+ iSCSITarget *target;
+ iSCSIPortal *portal;
+ iSCSISource *source;
+
+ parsed_targets = NULL;
+ parsed_sources = NULL;
+ ia_out = NULL;
+ ia_err = NULL;
+
+ /* TODO: might be problematic that we block here */
+ error = NULL;
+ command_line = "iscsiadm --mode discoverydb --print 1";
+ if (!g_spawn_command_line_sync (command_line,
+ &ia_out,
+ &ia_err,
+ &exit_status,
+ &error))
+ {
+ udisks_warning ("Error spawning `%s': %s",
+ command_line,
+ error->message);
+ g_error_free (error);
+ goto done_parsing;
+ }
+
+ if (!(WIFEXITED (exit_status) && WEXITSTATUS (exit_status) == 0))
+ {
+ udisks_warning ("The command-line `%s' didn't exit normally with return code 0: %d",
+ command_line, exit_status);
+ goto done_parsing;
+ }
+
+
+ gint mode;
+ mode = MODE_NOWHERE;
+ source = NULL;
+
+ s = ia_out;
+ target = NULL;
+ while (s != NULL)
+ {
+ const gchar *endl;
+ gchar *line;
+
+ endl = strstr (s, "\n");
+ if (endl == NULL)
+ {
+ line = g_strdup (s);
+ s = NULL;
+ }
+ else
+ {
+ line = g_strndup (s, endl - s);
+ s = endl + 1;
+ }
+
+ if (g_strcmp0 (line, "SENDTARGETS:") == 0)
+ {
+ mode = MODE_IN_SENDTARGETS;
+ source = NULL;
+ target = NULL;
+ portal = NULL;
+ }
+ else if (mode == MODE_IN_SENDTARGETS && g_str_has_prefix (line, "DiscoveryAddress: "))
+ {
+ source = g_new0 (iSCSISource, 1);
+ source->ref_count = 1;
+ source->mechanism = "sendtargets";
+ source->discovery_address = g_strdup (line + sizeof "DiscoveryAddress: " - 1);
+ /* TODO: fix up comma */
+ iscsi_source_compute_object_path (source);
+ parsed_sources = g_list_prepend (parsed_sources, source);
+ target = NULL;
+ portal = NULL;
+ }
+ else if (g_strcmp0 (line, "iSNS:") == 0)
+ {
+ mode = MODE_IN_ISNS;
+ source = NULL;
+ target = NULL;
+ portal = NULL;
+ }
+ else if (mode == MODE_IN_ISNS && g_str_has_prefix (line, "DiscoveryAddress: "))
+ {
+ source = g_new0 (iSCSISource, 1);
+ source->ref_count = 1;
+ source->mechanism = "isns";
+ source->discovery_address = g_strdup (line + sizeof "DiscoveryAddress: " - 1);
+ /* TODO: fix up comma */
+ iscsi_source_compute_object_path (source);
+ parsed_sources = g_list_prepend (parsed_sources, source);
+ target = NULL;
+ portal = NULL;
+ }
+ else if (g_strcmp0 (line, "STATIC:") == 0)
+ {
+ mode = MODE_IN_STATIC;
+ source = g_new0 (iSCSISource, 1);
+ source->ref_count = 1;
+ source->mechanism = "static";
+ iscsi_source_compute_object_path (source);
+ parsed_sources = g_list_prepend (parsed_sources, source);
+ target = NULL;
+ portal = NULL;
+ }
+ else if (g_strcmp0 (line, "FIRMWARE:") == 0)
+ {
+ mode = MODE_IN_FIRMWARE;
+ source = g_new0 (iSCSISource, 1);
+ source->ref_count = 1;
+ source->mechanism = "firmware";
+ iscsi_source_compute_object_path (source);
+ parsed_sources = g_list_prepend (parsed_sources, source);
+ target = NULL;
+ portal = NULL;
+ }
+ else if (g_strcmp0 (line, "No targets found.") == 0)
+ {
+ mode = MODE_NOWHERE;
+ source = NULL;
+ target = NULL;
+ portal = NULL;
+ }
+ else if (g_str_has_prefix (line, "Target: "))
+ {
+ if (source == NULL)
+ {
+ g_warning ("Target without a current Source");
+ }
+ else
+ {
+ target = g_new0 (iSCSITarget, 1);
+ target->ref_count = 1;
+ target->source_object_path = g_strdup (source->object_path);
+ target->target_name = g_strdup (line + sizeof "Target: " - 1);
+ g_strstrip (target->target_name);
+ parsed_targets = g_list_prepend (parsed_targets, target);
+ }
+ }
+ else if (g_str_has_prefix (line, "\tPortal: "))
+ {
+ if (target == NULL)
+ {
+ g_warning ("Portal without a current target");
+ }
+ else
+ {
+ const gchar *s;
+ gint port, tpgt;
+ s = g_strrstr (line, ":");
+ if (s == NULL || sscanf (s + 1, "%d,%d", &port, &tpgt) != 2)
+ {
+ g_warning ("Invalid line `%s'", line);
+ }
+ else
+ {
+ const gchar *s2;
+ portal = g_new0 (iSCSIPortal, 1);
+ s2 = line + sizeof "\tPortal: " - 1;
+ g_assert (s - s2 >= 0);
+ portal->address = g_strndup (s2, s - s2);
+ g_strstrip (portal->address);
+ if (portal->address[0] == '[' && portal->address[strlen (portal->address) - 1] == ']')
+ {
+ portal->address[0] = ' ';
+ portal->address[strlen (portal->address) - 1] = '\0';
+ g_strstrip (portal->address);
+ }
+ portal->port = port;
+ portal->tpgt = tpgt;
+ target->portals = g_list_append (target->portals, portal);
+ }
+ }
+ }
+ else if (g_str_has_prefix (line, "\t\tIface Name: "))
+ {
+ if (portal == NULL)
+ {
+ g_warning ("Iface Name without a current portal");
+ }
+ else
+ {
+ iSCSIIface *iface;
+ iface = g_new0 (iSCSIIface, 1);
+ iface->name = g_strdup (line + sizeof "\t\tIface Name: " - 1);
+ portal->ifaces = g_list_append (portal->ifaces, iface);
+ }
+ }
+ else if (strlen (line) > 0)
+ {
+ g_warning ("Unexpected line `%s'", line);
+ }
+
+ g_free (line);
+ }
+
+ done_parsing:
+
+ parsed_targets = g_list_sort (parsed_targets, (GCompareFunc) iscsi_target_compare);
+ parsed_sources = g_list_sort (parsed_sources, (GCompareFunc) iscsi_source_compare);
+
+ add_remove_targets (provider, parsed_targets);
+ add_remove_sources (provider, parsed_sources);
+
+ g_list_foreach (parsed_targets, (GFunc) iscsi_target_unref, NULL);
+ g_list_free (parsed_targets);
+ g_list_foreach (parsed_sources, (GFunc) iscsi_source_unref, NULL);
+ g_list_free (parsed_sources);
+ g_free (ia_out);
+ g_free (ia_err);
+}
+
+static void
+update_state (UDisksiSCSIProvider *provider)
+{
+ GList *l;
+ for (l = provider->targets; l != NULL; l = l->next)
+ {
+ iSCSITarget *target = l->data;
+ udisks_iscsi_target_set_connections (target->iface,
+ portals_and_ifaces_to_gvariant (provider, target));
+ }
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static gboolean
+on_cool_off_timeout_cb (gpointer user_data)
+{
+ UDisksiSCSIProvider *provider = UDISKS_ISCSI_PROVIDER (user_data);
+
+ udisks_info ("iscsi refresh..");
+ load_and_process_iscsi (provider);
+ provider->cool_off_timeout_id = 0;
+ return FALSE;
+}
+
+static void
+on_file_monitor_changed (GFileMonitor *monitor,
+ GFile *file,
+ GFile *other_file,
+ GFileMonitorEvent event_type,
+ gpointer user_data)
+{
+ UDisksiSCSIProvider *provider = UDISKS_ISCSI_PROVIDER (user_data);
+ udisks_info ("iscsi file monitor event..");
+ /* coalesce many events into one */
+ if (provider->cool_off_timeout_id == 0)
+ provider->cool_off_timeout_id = g_timeout_add (250, on_cool_off_timeout_cb, provider);
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+typedef struct
+{
+ /* from iscsi_session */
+ gchar *target_name;
+ gchar *iface_name;
+ gint tpgt;
+ gchar *state;
+ gchar *session_sysfs_path;
+
+ /* from iscsi_connection */
+ gchar *address;
+ gint port;
+
+ gchar *id;
+ gchar *id_without_tpgt;
+} Connection;
+
+static void
+connection_free (Connection *connection)
+{
+ g_free (connection->target_name);
+ g_free (connection->iface_name);
+ g_free (connection->state);
+ g_free (connection->session_sysfs_path);
+ g_free (connection->address);
+ g_free (connection->id);
+ g_free (connection->id_without_tpgt);
+ g_free (connection);
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+/* believe it or not, sometimes the kernel returns a sysfs attr with content "(null)" */
+static gboolean
+is_null (const gchar *str)
+{
+ return str == NULL || g_strcmp0 (str, "(null)") == 0;
+}
+
+static void
+handle_iscsi_connection_uevent (UDisksiSCSIProvider *provider,
+ const gchar *uevent,
+ GUdevDevice *device)
+{
+ const gchar *sysfs_path;
+ Connection *connection;
+
+ sysfs_path = g_udev_device_get_sysfs_path (device);
+ connection = g_hash_table_lookup (provider->sysfs_to_connection, sysfs_path);
+ if (g_strcmp0 (uevent, "remove") == 0)
+ {
+ if (connection != NULL)
+ {
+ /* g_debug ("removed %s %s", sysfs_path, connection->id); */
+ g_warn_if_fail (g_hash_table_remove (provider->id_to_connection, connection->id));
+ g_warn_if_fail (g_hash_table_remove (provider->id_without_tpgt_to_connection, connection->id_without_tpgt));
+ g_hash_table_remove (provider->sysfs_to_connection, sysfs_path);
+ }
+ else
+ {
+ g_warning ("no object for connection %s", sysfs_path);
+ }
+ }
+ else
+ {
+ /* This is a bit sketchy and includes assumptions about what sysfs
+ * currently looks like...
+ */
+ if (connection == NULL)
+ {
+ gchar *session_sysfs_dir;
+ GDir *session_dir;
+ gchar *session_sysfs_path;
+ GUdevDevice *session_device;
+ const gchar *name;
+
+ session_sysfs_dir = NULL;
+ session_dir = NULL;
+ session_sysfs_path = NULL;
+ session_device = NULL;
+
+ session_sysfs_dir = g_strdup_printf ("%s/device/../iscsi_session", sysfs_path);
+ if (!g_file_test (session_sysfs_dir, G_FILE_TEST_IS_DIR))
+ goto skip_connection;
+ session_dir = g_dir_open (session_sysfs_dir, 0, NULL);
+ if (session_dir == NULL)
+ goto skip_connection;
+ while ((name = g_dir_read_name (session_dir)) != NULL)
+ {
+ gint session_num;
+ if (sscanf (name, "session%d", &session_num) == 1)
+ {
+ session_sysfs_path = g_strdup_printf ("%s/%s", session_sysfs_dir, name);
+ break;
+ }
+ }
+ if (session_sysfs_path == NULL)
+ goto skip_connection;
+ session_device = g_udev_client_query_by_sysfs_path (provider->udev_client, session_sysfs_path);
+ if (session_device == NULL)
+ goto skip_connection;
+
+ connection = g_new0 (Connection, 1);
+ connection->target_name = g_strdup (g_udev_device_get_sysfs_attr (session_device, "targetname"));
+ connection->iface_name = g_strdup (g_udev_device_get_sysfs_attr (session_device, "ifacename"));
+ connection->tpgt = g_udev_device_get_sysfs_attr_as_int (session_device, "tpgt");
+ connection->address = g_strdup (g_udev_device_get_sysfs_attr (device, "persistent_address"));
+ connection->port = g_udev_device_get_sysfs_attr_as_int (device, "persistent_port");
+ connection->session_sysfs_path = g_strdup (g_udev_device_get_sysfs_path (session_device));
+
+ if (is_null (connection->target_name) ||
+ is_null (connection->iface_name) ||
+ is_null (connection->address) ||
+ connection->port == 0)
+ {
+ udisks_warning ("Abandoning incomplete iscsi_connection object at %s"
+ " (target_name=%s)"
+ " (iface_name=%s)"
+ " (address=%s)"
+ " (port=%d)",
+ sysfs_path,
+ connection->target_name,
+ connection->iface_name,
+ connection->address,
+ connection->port);
+ connection_free (connection);
+ connection = NULL;
+ goto skip_connection;
+ }
+
+ connection->id = g_strdup_printf ("%d,%s:%d,%s,%s",
+ connection->tpgt,
+ connection->address,
+ connection->port,
+ connection->iface_name,
+ connection->target_name);
+ connection->id_without_tpgt = g_strdup_printf ("%s:%d,%s,%s",
+ connection->address,
+ connection->port,
+ connection->iface_name,
+ connection->target_name);
+ /* g_debug ("added %s %s", sysfs_path, connection->id); */
+ g_hash_table_insert (provider->sysfs_to_connection, g_strdup (sysfs_path), connection);
+ g_hash_table_insert (provider->id_to_connection, connection->id, connection);
+ g_hash_table_insert (provider->id_without_tpgt_to_connection, connection->id_without_tpgt, connection);
+
+ skip_connection:
+ g_free (session_sysfs_dir);
+ if (session_dir != NULL)
+ g_dir_close (session_dir);
+ g_free (session_sysfs_path);
+ if (session_device != NULL)
+ g_object_unref (session_device);
+ }
+
+ /* update the Connection object */
+ if (connection != NULL)
+ {
+ GUdevDevice *session_device;
+ session_device = g_udev_client_query_by_sysfs_path (provider->udev_client,
+ connection->session_sysfs_path);
+ if (session_device != NULL)
+ {
+ g_free (connection->state);
+ connection->state = g_strdup (g_udev_device_get_sysfs_attr (session_device, "state"));
+ g_object_unref (session_device);
+ }
+ else
+ {
+ g_warning ("no session device for %s", connection->session_sysfs_path);
+ }
+ }
+ }
+}
+
+static void
+handle_scsi_target_uevent (UDisksiSCSIProvider *provider,
+ const gchar *uevent,
+ GUdevDevice *device)
+{
+ const gchar *sysfs_path;
+ gchar *parent_sysfs_dir;
+ GDir *parent_dir;
+ gchar *connection_sysfs_path;
+ GUdevDevice *connection_device;
+ const gchar *name;
+ gchar connection_canonical_sysfs_path[PATH_MAX];
+
+ /* Also sketchy and also includes assumptions about what sysfs
+ * currently looks like...
+ */
+
+ parent_sysfs_dir = NULL;
+ parent_dir = NULL;
+ connection_sysfs_path = NULL;
+ connection_device = NULL;
+
+ if (g_strcmp0 (uevent, "remove") == 0)
+ goto skip;
+
+ sysfs_path = g_udev_device_get_sysfs_path (device);
+
+ parent_sysfs_dir = g_strdup_printf ("%s/..", sysfs_path);
+ parent_dir = g_dir_open (parent_sysfs_dir, 0, NULL);
+ if (parent_dir == NULL)
+ goto skip;
+ while ((name = g_dir_read_name (parent_dir)) != NULL)
+ {
+ gint connection_num;
+ if (sscanf (name, "connection%d", &connection_num) == 1)
+ {
+ connection_sysfs_path = g_strdup_printf ("%s/%s/iscsi_connection/%s", parent_sysfs_dir, name, name);
+ break;
+ }
+ }
+ if (connection_sysfs_path == NULL)
+ goto skip;
+ if (realpath (connection_sysfs_path, connection_canonical_sysfs_path) == NULL)
+ goto skip;
+ connection_device = g_udev_client_query_by_sysfs_path (provider->udev_client, connection_canonical_sysfs_path);
+ if (connection_device == NULL)
+ goto skip;
+
+ handle_iscsi_connection_uevent (provider, "change", connection_device);
+ update_state (provider);
+
+ skip:
+ g_free (parent_sysfs_dir);
+ if (parent_dir != NULL)
+ g_dir_close (parent_dir);
+ g_free (connection_sysfs_path);
+ if (connection_device != NULL)
+ g_object_unref (connection_device);
+}
+
+static void
+connections_on_uevent (GUdevClient *udev_client,
+ const gchar *uevent,
+ GUdevDevice *device,
+ gpointer user_data)
+{
+ UDisksiSCSIProvider *provider = UDISKS_ISCSI_PROVIDER (user_data);
+ const gchar *subsystem;
+ const gchar *devtype;
+
+ subsystem = g_udev_device_get_subsystem (device);
+ devtype = g_udev_device_get_devtype (device);
+ if (g_strcmp0 (subsystem, "iscsi_connection") == 0)
+ {
+ handle_iscsi_connection_uevent (provider, uevent, device);
+ update_state (provider);
+ }
+ else if (g_strcmp0 (subsystem, "scsi") == 0 && g_strcmp0 (devtype, "scsi_target") == 0)
+ {
+ handle_scsi_target_uevent (provider, uevent, device);
+ }
+}
+
+static void
+connections_init (UDisksiSCSIProvider *provider)
+{
+ GList *devices;
+ GList *l;
+
+ provider->sysfs_to_connection = g_hash_table_new_full (g_str_hash, g_str_equal, g_free,
+ (GDestroyNotify) connection_free);
+ provider->id_to_connection = g_hash_table_new (g_str_hash, g_str_equal);
+ provider->id_without_tpgt_to_connection = g_hash_table_new (g_str_hash, g_str_equal);
+
+ /* hotplug */
+ g_signal_connect (provider->udev_client,
+ "uevent",
+ G_CALLBACK (connections_on_uevent),
+ provider);
+
+ /* coldplug */
+ devices = g_udev_client_query_by_subsystem (provider->udev_client, "iscsi_connection");
+ for (l = devices; l != NULL; l = l->next)
+ {
+ GUdevDevice *device = G_UDEV_DEVICE (l->data);
+ handle_iscsi_connection_uevent (provider, "add", device);
+ }
+ g_list_foreach (devices, (GFunc) g_object_unref, NULL);
+ g_list_free (devices);
+}
+
+static void
+connections_finalize (UDisksiSCSIProvider *provider)
+{
+ g_signal_handlers_disconnect_by_func (provider->udev_client,
+ G_CALLBACK (connections_on_uevent),
+ provider);
+ g_hash_table_unref (provider->id_to_connection);
+ g_hash_table_unref (provider->id_without_tpgt_to_connection);
+ g_hash_table_unref (provider->sysfs_to_connection);
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static const gchar *
+connections_get_state (UDisksiSCSIProvider *provider,
+ const gchar *target_name,
+ gint tpgt,
+ const gchar *portal_address,
+ gint portal_port,
+ const gchar *iface_name,
+ gint *out_tpgt)
+{
+ const gchar *ret;
+ gchar *id;
+ Connection *connection;
+
+ ret = "";
+
+ if (tpgt != -1)
+ {
+ id = g_strdup_printf ("%d,%s:%d,%s,%s",
+ tpgt,
+ portal_address,
+ portal_port,
+ iface_name,
+ target_name);
+ connection = g_hash_table_lookup (provider->id_to_connection, id);
+ }
+ else
+ {
+ id = g_strdup_printf ("%s:%d,%s,%s",
+ portal_address,
+ portal_port,
+ iface_name,
+ target_name);
+ connection = g_hash_table_lookup (provider->id_without_tpgt_to_connection, id);
+ }
+
+ if (connection != NULL)
+ {
+ ret = connection->state;
+ if (out_tpgt != NULL)
+ *out_tpgt = connection->tpgt;
+ }
+
+ g_free (id);
+ return ret;
+}
+
+
+/* ---------------------------------------------------------------------------------------------------- */
diff --git a/src/udisksiscsiprovider.h b/src/udisksiscsiprovider.h
new file mode 100644
index 0000000..42f9d35
--- /dev/null
+++ b/src/udisksiscsiprovider.h
@@ -0,0 +1,37 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*-
+ *
+ * Copyright (C) 2007-2010 David Zeuthen <zeuthen@gmail.com>
+ *
+ * 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
+ *
+ */
+
+#ifndef __UDISKS_ISCSI_PROVIDER_H__
+#define __UDISKS_ISCSI_PROVIDER_H__
+
+#include "udisksdaemontypes.h"
+
+G_BEGIN_DECLS
+
+#define UDISKS_TYPE_ISCSI_PROVIDER (udisks_iscsi_provider_get_type ())
+#define UDISKS_ISCSI_PROVIDER(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), UDISKS_TYPE_ISCSI_PROVIDER, UDisksiSCSIProvider))
+#define UDISKS_IS_ISCSI_PROVIDER(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), UDISKS_TYPE_ISCSI_PROVIDER))
+
+GType udisks_iscsi_provider_get_type (void) G_GNUC_CONST;
+UDisksiSCSIProvider *udisks_iscsi_provider_new (UDisksDaemon *daemon);
+
+G_END_DECLS
+
+#endif /* __UDISKS_ISCSI_PROVIDER_H__ */
diff --git a/src/udiskslinuxdrive.c b/src/udiskslinuxdrive.c
index 6192f4a..c7017b0 100644
--- a/src/udiskslinuxdrive.c
+++ b/src/udiskslinuxdrive.c
@@ -121,6 +121,207 @@ udisks_linux_drive_new (void)
/* ---------------------------------------------------------------------------------------------------- */
+static const gchar *
+find_iscsi_target (GDBusObjectManagerServer *object_manager,
+ const gchar *target_name)
+{
+ const gchar *ret;
+ GList *objects;
+ GList *l;
+
+ ret = NULL;
+
+ objects = g_dbus_object_manager_get_objects (G_DBUS_OBJECT_MANAGER (object_manager));
+ for (l = objects; l != NULL; l = l->next)
+ {
+ UDisksObjectSkeleton *object = UDISKS_OBJECT_SKELETON (l->data);
+ UDisksiSCSITarget *target;
+
+ target = udisks_object_peek_iscsi_target (UDISKS_OBJECT (object));
+ if (target == NULL)
+ continue;
+
+ if (g_strcmp0 (udisks_iscsi_target_get_name (target), target_name) == 0)
+ {
+ ret = g_dbus_object_get_object_path (G_DBUS_OBJECT (object));
+ goto out;
+ }
+ }
+
+ out:
+ g_list_foreach (objects, (GFunc) g_object_unref, NULL);
+ g_list_free (objects);
+ return ret;
+}
+
+static gboolean
+find_iscsi_devices_for_block (GUdevClient *udev_client,
+ GUdevDevice *block_device,
+ GUdevDevice **out_session_device,
+ GUdevDevice **out_connection_device)
+{
+ gchar *s;
+ gchar *session_sysfs_path;
+ gchar *connection_sysfs_path;
+ GDir *session_dir;
+ GDir *connection_dir;
+ const gchar *name;
+ gboolean ret;
+ GUdevDevice *session_device;
+ GUdevDevice *connection_device;
+
+ ret = FALSE;
+ session_device = NULL;
+ connection_device = NULL;
+
+ session_dir = NULL;
+ connection_dir = NULL;
+ s = NULL;
+ session_sysfs_path = NULL;
+ connection_sysfs_path = NULL;
+
+ /* This is a bit sketchy and includes assumptions about what sysfs
+ * currently looks like...
+ */
+
+ if (out_session_device != NULL)
+ {
+ s = g_strdup_printf ("%s/device/../../iscsi_session", g_udev_device_get_sysfs_path (block_device));
+ if (!g_file_test (s, G_FILE_TEST_IS_DIR))
+ goto out;
+ session_dir = g_dir_open (s, 0, NULL);
+ if (session_dir == NULL)
+ goto out;
+ while ((name = g_dir_read_name (session_dir)) != NULL)
+ {
+ gint session_num;
+ if (sscanf (name, "session%d", &session_num) == 1)
+ {
+ session_sysfs_path = g_strdup_printf ("%s/%s", s, name);
+ break;
+ }
+ }
+ if (session_sysfs_path == NULL)
+ goto out;
+ session_device = g_udev_client_query_by_sysfs_path (udev_client, session_sysfs_path);
+ if (session_device == NULL)
+ goto out;
+ }
+
+ if (out_connection_device != NULL)
+ {
+ /* here we assume there is only one connection per session... this could end up not being true */
+ g_free (s);
+ s = g_strdup_printf ("%s/device/../..", g_udev_device_get_sysfs_path (block_device));
+ if (!g_file_test (s, G_FILE_TEST_IS_DIR))
+ goto out;
+ connection_dir = g_dir_open (s, 0, NULL);
+ if (connection_dir == NULL)
+ goto out;
+ while ((name = g_dir_read_name (connection_dir)) != NULL)
+ {
+ gint connection_num;
+ if (sscanf (name, "connection%d", &connection_num) == 1)
+ {
+ connection_sysfs_path = g_strdup_printf ("%s/%s/iscsi_connection/%s", s, name, name);
+ break;
+ }
+ }
+ if (connection_sysfs_path == NULL)
+ goto out;
+ connection_device = g_udev_client_query_by_sysfs_path (udev_client, connection_sysfs_path);
+ if (connection_device == NULL)
+ goto out;
+ }
+
+ ret = TRUE;
+
+ out:
+ g_free (s);
+ g_free (session_sysfs_path);
+ if (session_dir != NULL)
+ g_dir_close (session_dir);
+ g_free (connection_sysfs_path);
+ if (connection_dir != NULL)
+ g_dir_close (connection_dir);
+
+ if (ret)
+ {
+ if (out_session_device != NULL)
+ *out_session_device = session_device;
+ else
+ g_object_unref (session_device);
+
+ if (out_connection_device != NULL)
+ *out_connection_device = connection_device;
+ else
+ g_object_unref (connection_device);
+ }
+ else
+ {
+ if (session_device != NULL)
+ g_object_unref (session_device);
+ if (connection_device != NULL)
+ g_object_unref (connection_device);
+ }
+
+ return ret;
+}
+
+static void
+set_iscsi_target (UDisksLinuxDrive *drive,
+ UDisksDrive *iface,
+ GUdevDevice *device,
+ UDisksDaemon *daemon)
+{
+ GUdevClient *udev_client;
+ GUdevDevice *session_device;
+ GUdevDevice *connection_device;
+
+ /* note: @device may vary - it can be any path for drive */
+ session_device = NULL;
+ connection_device = NULL;
+
+ udisks_drive_set_iscsi_target (iface, "/");
+
+ udev_client = udisks_linux_provider_get_udev_client (udisks_daemon_get_linux_provider (daemon));
+ if (find_iscsi_devices_for_block (udev_client,
+ device,
+ &session_device,
+ &connection_device))
+ {
+ GDBusObjectManagerServer *object_manager;
+ const gchar *target_name;
+ const gchar *target_object_path;
+
+ target_name = g_udev_device_get_sysfs_attr (session_device, "targetname");
+ if (target_name == NULL)
+ {
+ udisks_warning ("Cannot find iSCSI target name for sysfs path %s",
+ g_udev_device_get_sysfs_path (session_device));
+ goto out;
+ }
+
+ object_manager = udisks_daemon_get_object_manager (daemon);
+ target_object_path = find_iscsi_target (object_manager, target_name);
+ if (target_object_path == NULL)
+ {
+ udisks_warning ("Cannot find iSCSI target object for name `%s'",
+ target_name);
+ goto out;
+ }
+ udisks_drive_set_iscsi_target (iface, target_object_path);
+
+ }
+ out:
+ if (connection_device != NULL)
+ g_object_unref (connection_device);
+ if (session_device != NULL)
+ g_object_unref (session_device);
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
static const struct
{
const gchar *udev_property;
@@ -519,6 +720,8 @@ udisks_linux_drive_update (UDisksLinuxDrive *drive,
udisks_drive_set_revision (iface, g_udev_device_get_property (device, "ID_REVISION"));
udisks_drive_set_serial (iface, g_udev_device_get_property (device, "ID_SCSI_SERIAL"));
udisks_drive_set_wwn (iface, g_udev_device_get_property (device, "ID_WWN_WITH_EXTENSION"));
+
+ set_iscsi_target (drive, iface, device, daemon);
}
else if (g_str_has_prefix (g_udev_device_get_name (device), "mmcblk"))
{
diff --git a/src/udiskslinuxprovider.c b/src/udiskslinuxprovider.c
index ed05b99..8129c75 100644
--- a/src/udiskslinuxprovider.c
+++ b/src/udiskslinuxprovider.c
@@ -153,7 +153,7 @@ on_uevent (GUdevClient *client,
static void
udisks_linux_provider_init (UDisksLinuxProvider *provider)
{
- const gchar *subsystems[] = {"block", "iscsi_connection", "scsi", NULL};
+ const gchar *subsystems[] = {"block", "scsi", NULL};
/* get ourselves an udev client */
provider->gudev_client = g_udev_client_new (subsystems);