[gnome-disk-utility/new-ui] Add support for SAS expanders
- From: David Zeuthen <davidz src gnome org>
- To: svn-commits-list gnome org
- Cc:
- Subject: [gnome-disk-utility/new-ui] Add support for SAS expanders
- Date: Mon, 30 Nov 2009 04:27:40 +0000 (UTC)
commit 1180ddef2fddc639a1bb6d874494a82f3fad1a9a
Author: David Zeuthen <davidz redhat com>
Date: Sun Nov 29 23:27:23 2009 -0500
Add support for SAS expanders
src/gdu/Makefile.am | 8 +
src/gdu/gdu-expander.c | 364 ++++++++++++++++++++++++++++++++++++
src/gdu/gdu-expander.h | 78 ++++++++
src/gdu/gdu-hba.c | 23 ++-
src/gdu/gdu-hub.c | 266 ++++++++++++++++++++++++++
src/gdu/gdu-hub.h | 61 ++++++
src/gdu/gdu-pool.c | 338 ++++++++++++++++++++++++++++++++-
src/gdu/gdu-pool.h | 7 +
src/gdu/gdu-private.h | 5 +
src/gdu/gdu-types.h | 2 +
src/gdu/gdu.h | 2 +
src/palimpsest/Makefile.am | 1 +
src/palimpsest/gdu-section-drive.c | 68 ++++++-
src/palimpsest/gdu-section-hub.c | 234 +++++++++++++++++++++++
src/palimpsest/gdu-section-hub.h | 58 ++++++
src/palimpsest/gdu-shell.c | 5 +
16 files changed, 1498 insertions(+), 22 deletions(-)
---
diff --git a/src/gdu/Makefile.am b/src/gdu/Makefile.am
index 7594045..0b7cbc2 100644
--- a/src/gdu/Makefile.am
+++ b/src/gdu/Makefile.am
@@ -4,6 +4,7 @@ BUILT_SOURCES = \
devkit-disks-daemon-glue.h \
devkit-disks-device-glue.h \
devkit-disks-adapter-glue.h \
+ devkit-disks-expander-glue.h \
devkit-disks-port-glue.h \
gdu-marshal.h gdu-marshal.c
@@ -22,6 +23,9 @@ devkit-disks-device-glue.h: /usr/share/dbus-1/interfaces/org.freedesktop.DeviceK
devkit-disks-adapter-glue.h: /usr/share/dbus-1/interfaces/org.freedesktop.DeviceKit.Disks.Adapter.xml Makefile.am
dbus-binding-tool --prefix=devkit_disks_daemon --mode=glib-client --output=devkit-disks-adapter-glue.h /usr/share/dbus-1/interfaces/org.freedesktop.DeviceKit.Disks.Adapter.xml
+devkit-disks-expander-glue.h: /usr/share/dbus-1/interfaces/org.freedesktop.DeviceKit.Disks.Expander.xml Makefile.am
+ dbus-binding-tool --prefix=devkit_disks_daemon --mode=glib-client --output=devkit-disks-expander-glue.h /usr/share/dbus-1/interfaces/org.freedesktop.DeviceKit.Disks.Expander.xml
+
devkit-disks-port-glue.h: /usr/share/dbus-1/interfaces/org.freedesktop.DeviceKit.Disks.Port.xml Makefile.am
dbus-binding-tool --prefix=devkit_disks_daemon --mode=glib-client --output=devkit-disks-port-glue.h /usr/share/dbus-1/interfaces/org.freedesktop.DeviceKit.Disks.Port.xml
@@ -35,6 +39,7 @@ libgduinclude_HEADERS = \
gdu-callbacks.h \
gdu-device.h \
gdu-adapter.h \
+ gdu-expander.h \
gdu-port.h \
gdu-drive.h \
gdu-linux-md-drive.h \
@@ -47,6 +52,7 @@ libgduinclude_HEADERS = \
gdu-volume.h \
gdu-volume-hole.h \
gdu-hba.h \
+ gdu-hub.h \
$(NULL)
libgdu_la_SOURCES = \
@@ -57,6 +63,7 @@ libgdu_la_SOURCES = \
gdu-pool.c gdu-pool.h \
gdu-device.c gdu-device.h \
gdu-adapter.c gdu-adapter.h \
+ gdu-expander.c gdu-expander.h \
gdu-port.c gdu-port.h \
gdu-drive.c gdu-drive.h \
gdu-linux-md-drive.c gdu-linux-md-drive.h \
@@ -67,6 +74,7 @@ libgdu_la_SOURCES = \
gdu-error.c gdu-error.h \
gdu-process.c gdu-process.h \
gdu-hba.c gdu-hba.h \
+ gdu-hub.c gdu-hub.h \
gdu-private.h \
$(BUILT_SOURCES) \
$(NULL)
diff --git a/src/gdu/gdu-expander.c b/src/gdu/gdu-expander.c
new file mode 100644
index 0000000..376df7a
--- /dev/null
+++ b/src/gdu/gdu-expander.c
@@ -0,0 +1,364 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- */
+/* gdu-expander.c
+ *
+ * Copyright (C) 2009 David Zeuthen
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * as published by the Free Software Foundation; either version 2 of
+ * the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+ * 02111-1307, USA.
+ */
+
+#include <config.h>
+#include <stdlib.h>
+#include <string.h>
+#include <glib/gi18n-lib.h>
+#include <dbus/dbus-glib.h>
+#include <time.h>
+#include <unistd.h>
+#include <sys/types.h>
+
+#include "gdu-private.h"
+#include "gdu-pool.h"
+#include "gdu-expander.h"
+#include "devkit-disks-expander-glue.h"
+
+/* --- SUCKY CODE BEGIN --- */
+
+/* This totally sucks; dbus-bindings-tool and dbus-glib should be able
+ * to do this for us.
+ *
+ * TODO: keep in sync with code in tools/devkit-disks in DeviceKit-disks.
+ */
+
+typedef struct
+{
+ gchar *native_path;
+
+ gchar *vendor;
+ gchar *model;
+ gchar *revision;
+ guint num_ports;
+ gchar **upstream_ports;
+ gchar *adapter;
+} ExpanderProperties;
+
+static void
+collect_props (const char *key, const GValue *value, ExpanderProperties *props)
+{
+ gboolean handled = TRUE;
+
+ if (strcmp (key, "NativePath") == 0)
+ props->native_path = g_strdup (g_value_get_string (value));
+
+ else if (strcmp (key, "Vendor") == 0)
+ props->vendor = g_value_dup_string (value);
+ else if (strcmp (key, "Model") == 0)
+ props->model = g_value_dup_string (value);
+ else if (strcmp (key, "Revision") == 0)
+ props->revision = g_value_dup_string (value);
+ else if (strcmp (key, "NumPorts") == 0)
+ props->num_ports = g_value_get_uint (value);
+ else if (strcmp (key, "UpstreamPorts") == 0) {
+ guint n;
+ GPtrArray *object_paths;
+
+ object_paths = g_value_get_boxed (value);
+
+ props->upstream_ports = g_new0 (char *, object_paths->len + 1);
+ for (n = 0; n < object_paths->len; n++)
+ props->upstream_ports[n] = g_strdup (object_paths->pdata[n]);
+ props->upstream_ports[n] = NULL;
+ }
+ else if (strcmp (key, "Adapter") == 0)
+ props->adapter = g_value_dup_boxed (value);
+
+ else
+ handled = FALSE;
+
+ if (!handled)
+ g_warning ("unhandled property '%s'", key);
+}
+
+static void
+expander_properties_free (ExpanderProperties *props)
+{
+ g_free (props->native_path);
+ g_free (props->vendor);
+ g_free (props->model);
+ g_free (props->revision);
+ g_free (props->adapter);
+ g_strfreev (props->upstream_ports);
+ g_free (props);
+}
+
+static ExpanderProperties *
+expander_properties_get (DBusGConnection *bus,
+ const char *object_path)
+{
+ ExpanderProperties *props;
+ GError *error;
+ GHashTable *hash_table;
+ DBusGProxy *prop_proxy;
+ const char *ifname = "org.freedesktop.DeviceKit.Disks.Expander";
+
+ props = g_new0 (ExpanderProperties, 1);
+
+ prop_proxy = dbus_g_proxy_new_for_name (bus,
+ "org.freedesktop.DeviceKit.Disks",
+ object_path,
+ "org.freedesktop.DBus.Properties");
+ error = NULL;
+ if (!dbus_g_proxy_call (prop_proxy,
+ "GetAll",
+ &error,
+ G_TYPE_STRING,
+ ifname,
+ G_TYPE_INVALID,
+ dbus_g_type_get_map ("GHashTable", G_TYPE_STRING, G_TYPE_VALUE),
+ &hash_table,
+ G_TYPE_INVALID)) {
+ g_warning ("Couldn't call GetAll() to get properties for %s: %s", object_path, error->message);
+ g_error_free (error);
+
+ expander_properties_free (props);
+ props = NULL;
+ goto out;
+ }
+
+ g_hash_table_foreach (hash_table, (GHFunc) collect_props, props);
+
+ g_hash_table_unref (hash_table);
+
+#if 0
+ g_print ("----------------------------------------------------------------------\n");
+ g_print ("native_path: %s\n", props->native_path);
+ g_print ("vendor: %s\n", props->vendor);
+ g_print ("model: %s\n", props->model);
+ g_print ("revision: %s\n", props->revision);
+ g_print ("adapter: %s\n", props->adapter);
+ g_print ("num_ports: %d\n", props->num_ports);
+ g_print ("upstream_ports:\n");
+ {
+ guint n;
+ for (n = 0; props->upstream_ports != NULL && props->upstream_ports[n] != NULL; n++) {
+ g_print (" %s\n", props->upstream_ports[n]);
+ }
+ }
+#endif
+
+out:
+ g_object_unref (prop_proxy);
+ return props;
+}
+
+/* --- SUCKY CODE END --- */
+
+struct _GduExpanderPrivate
+{
+ DBusGConnection *bus;
+ DBusGProxy *proxy;
+ GduPool *pool;
+
+ char *object_path;
+
+ ExpanderProperties *props;
+};
+
+enum {
+ CHANGED,
+ REMOVED,
+ LAST_SIGNAL,
+};
+
+static GObjectClass *parent_class = NULL;
+static guint signals[LAST_SIGNAL] = { 0 };
+
+G_DEFINE_TYPE (GduExpander, gdu_expander, G_TYPE_OBJECT);
+
+GduPool *
+gdu_expander_get_pool (GduExpander *expander)
+{
+ return g_object_ref (expander->priv->pool);
+}
+
+static void
+gdu_expander_finalize (GduExpander *expander)
+{
+ g_debug ("##### finalized expander %s",
+ expander->priv->props != NULL ? expander->priv->props->native_path : expander->priv->object_path);
+
+ dbus_g_connection_unref (expander->priv->bus);
+ g_free (expander->priv->object_path);
+ if (expander->priv->proxy != NULL)
+ g_object_unref (expander->priv->proxy);
+ if (expander->priv->pool != NULL)
+ g_object_unref (expander->priv->pool);
+ if (expander->priv->props != NULL)
+ expander_properties_free (expander->priv->props);
+
+ if (G_OBJECT_CLASS (parent_class)->finalize)
+ (* G_OBJECT_CLASS (parent_class)->finalize) (G_OBJECT (expander));
+}
+
+static void
+gdu_expander_class_init (GduExpanderClass *klass)
+{
+ GObjectClass *obj_class = (GObjectClass *) klass;
+
+ parent_class = g_type_class_peek_parent (klass);
+
+ obj_class->finalize = (GObjectFinalizeFunc) gdu_expander_finalize;
+
+ g_type_class_add_private (klass, sizeof (GduExpanderPrivate));
+
+ signals[CHANGED] =
+ g_signal_new ("changed",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (GduExpanderClass, changed),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__VOID,
+ G_TYPE_NONE, 0);
+ signals[REMOVED] =
+ g_signal_new ("removed",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (GduExpanderClass, removed),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__VOID,
+ G_TYPE_NONE, 0);
+}
+
+static void
+gdu_expander_init (GduExpander *expander)
+{
+ expander->priv = G_TYPE_INSTANCE_GET_PRIVATE (expander, GDU_TYPE_EXPANDER, GduExpanderPrivate);
+}
+
+static gboolean
+update_info (GduExpander *expander)
+{
+ ExpanderProperties *new_properties;
+
+ new_properties = expander_properties_get (expander->priv->bus, expander->priv->object_path);
+ if (new_properties != NULL) {
+ if (expander->priv->props != NULL)
+ expander_properties_free (expander->priv->props);
+ expander->priv->props = new_properties;
+ return TRUE;
+ } else {
+ return FALSE;
+ }
+}
+
+
+GduExpander *
+_gdu_expander_new_from_object_path (GduPool *pool, const char *object_path)
+{
+ GError *error;
+ GduExpander *expander;
+
+ expander = GDU_EXPANDER (g_object_new (GDU_TYPE_EXPANDER, NULL));
+ expander->priv->object_path = g_strdup (object_path);
+ expander->priv->pool = g_object_ref (pool);
+
+ error = NULL;
+ expander->priv->bus = dbus_g_bus_get (DBUS_BUS_SYSTEM, &error);
+ if (expander->priv->bus == NULL) {
+ g_warning ("Couldn't connect to system bus: %s", error->message);
+ g_error_free (error);
+ goto error;
+ }
+
+ expander->priv->proxy = dbus_g_proxy_new_for_name (expander->priv->bus,
+ "org.freedesktop.DeviceKit.Disks",
+ expander->priv->object_path,
+ "org.freedesktop.DeviceKit.Disks.Expander");
+ dbus_g_proxy_set_default_timeout (expander->priv->proxy, INT_MAX);
+ dbus_g_proxy_add_signal (expander->priv->proxy, "Changed", G_TYPE_INVALID);
+
+ /* TODO: connect signals */
+
+ if (!update_info (expander))
+ goto error;
+
+ g_debug ("_gdu_expander_new_from_object_path: %s", expander->priv->props->native_path);
+
+ return expander;
+error:
+ g_object_unref (expander);
+ return NULL;
+}
+
+gboolean
+_gdu_expander_changed (GduExpander *expander)
+{
+ g_debug ("_gdu_expander_changed: %s", expander->priv->props->native_path);
+ if (update_info (expander)) {
+ g_signal_emit (expander, signals[CHANGED], 0);
+ return TRUE;
+ } else {
+ return FALSE;
+ }
+}
+
+const gchar *
+gdu_expander_get_object_path (GduExpander *expander)
+{
+ return expander->priv->object_path;
+}
+
+
+const gchar *
+gdu_expander_get_native_path (GduExpander *expander)
+{
+ return expander->priv->props->native_path;
+}
+
+const gchar *
+gdu_expander_get_vendor (GduExpander *expander)
+{
+ return expander->priv->props->vendor;
+}
+
+const gchar *
+gdu_expander_get_model (GduExpander *expander)
+{
+ return expander->priv->props->model;
+}
+
+const gchar *
+gdu_expander_get_revision (GduExpander *expander)
+{
+ return expander->priv->props->revision;
+}
+
+guint
+gdu_expander_get_num_ports (GduExpander *expander)
+{
+ return expander->priv->props->num_ports;
+}
+
+gchar **
+gdu_expander_get_upstream_ports (GduExpander *expander)
+{
+ return expander->priv->props->upstream_ports;
+}
+
+const gchar *
+gdu_expander_get_adapter (GduExpander *expander)
+{
+ return expander->priv->props->adapter;
+}
+
diff --git a/src/gdu/gdu-expander.h b/src/gdu/gdu-expander.h
new file mode 100644
index 0000000..0749002
--- /dev/null
+++ b/src/gdu/gdu-expander.h
@@ -0,0 +1,78 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- */
+/* gdu-expander.h
+ *
+ * Copyright (C) 2009 David Zeuthen
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * as published by the Free Software Foundation; either version 2 of
+ * the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+ * 02111-1307, USA.
+ */
+
+#if !defined (__GDU_INSIDE_GDU_H) && !defined (GDU_COMPILATION)
+#error "Only <gdu/gdu.h> can be included directly, this file may disappear or change contents."
+#endif
+
+#ifndef __GDU_EXPANDER_H
+#define __GDU_EXPANDER_H
+
+#include <unistd.h>
+#include <sys/types.h>
+
+#include <gdu/gdu-types.h>
+#include <gdu/gdu-callbacks.h>
+
+G_BEGIN_DECLS
+
+#define GDU_TYPE_EXPANDER (gdu_expander_get_type ())
+#define GDU_EXPANDER(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), GDU_TYPE_EXPANDER, GduExpander))
+#define GDU_EXPANDER_CLASS(k) (G_TYPE_CHECK_CLASS_CAST ((k), GDU_EXPANDER, GduExpanderClass))
+#define GDU_IS_EXPANDER(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), GDU_TYPE_EXPANDER))
+#define GDU_IS_EXPANDER_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), GDU_TYPE_EXPANDER))
+#define GDU_EXPANDER_GET_CLASS(k) (G_TYPE_INSTANCE_GET_CLASS ((k), GDU_TYPE_EXPANDER, GduExpanderClass))
+
+typedef struct _GduExpanderClass GduExpanderClass;
+typedef struct _GduExpanderPrivate GduExpanderPrivate;
+
+struct _GduExpander
+{
+ GObject parent;
+
+ /* private */
+ GduExpanderPrivate *priv;
+};
+
+struct _GduExpanderClass
+{
+ GObjectClass parent_class;
+
+ /* signals */
+ void (*changed) (GduExpander *expander);
+ void (*removed) (GduExpander *expander);
+};
+
+GType gdu_expander_get_type (void);
+const char *gdu_expander_get_object_path (GduExpander *expander);
+GduPool *gdu_expander_get_pool (GduExpander *expander);
+
+const gchar *gdu_expander_get_native_path (GduExpander *expander);
+const gchar *gdu_expander_get_vendor (GduExpander *expander);
+const gchar *gdu_expander_get_model (GduExpander *expander);
+const gchar *gdu_expander_get_revision (GduExpander *expander);
+guint gdu_expander_get_num_ports (GduExpander *expander);
+gchar **gdu_expander_get_upstream_ports (GduExpander *expander);
+const gchar *gdu_expander_get_adapter (GduExpander *expander);
+
+G_END_DECLS
+
+#endif /* __GDU_EXPANDER_H */
diff --git a/src/gdu/gdu-hba.c b/src/gdu/gdu-hba.c
index 74d27e7..40b7675 100644
--- a/src/gdu/gdu-hba.c
+++ b/src/gdu/gdu-hba.c
@@ -143,8 +143,27 @@ gdu_hba_get_enclosing_presentable (GduPresentable *presentable)
static char *
gdu_hba_get_name (GduPresentable *presentable)
{
- /* TODO: include type e.g. SATA-I, SATA-II, SAS etc */
- return g_strdup (_("Host Adapter"));
+ GduHba *hba = GDU_HBA (presentable);
+ const gchar *fabric;
+ gchar *fabric_str;
+
+ fabric = gdu_adapter_get_fabric (hba->priv->adapter);
+
+ if (g_str_has_prefix (fabric, "ata_pata")) {
+ fabric_str = g_strdup ("PATA Host Adapter");
+ } else if (g_str_has_prefix (fabric, "ata_sata")) {
+ fabric_str = g_strdup ("SATA Host Adapter");
+ } else if (g_str_has_prefix (fabric, "ata")) {
+ fabric_str = g_strdup ("ATA Host Adapter");
+ } else if (g_str_has_prefix (fabric, "scsi_sas")) {
+ fabric_str = g_strdup ("SAS Host Adapter");
+ } else if (g_str_has_prefix (fabric, "scsi")) {
+ fabric_str = g_strdup ("SCSI Host Adapter");
+ } else {
+ fabric_str = g_strdup ("Host Adapter");
+ }
+
+ return fabric_str;
}
static gchar *
diff --git a/src/gdu/gdu-hub.c b/src/gdu/gdu-hub.c
new file mode 100644
index 0000000..0986d74
--- /dev/null
+++ b/src/gdu/gdu-hub.c
@@ -0,0 +1,266 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- */
+/* gdu-hub.c
+ *
+ * Copyright (C) 2007 David Zeuthen
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * as published by the Free Software Foundation; either version 2 of
+ * the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+ * 02111-1307, USA.
+ */
+
+#include <config.h>
+#include <string.h>
+#include <glib/gi18n-lib.h>
+#include <dbus/dbus-glib.h>
+#include <stdlib.h>
+
+#include "gdu-private.h"
+#include "gdu-util.h"
+#include "gdu-pool.h"
+#include "gdu-expander.h"
+#include "gdu-hub.h"
+#include "gdu-presentable.h"
+#include "gdu-linux-md-drive.h"
+
+/**
+ * SECTION:gdu-hub
+ * @title: GduHub
+ * @short_description: HUBs
+ *
+ * #GduHub objects are used to represent host board expanders (also
+ * called disk expanders).
+ *
+ * See the documentation for #GduPresentable for the big picture.
+ */
+
+struct _GduHubPrivate
+{
+ GduExpander *expander;
+ GduPool *pool;
+ GduPresentable *enclosing_presentable;
+ gchar *id;
+};
+
+static GObjectClass *parent_class = NULL;
+
+static void gdu_hub_presentable_iface_init (GduPresentableIface *iface);
+G_DEFINE_TYPE_WITH_CODE (GduHub, gdu_hub, G_TYPE_OBJECT,
+ G_IMPLEMENT_INTERFACE (GDU_TYPE_PRESENTABLE,
+ gdu_hub_presentable_iface_init))
+
+static void expander_changed (GduExpander *expander, gpointer user_data);
+
+
+static void
+gdu_hub_finalize (GObject *object)
+{
+ GduHub *hub = GDU_HUB (object);
+
+ //g_debug ("##### finalized hub '%s' %p", hub->priv->id, hub);
+
+ if (hub->priv->expander != NULL) {
+ g_signal_handlers_disconnect_by_func (hub->priv->expander, expander_changed, hub);
+ g_object_unref (hub->priv->expander);
+ }
+
+ if (hub->priv->pool != NULL)
+ g_object_unref (hub->priv->pool);
+
+ if (hub->priv->enclosing_presentable != NULL)
+ g_object_unref (hub->priv->enclosing_presentable);
+
+ g_free (hub->priv->id);
+
+ if (G_OBJECT_CLASS (parent_class)->finalize != NULL)
+ (* G_OBJECT_CLASS (parent_class)->finalize) (object);
+}
+
+static void
+gdu_hub_class_init (GduHubClass *klass)
+{
+ GObjectClass *obj_class = (GObjectClass *) klass;
+
+ parent_class = g_type_class_peek_parent (klass);
+
+ obj_class->finalize = gdu_hub_finalize;
+
+ g_type_class_add_private (klass, sizeof (GduHubPrivate));
+}
+
+static void
+gdu_hub_init (GduHub *hub)
+{
+ hub->priv = G_TYPE_INSTANCE_GET_PRIVATE (hub, GDU_TYPE_HUB, GduHubPrivate);
+}
+
+static void
+expander_changed (GduExpander *expander, gpointer user_data)
+{
+ GduHub *hub = GDU_HUB (user_data);
+ g_signal_emit_by_name (hub, "changed");
+ g_signal_emit_by_name (hub->priv->pool, "presentable-changed", hub);
+}
+
+GduHub *
+_gdu_hub_new_from_expander (GduPool *pool, GduExpander *expander, GduPresentable *enclosing_presentable)
+{
+ GduHub *hub;
+
+ hub = GDU_HUB (g_object_new (GDU_TYPE_HUB, NULL));
+ hub->priv->expander = g_object_ref (expander);
+ hub->priv->pool = g_object_ref (pool);
+ hub->priv->enclosing_presentable =
+ enclosing_presentable != NULL ? g_object_ref (enclosing_presentable) : NULL;
+ hub->priv->id = g_strdup (gdu_expander_get_native_path (hub->priv->expander));
+ g_signal_connect (expander, "changed", (GCallback) expander_changed, hub);
+ return hub;
+}
+
+static const gchar *
+gdu_hub_get_id (GduPresentable *presentable)
+{
+ GduHub *hub = GDU_HUB (presentable);
+ return hub->priv->id;
+}
+
+static GduDevice *
+gdu_hub_get_device (GduPresentable *presentable)
+{
+ return NULL;
+}
+
+static GduPresentable *
+gdu_hub_get_enclosing_presentable (GduPresentable *presentable)
+{
+ GduHub *hub = GDU_HUB (presentable);
+ if (hub->priv->enclosing_presentable != NULL)
+ return g_object_ref (hub->priv->enclosing_presentable);
+ return NULL;
+}
+
+static char *
+gdu_hub_get_name (GduPresentable *presentable)
+{
+ /* TODO: include type e.g. SATA Port Multiplier, SAS Expander etc */
+ return g_strdup (_("SAS Expander"));
+}
+
+static gchar *
+gdu_hub_get_vpd_name (GduPresentable *presentable)
+{
+ GduHub *hub = GDU_HUB (presentable);
+ gchar *s;
+ const gchar *vendor;
+ const gchar *model;
+
+ vendor = gdu_expander_get_vendor (hub->priv->expander);
+ model = gdu_expander_get_model (hub->priv->expander);
+ s = g_strdup_printf ("%s %s", vendor, model);
+ return s;
+}
+
+static gchar *
+gdu_hub_get_description (GduPresentable *presentable)
+{
+ /* TODO: include number of ports, speed, receptable type etc. */
+ return gdu_hub_get_vpd_name (presentable);
+}
+
+static GIcon *
+gdu_hub_get_icon (GduPresentable *presentable)
+{
+ GIcon *icon;
+ icon = g_themed_icon_new_with_default_fallbacks ("gdu-hba"); /* TODO */
+ return icon;
+}
+
+static guint64
+gdu_hub_get_offset (GduPresentable *presentable)
+{
+ return 0;
+}
+
+static guint64
+gdu_hub_get_size (GduPresentable *presentable)
+{
+ return 0;
+}
+
+static GduPool *
+gdu_hub_get_pool (GduPresentable *presentable)
+{
+ GduHub *hub = GDU_HUB (presentable);
+ return gdu_expander_get_pool (hub->priv->expander);
+}
+
+static gboolean
+gdu_hub_is_allocated (GduPresentable *presentable)
+{
+ return FALSE;
+}
+
+static gboolean
+gdu_hub_is_recognized (GduPresentable *presentable)
+{
+ return FALSE;
+}
+
+GduExpander *
+gdu_hub_get_expander (GduHub *hub)
+{
+ return g_object_ref (hub->priv->expander);
+}
+
+static void
+gdu_hub_presentable_iface_init (GduPresentableIface *iface)
+{
+ iface->get_id = gdu_hub_get_id;
+ iface->get_device = gdu_hub_get_device;
+ iface->get_enclosing_presentable = gdu_hub_get_enclosing_presentable;
+ iface->get_name = gdu_hub_get_name;
+ iface->get_description = gdu_hub_get_description;
+ iface->get_vpd_name = gdu_hub_get_vpd_name;
+ iface->get_icon = gdu_hub_get_icon;
+ iface->get_offset = gdu_hub_get_offset;
+ iface->get_size = gdu_hub_get_size;
+ iface->get_pool = gdu_hub_get_pool;
+ iface->is_allocated = gdu_hub_is_allocated;
+ iface->is_recognized = gdu_hub_is_recognized;
+}
+
+void
+_gdu_hub_rewrite_enclosing_presentable (GduHub *hub)
+{
+ if (hub->priv->enclosing_presentable != NULL) {
+ const gchar *enclosing_presentable_id;
+ GduPresentable *new_enclosing_presentable;
+
+ enclosing_presentable_id = gdu_presentable_get_id (hub->priv->enclosing_presentable);
+
+ new_enclosing_presentable = gdu_pool_get_presentable_by_id (hub->priv->pool,
+ enclosing_presentable_id);
+ if (new_enclosing_presentable == NULL) {
+ g_warning ("Error rewriting enclosing_presentable for %s, no such id %s",
+ hub->priv->id,
+ enclosing_presentable_id);
+ goto out;
+ }
+
+ g_object_unref (hub->priv->enclosing_presentable);
+ hub->priv->enclosing_presentable = new_enclosing_presentable;
+ }
+
+ out:
+ ;
+}
diff --git a/src/gdu/gdu-hub.h b/src/gdu/gdu-hub.h
new file mode 100644
index 0000000..8d65735
--- /dev/null
+++ b/src/gdu/gdu-hub.h
@@ -0,0 +1,61 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- */
+/* gdu-hub.h
+ *
+ * Copyright (C) 2009 David Zeuthen
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * as published by the Free Software Foundation; either version 2 of
+ * the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+ * 02111-1307, USA.
+ */
+
+#if !defined (__GDU_INSIDE_GDU_H) && !defined (GDU_COMPILATION)
+#error "Only <gdu/gdu.h> can be included directly, this file may disappear or change contents."
+#endif
+
+#ifndef __GDU_HUB_H
+#define __GDU_HUB_H
+
+#include <gdu/gdu-types.h>
+
+G_BEGIN_DECLS
+
+#define GDU_TYPE_HUB (gdu_hub_get_type ())
+#define GDU_HUB(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), GDU_TYPE_HUB, GduHub))
+#define GDU_HUB_CLASS(k) (G_TYPE_CHECK_CLASS_CAST ((k), GDU_HUB, GduHubClass))
+#define GDU_IS_HUB(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), GDU_TYPE_HUB))
+#define GDU_IS_HUB_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), GDU_TYPE_HUB))
+#define GDU_HUB_GET_CLASS(k) (G_TYPE_INSTANCE_GET_CLASS ((k), GDU_TYPE_HUB, GduHubClass))
+
+typedef struct _GduHubClass GduHubClass;
+typedef struct _GduHubPrivate GduHubPrivate;
+
+struct _GduHub
+{
+ GObject parent;
+
+ /* private */
+ GduHubPrivate *priv;
+};
+
+struct _GduHubClass
+{
+ GObjectClass parent_class;
+};
+
+GType gdu_hub_get_type (void);
+GduExpander *gdu_hub_get_expander (GduHub *hub);
+
+G_END_DECLS
+
+#endif /* __GDU_HUB_H */
diff --git a/src/gdu/gdu-pool.c b/src/gdu/gdu-pool.c
index 69c1c5f..5903374 100644
--- a/src/gdu/gdu-pool.c
+++ b/src/gdu/gdu-pool.c
@@ -28,11 +28,14 @@
#include "gdu-presentable.h"
#include "gdu-device.h"
#include "gdu-adapter.h"
+#include "gdu-expander.h"
#include "gdu-port.h"
#include "gdu-drive.h"
#include "gdu-linux-md-drive.h"
#include "gdu-volume.h"
#include "gdu-volume-hole.h"
+#include "gdu-hba.h"
+#include "gdu-hub.h"
#include "gdu-known-filesystem.h"
#include "gdu-private.h"
@@ -55,6 +58,9 @@ enum {
ADAPTER_ADDED,
ADAPTER_REMOVED,
ADAPTER_CHANGED,
+ EXPANDER_ADDED,
+ EXPANDER_REMOVED,
+ EXPANDER_CHANGED,
PORT_ADDED,
PORT_REMOVED,
PORT_CHANGED,
@@ -86,6 +92,9 @@ struct _GduPoolPrivate
/* the current set of adapters we know about */
GHashTable *object_path_to_adapter;
+ /* the current set of expanders we know about */
+ GHashTable *object_path_to_expander;
+
/* the current set of ports we know about */
GHashTable *object_path_to_port;
};
@@ -107,6 +116,8 @@ gdu_pool_finalize (GduPool *pool)
g_hash_table_unref (pool->priv->object_path_to_adapter);
+ g_hash_table_unref (pool->priv->object_path_to_expander);
+
g_hash_table_unref (pool->priv->object_path_to_port);
g_list_foreach (pool->priv->presentables, (GFunc) g_object_unref, NULL);
@@ -249,6 +260,58 @@ gdu_pool_class_init (GduPoolClass *klass)
GDU_TYPE_ADAPTER);
/**
+ * GduPool::expander-added
+ * @pool: The #GduPool emitting the signal.
+ * @expander: The #GduExpander that was added.
+ *
+ * Emitted when @expander is added to @pool.
+ **/
+ signals[EXPANDER_ADDED] =
+ g_signal_new ("expander-added",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (GduPoolClass, expander_added),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__OBJECT,
+ G_TYPE_NONE, 1,
+ GDU_TYPE_EXPANDER);
+
+ /**
+ * GduPool::expander-removed
+ * @pool: The #GduPool emitting the signal.
+ * @expander: The #GduExpander that was removed.
+ *
+ * Emitted when @expander is removed from @pool. Recipients
+ * should release references to @expander.
+ **/
+ signals[EXPANDER_REMOVED] =
+ g_signal_new ("expander-removed",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (GduPoolClass, expander_removed),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__OBJECT,
+ G_TYPE_NONE, 1,
+ GDU_TYPE_EXPANDER);
+
+ /**
+ * GduPool::expander-changed
+ * @pool: The #GduPool emitting the signal.
+ * @expander: A #GduExpander.
+ *
+ * Emitted when @expander is changed.
+ **/
+ signals[EXPANDER_CHANGED] =
+ g_signal_new ("expander-changed",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (GduPoolClass, expander_changed),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__OBJECT,
+ G_TYPE_NONE, 1,
+ GDU_TYPE_EXPANDER);
+
+ /**
* GduPool::port-added
* @pool: The #GduPool emitting the signal.
* @port: The #GduPort that was added.
@@ -414,9 +477,14 @@ gdu_pool_init (GduPool *pool)
g_object_unref);
pool->priv->object_path_to_adapter = g_hash_table_new_full (g_str_hash,
- g_str_equal,
- NULL,
- g_object_unref);
+ g_str_equal,
+ NULL,
+ g_object_unref);
+
+ pool->priv->object_path_to_expander = g_hash_table_new_full (g_str_hash,
+ g_str_equal,
+ NULL,
+ g_object_unref);
pool->priv->object_path_to_port = g_hash_table_new_full (g_str_hash,
g_str_equal,
@@ -738,6 +806,7 @@ recompute_presentables (GduPool *pool)
GList *l;
GList *devices;
GList *adapters;
+ GList *expanders;
GList *new_partitioned_drives;
GList *new_presentables;
GList *added_presentables;
@@ -745,6 +814,7 @@ recompute_presentables (GduPool *pool)
GHashTable *hash_map_from_drive_to_extended_partition;
GHashTable *hash_map_from_linux_md_uuid_to_drive;
GHashTable *hash_map_from_adapter_objpath_to_hba;
+ GHashTable *hash_map_from_expander_objpath_to_hub;
/* The general strategy for (re-)computing presentables is rather brute force; we
* compute the complete set of presentables every time and diff it against the
@@ -774,6 +844,11 @@ recompute_presentables (GduPool *pool)
NULL,
NULL);
+ hash_map_from_expander_objpath_to_hub = g_hash_table_new_full (g_str_hash,
+ g_str_equal,
+ NULL,
+ NULL);
+
/* First add all HBAs */
adapters = gdu_pool_get_adapters (pool);
for (l = adapters; l != NULL; l = l->next) {
@@ -789,6 +864,47 @@ recompute_presentables (GduPool *pool)
new_presentables = g_list_prepend (new_presentables, hba);
} /* for all adapters */
+ /* Then all expanders */
+ expanders = gdu_pool_get_expanders (pool);
+ for (l = expanders; l != NULL; l = l->next) {
+ GduExpander *expander = GDU_EXPANDER (l->data);
+ GduHub *hub;
+ gchar **port_object_paths;
+ GduPresentable *expander_parent;
+
+ /* we are guaranteed that upstream ports all stem from the same expander or
+ * host adapter - so just pick the first one */
+ expander_parent = NULL;
+ port_object_paths = gdu_expander_get_upstream_ports (expander);
+ if (port_object_paths != NULL && port_object_paths[0] != NULL) {
+ GduPort *port;
+
+ port = gdu_pool_get_port_by_object_path (pool, port_object_paths[0]);
+
+ /* For now, always choose the adapter as the parent - this is *probably*
+ * the right thing (e.g. what people expect) to do _anyway_ because of
+ * the way expanders are daisy-chained
+ */
+ if (port != NULL) {
+ const gchar *adapter_object_path;
+ adapter_object_path = gdu_port_get_adapter (port);
+ expander_parent = g_hash_table_lookup (hash_map_from_adapter_objpath_to_hba,
+ adapter_object_path);
+ g_object_unref (port);
+ }
+ }
+
+ g_warn_if_fail (expander_parent != NULL);
+
+ hub = _gdu_hub_new_from_expander (pool, expander, expander_parent);
+
+ g_hash_table_insert (hash_map_from_expander_objpath_to_hub,
+ (gpointer) gdu_expander_get_object_path (expander),
+ hub);
+
+ new_presentables = g_list_prepend (new_presentables, hub);
+ } /* for all expanders */
+
/* TODO: Ensure that pool->priv->devices is in topological sort order, then just loop
* through it and handle devices sequentially.
*
@@ -838,17 +954,46 @@ recompute_presentables (GduPool *pool)
} else {
+ GduPresentable *drive_parent;
+
+ drive_parent = NULL;
+#if 1
+ gchar **port_object_paths;
+
+ /* we are guaranteed that upstream ports all stem from the same expander or
+ * host adapter - so just pick the first one */
+ port_object_paths = gdu_device_drive_get_ports (device);
+ if (port_object_paths != NULL && port_object_paths[0] != NULL) {
+ GduPort *port;
+
+ port = gdu_pool_get_port_by_object_path (pool, port_object_paths[0]);
+ /* choose the expander, if available, otherwise the adapter */
+ if (port != NULL) {
+ const gchar *parent_object_path;
+ const gchar *adapter_object_path;
+
+ parent_object_path = gdu_port_get_parent (port);
+ adapter_object_path = gdu_port_get_adapter (port);
+ if (g_strcmp0 (parent_object_path, adapter_object_path) != 0) {
+ drive_parent = g_hash_table_lookup (hash_map_from_expander_objpath_to_hub,
+ parent_object_path);
+ } else {
+ drive_parent = g_hash_table_lookup (hash_map_from_adapter_objpath_to_hba,
+ adapter_object_path);
+ }
+ g_object_unref (port);
+ }
+ }
+#else
const gchar *adapter_objpath;
- GduPresentable *hba;
-
- hba = NULL;
adapter_objpath = gdu_device_drive_get_adapter (device);
if (adapter_objpath != NULL)
- hba = g_hash_table_lookup (hash_map_from_adapter_objpath_to_hba,
- adapter_objpath);
+ drive_parent = g_hash_table_lookup (hash_map_from_adapter_objpath_to_hba,
+ adapter_objpath);
+#endif
- drive = _gdu_drive_new_from_device (pool, device, hba);
+ drive = _gdu_drive_new_from_device (pool, device, drive_parent);
}
new_presentables = g_list_prepend (new_presentables, drive);
@@ -981,6 +1126,7 @@ recompute_presentables (GduPool *pool)
g_hash_table_unref (hash_map_from_drive_to_extended_partition);
g_hash_table_unref (hash_map_from_linux_md_uuid_to_drive);
g_hash_table_unref (hash_map_from_adapter_objpath_to_hba);
+ g_hash_table_unref (hash_map_from_expander_objpath_to_hub);
/* figure out the diff */
new_presentables = g_list_sort (new_presentables, (GCompareFunc) gdu_presentable_compare);
@@ -1013,7 +1159,9 @@ recompute_presentables (GduPool *pool)
/* rewrite all enclosing_presentable references for presentables we are going to add
* such that they really refer to presentables _previously_ added
*/
- if (GDU_IS_DRIVE (p))
+ if (GDU_IS_HUB (p))
+ _gdu_hub_rewrite_enclosing_presentable (GDU_HUB (p));
+ else if (GDU_IS_DRIVE (p))
_gdu_drive_rewrite_enclosing_presentable (GDU_DRIVE (p));
else if (GDU_IS_VOLUME (p))
_gdu_volume_rewrite_enclosing_presentable (GDU_VOLUME (p));
@@ -1038,6 +1186,8 @@ recompute_presentables (GduPool *pool)
g_list_free (devices);
g_list_foreach (adapters, (GFunc) g_object_unref, NULL);
g_list_free (adapters);
+ g_list_foreach (expanders, (GFunc) g_object_unref, NULL);
+ g_list_free (expanders);
}
/* ---------------------------------------------------------------------------------------------------- */
@@ -1250,6 +1400,96 @@ adapter_changed_signal_handler (DBusGProxy *proxy, const char *object_path, gpoi
/* ---------------------------------------------------------------------------------------------------- */
static void
+expander_changed_signal_handler (DBusGProxy *proxy, const char *object_path, gpointer user_data);
+
+static void
+expander_added_signal_handler (DBusGProxy *proxy, const char *object_path, gpointer user_data)
+{
+ GduPool *pool;
+ GduExpander *expander;
+
+ pool = GDU_POOL (user_data);
+
+ expander = gdu_pool_get_expander_by_object_path (pool, object_path);
+ if (expander != NULL) {
+ g_object_unref (expander);
+ g_warning ("Treating add for previously added expander %s as change", object_path);
+ expander_changed_signal_handler (proxy, object_path, user_data);
+ goto out;
+ }
+
+ expander = _gdu_expander_new_from_object_path (pool, object_path);
+ if (expander == NULL)
+ goto out;
+
+ g_hash_table_insert (pool->priv->object_path_to_expander,
+ (gpointer) gdu_expander_get_object_path (expander),
+ expander);
+ g_signal_emit (pool, signals[EXPANDER_ADDED], 0, expander);
+ //g_debug ("Added expander %s", object_path);
+
+ recompute_presentables (pool);
+
+ out:
+ ;
+}
+
+static void
+expander_removed_signal_handler (DBusGProxy *proxy, const char *object_path, gpointer user_data)
+{
+ GduPool *pool;
+ GduExpander *expander;
+
+ pool = GDU_POOL (user_data);
+
+ expander = gdu_pool_get_expander_by_object_path (pool, object_path);
+ if (expander == NULL) {
+ g_warning ("No expander to remove for remove %s", object_path);
+ goto out;
+ }
+
+ g_hash_table_remove (pool->priv->object_path_to_expander,
+ gdu_expander_get_object_path (expander));
+ g_signal_emit (pool, signals[EXPANDER_REMOVED], 0, expander);
+ g_signal_emit_by_name (expander, "removed");
+ g_object_unref (expander);
+ g_debug ("Removed expander %s", object_path);
+
+ recompute_presentables (pool);
+
+ out:
+ ;
+}
+
+static void
+expander_changed_signal_handler (DBusGProxy *proxy, const char *object_path, gpointer user_data)
+{
+ GduPool *pool;
+ GduExpander *expander;
+
+ pool = GDU_POOL (user_data);
+
+ expander = gdu_pool_get_expander_by_object_path (pool, object_path);
+ if (expander == NULL) {
+ g_warning ("Ignoring change event on non-existant expander %s", object_path);
+ goto out;
+ }
+
+ if (_gdu_expander_changed (expander)) {
+ g_signal_emit (pool, signals[EXPANDER_CHANGED], 0, expander);
+ g_signal_emit_by_name (expander, "changed");
+ }
+ g_object_unref (expander);
+
+ recompute_presentables (pool);
+
+ out:
+ ;
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static void
port_changed_signal_handler (DBusGProxy *proxy, const char *object_path, gpointer user_data);
static void
@@ -1420,6 +1660,7 @@ gdu_pool_new (void)
int n;
GPtrArray *devices;
GPtrArray *adapters;
+ GPtrArray *expanders;
GPtrArray *ports;
GduPool *pool;
GError *error;
@@ -1481,6 +1722,16 @@ gdu_pool_new (void)
dbus_g_proxy_connect_signal (pool->priv->proxy, "AdapterChanged",
G_CALLBACK (adapter_changed_signal_handler), pool, NULL);
+ dbus_g_proxy_add_signal (pool->priv->proxy, "ExpanderAdded", DBUS_TYPE_G_OBJECT_PATH, G_TYPE_INVALID);
+ dbus_g_proxy_add_signal (pool->priv->proxy, "ExpanderRemoved", DBUS_TYPE_G_OBJECT_PATH, G_TYPE_INVALID);
+ dbus_g_proxy_add_signal (pool->priv->proxy, "ExpanderChanged", DBUS_TYPE_G_OBJECT_PATH, G_TYPE_INVALID);
+ dbus_g_proxy_connect_signal (pool->priv->proxy, "ExpanderAdded",
+ G_CALLBACK (expander_added_signal_handler), pool, NULL);
+ dbus_g_proxy_connect_signal (pool->priv->proxy, "ExpanderRemoved",
+ G_CALLBACK (expander_removed_signal_handler), pool, NULL);
+ dbus_g_proxy_connect_signal (pool->priv->proxy, "ExpanderChanged",
+ G_CALLBACK (expander_changed_signal_handler), pool, NULL);
+
dbus_g_proxy_add_signal (pool->priv->proxy, "PortAdded", DBUS_TYPE_G_OBJECT_PATH, G_TYPE_INVALID);
dbus_g_proxy_add_signal (pool->priv->proxy, "PortRemoved", DBUS_TYPE_G_OBJECT_PATH, G_TYPE_INVALID);
dbus_g_proxy_add_signal (pool->priv->proxy, "PortChanged", DBUS_TYPE_G_OBJECT_PATH, G_TYPE_INVALID);
@@ -1544,6 +1795,28 @@ gdu_pool_new (void)
g_ptr_array_foreach (adapters, (GFunc) g_free, NULL);
g_ptr_array_free (adapters, TRUE);
+ /* prime the list of expanders */
+ error = NULL;
+ if (!org_freedesktop_DeviceKit_Disks_enumerate_expanders (pool->priv->proxy, &expanders, &error)) {
+ g_warning ("Couldn't enumerate expanders: %s", error->message);
+ g_error_free (error);
+ goto error;
+ }
+ for (n = 0; n < (int) expanders->len; n++) {
+ const char *object_path;
+ GduExpander *expander;
+
+ object_path = expanders->pdata[n];
+
+ expander = _gdu_expander_new_from_object_path (pool, object_path);
+
+ g_hash_table_insert (pool->priv->object_path_to_expander,
+ (gpointer) gdu_expander_get_object_path (expander),
+ expander);
+ }
+ g_ptr_array_foreach (expanders, (GFunc) g_free, NULL);
+ g_ptr_array_free (expanders, TRUE);
+
/* prime the list of ports */
error = NULL;
if (!org_freedesktop_DeviceKit_Disks_enumerate_ports (pool->priv->proxy, &ports, &error)) {
@@ -1599,7 +1872,7 @@ gdu_pool_get_by_object_path (GduPool *pool, const char *object_path)
}
/**
- * gdu_pool_get_by_object_path:
+ * gdu_pool_get_adapter_by_object_path:
* @pool: the pool
* @object_path: the D-Bus object path
*
@@ -1621,6 +1894,28 @@ gdu_pool_get_adapter_by_object_path (GduPool *pool, const char *object_path)
}
/**
+ * gdu_pool_get_expander_by_object_path:
+ * @pool: the pool
+ * @object_path: the D-Bus object path
+ *
+ * Looks up #GduExpander object for @object_path.
+ *
+ * Returns: A #GduExpander object for @object_path, otherwise
+ * #NULL. Caller must unref this object using g_object_unref().
+ **/
+GduExpander *
+gdu_pool_get_expander_by_object_path (GduPool *pool, const char *object_path)
+{
+ GduExpander *ret;
+
+ ret = g_hash_table_lookup (pool->priv->object_path_to_expander, object_path);
+ if (ret != NULL) {
+ g_object_ref (ret);
+ }
+ return ret;
+}
+
+/**
* gdu_pool_get_by_object_path:
* @pool: the pool
* @object_path: the D-Bus object path
@@ -1846,6 +2141,27 @@ gdu_pool_get_adapters (GduPool *pool)
}
/**
+ * gdu_pool_get_expanders:
+ * @pool: A #GduPool.
+ *
+ * Get a list of all expanders.
+ *
+ * Returns: A #GList of #GduExpander objects. Caller must free this
+ * (unref all objects, then use g_list_free()).
+ **/
+GList *
+gdu_pool_get_expanders (GduPool *pool)
+{
+ GList *ret;
+
+ ret = NULL;
+
+ ret = g_hash_table_get_values (pool->priv->object_path_to_expander);
+ g_list_foreach (ret, (GFunc) g_object_ref, NULL);
+ return ret;
+}
+
+/**
* gdu_pool_get_ports:
* @pool: A #GduPool.
*
diff --git a/src/gdu/gdu-pool.h b/src/gdu/gdu-pool.h
index a44a500..532a5d8 100644
--- a/src/gdu/gdu-pool.h
+++ b/src/gdu/gdu-pool.h
@@ -63,6 +63,10 @@ struct _GduPoolClass
void (*adapter_removed) (GduPool *pool, GduAdapter *adapter);
void (*adapter_changed) (GduPool *pool, GduAdapter *adapter);
+ void (*expander_added) (GduPool *pool, GduExpander *expander);
+ void (*expander_removed) (GduPool *pool, GduExpander *expander);
+ void (*expander_changed) (GduPool *pool, GduExpander *expander);
+
void (*port_added) (GduPool *pool, GduPort *port);
void (*port_removed) (GduPool *pool, GduPort *port);
void (*port_changed) (GduPool *pool, GduPort *port);
@@ -98,6 +102,9 @@ GList *gdu_pool_get_enclosed_presentables (GduPool *pool, GduPresentable *p
GduAdapter *gdu_pool_get_adapter_by_object_path (GduPool *pool, const char *object_path);
GList *gdu_pool_get_adapters (GduPool *pool);
+GduExpander *gdu_pool_get_expander_by_object_path (GduPool *pool, const char *object_path);
+GList *gdu_pool_get_expanders (GduPool *pool);
+
GduPort *gdu_pool_get_port_by_object_path (GduPool *pool, const char *object_path);
GList *gdu_pool_get_ports (GduPool *pool);
diff --git a/src/gdu/gdu-private.h b/src/gdu/gdu-private.h
index 52bc2f5..4306580 100644
--- a/src/gdu/gdu-private.h
+++ b/src/gdu/gdu-private.h
@@ -108,9 +108,14 @@ GduAdapter *_gdu_adapter_new_from_object_path (GduPool *pool, const char *object
gboolean _gdu_adapter_changed (GduAdapter *adapter);
GduHba *_gdu_hba_new_from_adapter (GduPool *pool, GduAdapter *adapter);
+GduExpander *_gdu_expander_new_from_object_path (GduPool *pool, const char *object_path);
+gboolean _gdu_expander_changed (GduExpander *expander);
+GduHub *_gdu_hub_new_from_expander (GduPool *pool, GduExpander *expander, GduPresentable *enclosing_presentable);
+
GduPort *_gdu_port_new_from_object_path (GduPool *pool, const char *object_path);
gboolean _gdu_port_changed (GduPort *port);
+void _gdu_hub_rewrite_enclosing_presentable (GduHub *hub);
void _gdu_drive_rewrite_enclosing_presentable (GduDrive *drive);
void _gdu_volume_rewrite_enclosing_presentable (GduVolume *volume);
void _gdu_volume_hole_rewrite_enclosing_presentable (GduVolumeHole *volume_hole);
diff --git a/src/gdu/gdu-types.h b/src/gdu/gdu-types.h
index 96ede57..999b33a 100644
--- a/src/gdu/gdu-types.h
+++ b/src/gdu/gdu-types.h
@@ -36,6 +36,7 @@ G_BEGIN_DECLS
typedef struct _GduPool GduPool;
typedef struct _GduDevice GduDevice;
typedef struct _GduAdapter GduAdapter;
+typedef struct _GduExpander GduExpander;
typedef struct _GduPort GduPort;
typedef struct _GduPresentable GduPresentable; /* Dummy typedef */
@@ -44,6 +45,7 @@ typedef struct _GduLinuxMdDrive GduLinuxMdDrive;
typedef struct _GduVolume GduVolume;
typedef struct _GduVolumeHole GduVolumeHole;
typedef struct _GduHba GduHba;
+typedef struct _GduHub GduHub;
typedef struct _GduKnownFilesystem GduKnownFilesystem;
typedef struct _GduProcess GduProcess;
diff --git a/src/gdu/gdu.h b/src/gdu/gdu.h
index 7bd9dbc..cedf7e0 100644
--- a/src/gdu/gdu.h
+++ b/src/gdu/gdu.h
@@ -32,6 +32,7 @@
#include <gdu/gdu-linux-md-drive.h>
#include <gdu/gdu-device.h>
#include <gdu/gdu-adapter.h>
+#include <gdu/gdu-expander.h>
#include <gdu/gdu-port.h>
#include <gdu/gdu-drive.h>
#include <gdu/gdu-error.h>
@@ -43,6 +44,7 @@
#include <gdu/gdu-volume.h>
#include <gdu/gdu-volume-hole.h>
#include <gdu/gdu-hba.h>
+#include <gdu/gdu-hub.h>
#include <gdu/gdu-callbacks.h>
#undef __GDU_INSIDE_GDU_H
diff --git a/src/palimpsest/Makefile.am b/src/palimpsest/Makefile.am
index 8423f49..21776ec 100644
--- a/src/palimpsest/Makefile.am
+++ b/src/palimpsest/Makefile.am
@@ -19,6 +19,7 @@ palimpsest_SOURCES = \
gdu-section-drive.h gdu-section-drive.c \
gdu-section-volumes.h gdu-section-volumes.c \
gdu-section-hba.h gdu-section-hba.c \
+ gdu-section-hub.h gdu-section-hub.c \
$(NULL)
palimpsest_CPPFLAGS = \
diff --git a/src/palimpsest/gdu-section-drive.c b/src/palimpsest/gdu-section-drive.c
index 9ad7833..f98952f 100644
--- a/src/palimpsest/gdu-section-drive.c
+++ b/src/palimpsest/gdu-section-drive.c
@@ -139,21 +139,71 @@ gdu_section_drive_update (GduSection *_section)
}
if (port != NULL) {
gint port_number;
+ GduPool *pool;
+ GduAdapter *adapter;
+ GduExpander *expander;
+ const gchar *port_parent_object_path;
+ const gchar *fabric;
+ gchar *s;
+
+ pool = gdu_device_get_pool (d);
+
+ port_parent_object_path = gdu_port_get_parent (port);
+
+ expander = NULL;
+ adapter = gdu_pool_get_adapter_by_object_path (pool, port_parent_object_path);
+ if (adapter == NULL) {
+ expander = gdu_pool_get_expander_by_object_path (pool, port_parent_object_path);
+ adapter = gdu_pool_get_adapter_by_object_path (pool, gdu_device_drive_get_adapter (d));
+ }
+ fabric = NULL;
+ if (adapter != NULL)
+ fabric = gdu_adapter_get_fabric (adapter);
+ s = NULL;
port_number = gdu_port_get_number (port);
if (port_number >= 0) {
/* TODO: provide a link to the HBA? Probably */
- /* Translators: This is used in the "Location" element for a disk
- * directly connected to the Host Adapter (aka HBA) - port numbers
- * start at 1
- */
- s = g_strdup_printf (_("Port %d of Host Adapter"), port_number + 1);
- gdu_details_element_set_text (section->priv->location_element, s);
- g_free (s);
- } else {
- gdu_details_element_set_text (section->priv->location_element, "â??");
+ if (expander == NULL) {
+ if (g_strcmp0 (fabric, "scsi_sas") == 0) {
+ /* Translators: This is used in the "Location" element for a disk
+ * directly connected to the SAS Host Adapter - PHY numbers
+ * start at 1
+ */
+ s = g_strdup_printf (_("PHY %d of SAS Host Adapter"), port_number + 1);
+ } else {
+ /* Translators: This is used in the "Location" element for a disk
+ * directly connected to the Host Adapter (aka HBA) - port numbers
+ * start at 1
+ */
+ s = g_strdup_printf (_("Port %d of Host Adapter"), port_number + 1);
+ }
+ } else {
+ if (g_strcmp0 (fabric, "scsi_sas") == 0) {
+ /* Translators: This is used in the "Location" element for a disk
+ * connected to a SAS expander - PHY numbers start at 1
+ */
+ s = g_strdup_printf (_("PHY %d of Expander"), port_number + 1);
+ } else {
+ /* Translators: This is used in the "Location" element for a disk
+ * connected to an expander / port multiplier - port numbers
+ * start at 1
+ */
+ s = g_strdup_printf (_("Port %d of Expander"), port_number + 1);
+ }
+ }
}
+ if (s == NULL)
+ s = g_strdup ("â??");
+ gdu_details_element_set_text (section->priv->location_element, s);
+ g_free (s);
+
+ g_object_unref (pool);
+ if (adapter != NULL)
+ g_object_unref (adapter);
+ if (expander != NULL)
+ g_object_unref (expander);
} else {
gdu_details_element_set_text (section->priv->location_element, "â??");
}
diff --git a/src/palimpsest/gdu-section-hub.c b/src/palimpsest/gdu-section-hub.c
new file mode 100644
index 0000000..413ac81
--- /dev/null
+++ b/src/palimpsest/gdu-section-hub.c
@@ -0,0 +1,234 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- */
+/* gdu-section-hub.c
+ *
+ * Copyright (C) 2009 David Zeuthen
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * as published by the Free Software Foundation; either version 2 of
+ * the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+ * 02111-1307, USA.
+ */
+
+#include <config.h>
+#include <string.h>
+#include <glib/gi18n.h>
+#include <dbus/dbus-glib.h>
+#include <stdlib.h>
+#include <math.h>
+#include <gio/gdesktopappinfo.h>
+
+#include <gdu-gtk/gdu-gtk.h>
+#include "gdu-section-hub.h"
+
+struct _GduSectionHubPrivate
+{
+ GduDetailsElement *vendor_element;
+ GduDetailsElement *model_element;
+ GduDetailsElement *revision_element;
+ GduDetailsElement *driver_element;
+ GduDetailsElement *fabric_element;
+ GduDetailsElement *num_ports_element;
+};
+
+G_DEFINE_TYPE (GduSectionHub, gdu_section_hub, GDU_TYPE_SECTION)
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static void
+gdu_section_hub_finalize (GObject *object)
+{
+ //GduSectionHub *section = GDU_SECTION_HUB (object);
+
+ if (G_OBJECT_CLASS (gdu_section_hub_parent_class)->finalize != NULL)
+ G_OBJECT_CLASS (gdu_section_hub_parent_class)->finalize (object);
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static void
+gdu_section_hub_update (GduSection *_section)
+{
+ GduSectionHub *section = GDU_SECTION_HUB (_section);
+ GduPresentable *p;
+ GduExpander *e;
+ GduPool *pool;
+ const gchar *adapter_object_path;
+ GduAdapter *a;
+ const gchar *vendor;
+ const gchar *model;
+ const gchar *revision;
+ const gchar *fabric;
+ guint num_ports;
+ gchar *num_ports_str;
+
+ a = NULL;
+ e = NULL;
+ num_ports_str = NULL;
+
+ p = gdu_section_get_presentable (_section);
+
+ e = gdu_hub_get_expander (GDU_HUB (p));
+ if (e == NULL)
+ goto out;
+
+ pool = gdu_expander_get_pool (e);
+ if (pool == NULL)
+ goto out;
+
+ adapter_object_path = gdu_expander_get_adapter (e);
+ if (adapter_object_path != NULL) {
+ a = gdu_pool_get_adapter_by_object_path (pool, adapter_object_path);
+ }
+
+ vendor = gdu_expander_get_vendor (e);
+ model = gdu_expander_get_model (e);
+ revision = gdu_expander_get_revision (e);
+ fabric = NULL;
+ if (a != NULL)
+ fabric = gdu_adapter_get_fabric (a);
+ num_ports = gdu_expander_get_num_ports (e);
+
+ /* TODO: maybe move these blocks of code to gdu-util.c as util functions */
+
+ if (num_ports > 0) {
+ if (g_strcmp0 (fabric, "scsi_sas") == 0) {
+ /* Translators: Used for SAS to convey the number of PHYs in the
+ * "Number of Ports" element. You should probably not translate PHY.
+ */
+ num_ports_str = g_strdup_printf (_("%d PHYs"), num_ports);
+ } else {
+ num_ports_str = g_strdup_printf ("%d", num_ports);
+ }
+ } else {
+ num_ports_str = g_strdup ("â??");
+ }
+
+ if (revision == NULL || strlen (revision) == 0)
+ revision = "â??";
+
+ gdu_details_element_set_text (section->priv->vendor_element, vendor);
+ gdu_details_element_set_text (section->priv->model_element, model);
+ gdu_details_element_set_text (section->priv->revision_element, revision);
+ gdu_details_element_set_text (section->priv->num_ports_element, num_ports_str);
+
+ out:
+ g_free (num_ports_str);
+ if (pool != NULL)
+ g_object_unref (pool);
+ if (a != NULL)
+ g_object_unref (a);
+ if (e != NULL)
+ g_object_unref (e);
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static void
+gdu_section_hub_constructed (GObject *object)
+{
+ GduSectionHub *section = GDU_SECTION_HUB (object);
+ GtkWidget *align;
+ GtkWidget *label;
+ GtkWidget *table;
+ GtkWidget *vbox;
+ gchar *s;
+ GduPresentable *p;
+ GduDevice *d;
+ GPtrArray *elements;
+ GduDetailsElement *element;
+
+ p = gdu_section_get_presentable (GDU_SECTION (section));
+ d = gdu_presentable_get_device (p);
+
+ gtk_box_set_spacing (GTK_BOX (section), 12);
+
+ /*------------------------------------- */
+
+ label = gtk_label_new (NULL);
+ gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5);
+ s = g_strconcat ("<b>", _("SAS Expander"), "</b>", NULL);
+ gtk_label_set_markup (GTK_LABEL (label), s);
+ g_free (s);
+ gtk_box_pack_start (GTK_BOX (section), label, FALSE, FALSE, 0);
+
+ align = gtk_alignment_new (0.5, 0.5, 1.0, 1.0);
+ gtk_alignment_set_padding (GTK_ALIGNMENT (align), 0, 0, 12, 0);
+ gtk_box_pack_start (GTK_BOX (section), align, FALSE, FALSE, 0);
+
+ vbox = gtk_vbox_new (FALSE, 6);
+ gtk_container_add (GTK_CONTAINER (align), vbox);
+
+ elements = g_ptr_array_new_with_free_func (g_object_unref);
+
+ element = gdu_details_element_new (_("Vendor:"), NULL, NULL);
+ g_ptr_array_add (elements, element);
+ section->priv->vendor_element = element;
+
+ element = gdu_details_element_new (_("Model:"), NULL, NULL);
+ g_ptr_array_add (elements, element);
+ section->priv->model_element = element;
+
+ element = gdu_details_element_new (_("Revision:"), NULL, NULL);
+ g_ptr_array_add (elements, element);
+ section->priv->revision_element = element;
+
+ element = gdu_details_element_new (_("Number of Ports:"), NULL, NULL);
+ g_ptr_array_add (elements, element);
+ section->priv->num_ports_element = element;
+
+ table = gdu_details_table_new (1, elements);
+ g_ptr_array_unref (elements);
+ gtk_box_pack_start (GTK_BOX (vbox), table, FALSE, FALSE, 0);
+
+ /* -------------------------------------------------------------------------------- */
+
+ gtk_widget_show_all (GTK_WIDGET (section));
+
+ if (d != NULL)
+ g_object_unref (d);
+
+ if (G_OBJECT_CLASS (gdu_section_hub_parent_class)->constructed != NULL)
+ G_OBJECT_CLASS (gdu_section_hub_parent_class)->constructed (object);
+}
+
+static void
+gdu_section_hub_class_init (GduSectionHubClass *klass)
+{
+ GObjectClass *gobject_class;
+ GduSectionClass *section_class;
+
+ gobject_class = G_OBJECT_CLASS (klass);
+ section_class = GDU_SECTION_CLASS (klass);
+
+ gobject_class->finalize = gdu_section_hub_finalize;
+ gobject_class->constructed = gdu_section_hub_constructed;
+ section_class->update = gdu_section_hub_update;
+
+ g_type_class_add_private (klass, sizeof (GduSectionHubPrivate));
+}
+
+static void
+gdu_section_hub_init (GduSectionHub *section)
+{
+ section->priv = G_TYPE_INSTANCE_GET_PRIVATE (section, GDU_TYPE_SECTION_HUB, GduSectionHubPrivate);
+}
+
+GtkWidget *
+gdu_section_hub_new (GduShell *shell,
+ GduPresentable *presentable)
+{
+ return GTK_WIDGET (g_object_new (GDU_TYPE_SECTION_HUB,
+ "shell", shell,
+ "presentable", presentable,
+ NULL));
+}
diff --git a/src/palimpsest/gdu-section-hub.h b/src/palimpsest/gdu-section-hub.h
new file mode 100644
index 0000000..9cd36a3
--- /dev/null
+++ b/src/palimpsest/gdu-section-hub.h
@@ -0,0 +1,58 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- */
+/* gdu-section-hub.h
+ *
+ * Copyright (C) 2007 David Zeuthen
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * as published by the Free Software Foundation; either version 2 of
+ * the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+ * 02111-1307, USA.
+ */
+
+#include <gtk/gtk.h>
+#include "gdu-section.h"
+
+#ifndef GDU_SECTION_HUB_H
+#define GDU_SECTION_HUB_H
+
+#define GDU_TYPE_SECTION_HUB (gdu_section_hub_get_type ())
+#define GDU_SECTION_HUB(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), GDU_TYPE_SECTION_HUB, GduSectionHub))
+#define GDU_SECTION_HUB_CLASS(k) (G_TYPE_CHECK_CLASS_CAST ((k), GDU_TYPE_SECTION_HUB, GduSectionHubClass))
+#define GDU_IS_SECTION_HUB(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), GDU_TYPE_SECTION_HUB))
+#define GDU_IS_SECTION_HUB_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), GDU_TYPE_SECTION_HUB))
+#define GDU_SECTION_HUB_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), GDU_TYPE_SECTION_HUB, GduSectionHubClass))
+
+typedef struct _GduSectionHubClass GduSectionHubClass;
+typedef struct _GduSectionHub GduSectionHub;
+
+struct _GduSectionHubPrivate;
+typedef struct _GduSectionHubPrivate GduSectionHubPrivate;
+
+struct _GduSectionHub
+{
+ GduSection parent;
+
+ /* private */
+ GduSectionHubPrivate *priv;
+};
+
+struct _GduSectionHubClass
+{
+ GduSectionClass parent_class;
+};
+
+GType gdu_section_hub_get_type (void);
+GtkWidget *gdu_section_hub_new (GduShell *shell,
+ GduPresentable *presentable);
+
+#endif /* GDU_SECTION_HUB_H */
diff --git a/src/palimpsest/gdu-shell.c b/src/palimpsest/gdu-shell.c
index 9c37727..73d84da 100644
--- a/src/palimpsest/gdu-shell.c
+++ b/src/palimpsest/gdu-shell.c
@@ -46,6 +46,7 @@
#include "gdu-section-drive.h"
#include "gdu-section-volumes.h"
#include "gdu-section-hba.h"
+#include "gdu-section-hub.h"
struct _GduShellPrivate
{
@@ -209,6 +210,10 @@ compute_sections_to_show (GduShell *shell)
sections_to_show = g_list_append (sections_to_show, (gpointer) GDU_TYPE_SECTION_HBA);
+ } else if (GDU_IS_HUB (shell->priv->presentable_now_showing)) {
+
+ sections_to_show = g_list_append (sections_to_show, (gpointer) GDU_TYPE_SECTION_HUB);
+
} else if (GDU_IS_LINUX_MD_DRIVE (shell->priv->presentable_now_showing)) {
sections_to_show = g_list_append (sections_to_show, (gpointer) GDU_TYPE_SECTION_LINUX_MD_DRIVE);
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]