Re: [PATCH 8/9] Track killswitch change signals from urfkill



On Wed, 2016-06-08 at 16:20 -0400, Tony Espy wrote:
From: Mathieu Trudel-Lapierre <mathieu trudel-lapierre canonical com>

... Also do that rather than resetting the killswitch to the NM saved
WirelessEnabled state if urfkill support is enabled.

Gbp-Pq: Name Track-killswitch-change-signals-from-urfkill.patch
---
 config.h.in              |   6 +
 configure.ac             |  11 ++
 src/Makefile.am          |   6 +
 src/nm-manager.c         | 112 +++++++++++++++++-
 src/nm-urfkill-manager.c | 289
+++++++++++++++++++++++++++++++++++++++++++++++
 src/nm-urfkill-manager.h |  50 ++++++++
 6 files changed, 472 insertions(+), 2 deletions(-)
 create mode 100644 src/nm-urfkill-manager.c
 create mode 100644 src/nm-urfkill-manager.h

diff --git a/config.h.in b/config.h.in
index baabb79..356de31 100644
--- a/config.h.in
+++ b/config.h.in
@@ -261,6 +261,9 @@
 /* Define if you have ModemManager1 support */
 #undef WITH_MODEM_MANAGER_1
 
+/* Define if you have oFono support */
+#undef WITH_OFONO
+
 /* whether to compile polkit support */
 #undef WITH_POLKIT
 
@@ -276,6 +279,9 @@
 /* Define if you have Teamd control support */
 #undef WITH_TEAMDCTL
 
+/* Define if you want to build with support for the urfkill daemon
*/
+#undef WITH_URFKILL
+
 /* Define if you have Wi-Fi support */
 #undef WITH_WIFI

This hunk wouldn't apply upstream since config.h.in gets auto-
generated...  so can just be removed from the patch.
 
diff --git a/configure.ac b/configure.ac
index 2ccfa23..6ad177d 100644
--- a/configure.ac
+++ b/configure.ac
@@ -268,6 +268,17 @@ else
 fi
 
 dnl
+dnl Default to using WEXT but allow it to be disabled
+dnl
+AC_ARG_WITH(urfkill, AS_HELP_STRING([--with-urfkill=yes], [Enable or
disable support for urfkill]), ac_with_urfkill=$withval,
ac_with_urfkill="yes")
+if test x"$ac_with_urfkill" = x"yes"; then
+     AC_DEFINE(WITH_URFKILL, 1, [Define if you want to build with
support for the urfkill daemon])
+else
+     AC_DEFINE(WITH_URFKILL, 0, [Define if you want to build with
support for the urfkill daemon])
+fi
+AM_CONDITIONAL(WITH_URFKILL, test x"${ac_with_urfkill}" = x"yes")
+
+dnl
 dnl Checks for libm - needed for pow()
 dnl
 LT_LIB_M
diff --git a/src/Makefile.am b/src/Makefile.am
index 2495bbc..49028f1 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -445,6 +445,12 @@ libNetworkManager_la_SOURCES = \
      NetworkManagerUtils.c \
      NetworkManagerUtils.h
 
+if WITH_URFKILL
+libNetworkManager_la_SOURCES += \
+       nm-urfkill-manager.c \
+       nm-urfkill-manager.h \
+       $(NULL)
+endif
 
 if SUSPEND_RESUME_UPOWER
 libNetworkManager_la_SOURCES += nm-sleep-monitor-upower.c
diff --git a/src/nm-manager.c b/src/nm-manager.c
index 8cf70c7..8f45b0f 100644
--- a/src/nm-manager.c
+++ b/src/nm-manager.c
@@ -34,6 +34,7 @@
 #include "nm-device-generic.h"
 #include "nm-platform.h"
 #include "nm-rfkill-manager.h"
+#include "nm-urfkill-manager.h"
 #include "nm-dhcp-manager.h"
 #include "nm-settings.h"
 #include "nm-settings-connection.h"
@@ -121,6 +122,9 @@ typedef struct {
              guint            id;
      } prop_filter;
      NMRfkillManager *rfkill_mgr;
+#if WITH_URFKILL
+     NMUrfkillManager *urfkill_mgr;
+#endif
 
      NMSettings *settings;
      char *hostname;
@@ -144,6 +148,7 @@ typedef struct {
      gboolean ifstate_force_online;
 
      guint timestamp_update_id;
+     guint rfkill_initial_id;
 
      gboolean startup;
      gboolean devices_inited;
@@ -5213,6 +5218,49 @@ dbus_connection_changed_cb (NMBusManager
*dbus_mgr,
 
 /*******************************************************************
***/
 
+#if WITH_URFKILL
+static void
+urfkill_wlan_state_changed_cb (NMUrfkillManager *mgr,
+                            gboolean enabled,
+                            gpointer user_data)
+{
+     NMManager *self = NM_MANAGER (user_data);
+     NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (self);
+     GError *error = NULL;
+
+     nm_log_dbg (LOGD_RFKILL, "urfkill wlan state changed to %s",
+                 enabled ? "enabled" : "disabled");
+
+     if (priv->rfkill_initial_id) {
+             g_source_remove (priv->rfkill_initial_id);
+             priv->rfkill_initial_id = 0;
+     }

nm_clear_g_source(&priv->rfkill_initial_id) replaces all 4 of these
lines; but as below I don't think we'd really need them.

+     manager_update_radio_enabled (self,
+                                   &priv-
radio_states[RFKILL_TYPE_WLAN],
+                                   enabled);
+     nm_manager_update_state (self);
+}
+
+static void
+urfkill_wwan_state_changed_cb (NMUrfkillManager *mgr,
+                            gboolean enabled,
+                            gpointer user_data)
+{
+     NMManager *self = NM_MANAGER (user_data);
+     NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (self);
+     GError *error = NULL;
+
+     nm_log_dbg (LOGD_RFKILL, "urfkill wwan state changed to %s",
+                 enabled ? "enabled" : "disabled" );

For your own patches in Ubuntu, wouldn't you need to clear
rfkill_initial_id here too?  Just in case WWAN gets emitted before
WLAN.

+     manager_update_radio_enabled (self,
+                                   &priv-
radio_states[RFKILL_TYPE_WWAN],
+                                   enabled);
+     nm_manager_update_state (self);
+}
+#endif /* WITH_URFKILL */
+
 NM_DEFINE_SINGLETON_REGISTER (NMManager);
 
 NMManager *
@@ -5234,6 +5282,44 @@ nm_connection_provider_get (void)
      return p;
 }
 
+typedef struct KillState KillState;
+struct KillState {
+     NMManager *manager;
+     gboolean wlan_enabled;
+     gboolean wwan_enabled;
+};
+
+static gboolean
+rfkill_change_timeout (gpointer user_data)
+{
+     KillState *state = (KillState *) user_data;
+     NMManager *mgr = state->manager;
+     NMManagerPrivate *priv;
+
+     g_return_val_if_fail (NM_IS_MANAGER (mgr), G_SOURCE_REMOVE);
+
+     priv = NM_MANAGER_GET_PRIVATE (mgr);
+
+     rfkill_change (priv->radio_states[RFKILL_TYPE_WLAN].desc,
RFKILL_TYPE_WLAN, state->wlan_enabled);
+     rfkill_change (priv->radio_states[RFKILL_TYPE_WWAN].desc,
RFKILL_TYPE_WWAN, state->wwan_enabled);
+
+       return G_SOURCE_REMOVE;
+}
+
+static void
+kill_state_free (gpointer user_data)
+{
+     KillState *state = (KillState *) user_data;
+
+     if (state->manager) {
+             g_object_unref (state->manager);
+             state->manager = NULL;
+     }
+
+     g_slice_free (KillState, (KillState *)user_data);
+}
+
+
 NMManager *
 nm_manager_setup (const char *state_file,
                   gboolean initial_net_enabled,
@@ -5314,16 +5400,38 @@ constructed (GObject *object)
                        "rfkill-changed",
                        G_CALLBACK
(rfkill_manager_rfkill_changed_cb),
                        self);
+#if WITH_URFKILL
+     priv->urfkill_mgr = nm_urfkill_manager_new ();
+
+     g_signal_connect (priv->urfkill_mgr,
+                       "wlan-state-changed",
+                       G_CALLBACK
(urfkill_wlan_state_changed_cb),
+                       self);
+     g_signal_connect (priv->urfkill_mgr,
+                       "wwan-state-changed",
+                       G_CALLBACK
(urfkill_wwan_state_changed_cb),
+                       self);
+     urfkill_wlan_state_changed_cb (priv->urfkill_mgr, TRUE,
self);
+     urfkill_wwan_state_changed_cb (priv->urfkill_mgr, TRUE,
self);
+#endif
 
      /* Force kernel WiFi/WWAN rfkill state to follow NM saved
WiFi/WWAN state
       * in case the BIOS doesn't save rfkill state, and to be
consistent with user
       * changes to the WirelessEnabled/WWANEnabled properties
which toggle kernel
       * rfkill.
       */
-     rfkill_change (priv->radio_states[RFKILL_TYPE_WLAN].desc,
RFKILL_TYPE_WLAN, priv->radio_states[RFKILL_TYPE_WLAN].user_enabled);
-     rfkill_change (priv->radio_states[RFKILL_TYPE_WWAN].desc,
RFKILL_TYPE_WWAN, priv->radio_states[RFKILL_TYPE_WWAN].user_enabled);
+     KillState *kill_state = g_slice_new0 (KillState);
+     kill_state->manager = g_object_ref (self);
+     kill_state->wlan_enabled = priv-
radio_states[RFKILL_TYPE_WLAN].user_enabled;
+     kill_state->wwan_enabled = priv-
radio_states[RFKILL_TYPE_WWAN].user_enabled;
+
+     priv->rfkill_initial_id = g_timeout_add_seconds_full
(G_PRIORITY_DEFAULT, 10,
+                                                           rfkill
_change_timeout,
+                                                           kill_s
tate,
+                                                           kill_s
tate_free);
 }

This looks a bit odd; what is the timeout supposed to do?  I'd expect
that the urfkill-manager would emit the initial state signals when it
got the initial states from the GDBusProxy and then the manager would
update.  Which shouldn't need a timeout to update the states...  what
problem did the timeout solve for you here?
 
+
 static void
 nm_manager_init (NMManager *self)
 {
diff --git a/src/nm-urfkill-manager.c b/src/nm-urfkill-manager.c
new file mode 100644
index 0000000..211ebc1
--- /dev/null
+++ b/src/nm-urfkill-manager.c
@@ -0,0 +1,289 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4
-*- */
+/* NetworkManager -- Network link manager
+ *
+ * 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 Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Copyright (C) 2006 - 2010 Red Hat, Inc.
+ * Copyright (C) 2006 - 2008 Novell, Inc.
+ */
+
+#include "config.h"
+#include "nm-core-internal.h"
+#include "nm-urfkill-manager.h"
+#include "nm-glib.h"
+
+#include <gio/gio.h>
+#include <string.h>
+#include "nm-logging.h"
+
+enum {
+     WLAN_STATE_CHANGED,
+     WWAN_STATE_CHANGED,
+     NUMBER_OF_SIGNALS
+};
+
+static guint signals[NUMBER_OF_SIGNALS];
+
+struct _NMUrfkillManager {
+     GObject parent_instance;
+
+     guint urfkill_watch;
+
+     GDBusProxy *wlan_proxy;
+     GDBusProxy *wwan_proxy;
+
+     GCancellable *watch_cancellable;
+     GCancellable *wlan_proxy_cancellable;
+     GCancellable *wwan_proxy_cancellable;
+};
+
+typedef GObjectClass NMUrfkillManagerClass;
+
+G_DEFINE_TYPE(NMUrfkillManager, nm_urfkill_manager, G_TYPE_OBJECT)
+
+static void
+wlan_state_changed (GDBusProxy *proxy,
+                 GVariant            *changed_properties,
+                 const gchar* const  *invalidated_properties,
+                 gpointer             user_data)
+{
+     NMUrfkillManager *self = NM_URFKILL_MANAGER (user_data);
+     gboolean enabled;
+
+     enabled = nm_urfkill_get_wlan_state (self);
+
+     nm_log_dbg (LOGD_RFKILL, "received state change for WLAN:
%s",
+                 enabled ? "unblocked" : "blocked");
+
+     g_signal_emit (self, signals[WLAN_STATE_CHANGED], 0,
enabled);
+}
+
+static void
+wwan_state_changed (GDBusProxy *proxy,
+                 GVariant            *changed_properties,
+                 const gchar* const  *invalidated_properties,
+                 gpointer             user_data)
+{
+     NMUrfkillManager *self = NM_URFKILL_MANAGER (user_data);
+     gboolean enabled;
+
+     enabled = nm_urfkill_get_wwan_state (self);
+
+     nm_log_dbg (LOGD_RFKILL, "received state change for WWAN:
%s",
+                 enabled ? "unblocked" : "blocked");
+
+     g_signal_emit (self, signals[WWAN_STATE_CHANGED], 0,
enabled);
+}
+
+static void
+wlan_proxy_created (GObject *source_object,
+                 GAsyncResult *res,
+                 gpointer user_data)
+{
+     NMUrfkillManager *self = NM_URFKILL_MANAGER (user_data);
+     GDBusProxy *wlan_proxy;
+     GError *error;
+
+     wlan_proxy = g_dbus_proxy_new_for_bus_finish (res, &error);

See below...

+     if (wlan_proxy) {
+             self->wlan_proxy = wlan_proxy;
+
+             g_signal_connect (self->wlan_proxy, "g-properties-
changed",
+                               G_CALLBACK (wlan_state_changed),
self);
+             g_signal_emit (self, signals[WLAN_STATE_CHANGED], 0,
+                            nm_urfkill_get_wlan_state (self));
+     } else {
+             nm_log_warn (LOGD_RFKILL, "could not create URfkill
WLAN device proxy");
+     }
+}
+
+static void
+wwan_proxy_created (GObject *source_object,
+                 GAsyncResult *res,
+                 gpointer user_data)
+{
+     NMUrfkillManager *self = NM_URFKILL_MANAGER (user_data);
+     GDBusProxy *wwan_proxy;
+     GError *error;
+
+     wwan_proxy = g_dbus_proxy_new_for_bus_finish (res, &error);

If the urfkill-manager got destroyed/finalized while the proxy creation
was in-flight, you'll crash here because of the 'self' assignment,
since 'user_data'/self is already de-allocated.  But that's fairly easy
to handle, what you want to do is just:

        NMUrfkillManager *self;

        ....

        wwan_proxy = g_dbus_proxy_new_for_bus_finish (res, &error);
        if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
            return;
        self = NM_URFKILL_MANAGER (user_data);

alternatively, you can g_object_ref (self) in the g_dbus_proxy_new()
call and then unref everywhere this function returns, but that's not
really worth it.

+     if (wwan_proxy) {
+             self->wwan_proxy = wwan_proxy;
+
+             g_signal_connect (self->wwan_proxy, "g-properties-
changed",
+                               G_CALLBACK (wwan_state_changed),
self);
+             g_signal_emit (self, signals[WWAN_STATE_CHANGED], 0,
+                            nm_urfkill_get_wwan_state (self));
+     } else {
+             nm_log_warn (LOGD_RFKILL, "could not create URfkill
WWAN device proxy");
+     }
+}
+
+gboolean
+nm_urfkill_get_wlan_state (NMUrfkillManager *self)
+{
+     GVariant *state;
+     gboolean enabled = TRUE;
+
+     g_return_val_if_fail (self->wlan_proxy != NULL, enabled);
+
+     state = g_dbus_proxy_get_cached_property (self->wlan_proxy,
"state");
+
+     if (state) {
+             nm_log_dbg (LOGD_RFKILL, "wlan state from urfkill:
%d",
+                         g_variant_get_int32 (state));
+             enabled = (g_variant_get_int32 (state) <= 0);
+             g_variant_unref (state);
+     } else {
+             nm_log_warn (LOGD_RFKILL, "invalid wlan state from
urfkill cached properties");
+     }
+
+     return enabled;
+}
+
+gboolean
+nm_urfkill_get_wwan_state (NMUrfkillManager *self)
+{
+     GVariant *state;
+     gboolean enabled = TRUE;
+
+     g_return_val_if_fail (self->wwan_proxy != NULL, enabled);
+
+     state = g_dbus_proxy_get_cached_property (self->wwan_proxy,
"state");
+
+     if (state) {
+             nm_log_dbg (LOGD_RFKILL, "wwan state from urfkill:
%d",
+                         g_variant_get_int32 (state));
+             enabled = (g_variant_get_int32 (state) <= 0);
+             g_variant_unref (state);
+     } else {
+             nm_log_warn (LOGD_RFKILL, "invalid wwan state from
urfkill cached properties");
+     }
+
+     return enabled;
+}
+
+static void
+on_urfkill_appeared (GDBusConnection *connection,
+                  const gchar     *name,
+                  const gchar     *name_owner,
+                  gpointer         user_data)
+{
+     NMUrfkillManager *self = NM_URFKILL_MANAGER (user_data);
+
+     nm_log_info (LOGD_RFKILL, "urfkill appeared on the bus");
+
+     self->wlan_proxy_cancellable = g_cancellable_new ();
+     self->wwan_proxy_cancellable = g_cancellable_new ();
+
+     g_dbus_proxy_new (connection,
+                       G_DBUS_PROXY_FLAGS_NONE,
+                       NULL,
+                       "org.freedesktop.URfkill",
+                       "/org/freedesktop/URfkill/WLAN",
+                       "org.freedesktop.URfkill.Killswitch",
+                       self->wlan_proxy_cancellable,
+                       wlan_proxy_created,
+                       self);
+
+     g_dbus_proxy_new (connection,
+                       G_DBUS_PROXY_FLAGS_NONE,
+                       NULL,
+                       "org.freedesktop.URfkill",
+                       "/org/freedesktop/URfkill/WWAN",
+                       "org.freedesktop.URfkill.Killswitch",
+                       self->wwan_proxy_cancellable,
+                       wwan_proxy_created,
+                       self);
+}
+
+static void
+on_urfkill_vanished (GDBusConnection *connection,
+                  const gchar     *name,
+                  gpointer         user_data)
+{
+     NMUrfkillManager *self = NM_URFKILL_MANAGER (user_data);
+
+     nm_log_info (LOGD_RFKILL, "urfkill disappeared from the
bus");
+
+     if (self->wlan_proxy)
+             g_object_unref (self->wlan_proxy);
+     if (self->wwan_proxy)
+             g_object_unref (self->wwan_proxy);

g_clear_object() for style points.

+}
+
+static void
+nm_urfkill_manager_init (NMUrfkillManager *self)
+{
+     self->urfkill_watch = g_bus_watch_name (G_BUS_TYPE_SYSTEM,
+                                             "org.freedesktop.URf
kill",
+                                             0,
+                                             on_urfkill_appeared,
+                                             on_urfkill_vanished,
+                                             self,
+                                             NULL);
+}
+
+NMUrfkillManager *
+nm_urfkill_manager_new (void)
+{
+     return NM_URFKILL_MANAGER (g_object_new
(NM_TYPE_URFKILL_MANAGER, NULL));
+}
+
+static void
+nm_urfkill_manager_finalize (GObject *object)
+{
+     NMUrfkillManager *mgr = NM_URFKILL_MANAGER (object);
+
+     if (mgr->wlan_proxy_cancellable)
+             g_cancellable_cancel (mgr->wlan_proxy_cancellable);
+     if (mgr->wwan_proxy_cancellable)
+             g_cancellable_cancel (mgr->wwan_proxy_cancellable);

Need to g_object_unref() them too otherwise they get leaked.

+     if (mgr->urfkill_watch) {
+             g_bus_unwatch_name (mgr->urfkill_watch);
+             mgr->urfkill_watch = 0;
+     }
+
+     if (mgr->wlan_proxy)
+             g_object_unref (mgr->wlan_proxy);
+     if (mgr->wwan_proxy)
+             g_object_unref (mgr->wwan_proxy);

g_clear_object() for style points.

+     G_OBJECT_CLASS (nm_urfkill_manager_parent_class)->finalize
(object);
+}
+
+static void
+nm_urfkill_manager_class_init (NMUrfkillManagerClass *class)
+{
+     class->finalize = nm_urfkill_manager_finalize;
+
+     signals[WLAN_STATE_CHANGED] =
+             g_signal_new (NM_URFKILL_MANAGER_WLAN_STATE_CHANGED,
+                           G_OBJECT_CLASS_TYPE (class),
+                           G_SIGNAL_RUN_LAST, 0,
+                           NULL, NULL,
g_cclosure_marshal_VOID__BOOLEAN,
+                           G_TYPE_NONE, 1, G_TYPE_BOOLEAN);
+
+     signals[WWAN_STATE_CHANGED] =
+             g_signal_new (NM_URFKILL_MANAGER_WWAN_STATE_CHANGED,
+                           G_OBJECT_CLASS_TYPE (class),
+                           G_SIGNAL_RUN_LAST, 0,
+                           NULL, NULL,
g_cclosure_marshal_VOID__BOOLEAN,
+                           G_TYPE_NONE, 1, G_TYPE_BOOLEAN);
+}
+
diff --git a/src/nm-urfkill-manager.h b/src/nm-urfkill-manager.h
new file mode 100644
index 0000000..d99155b
--- /dev/null
+++ b/src/nm-urfkill-manager.h
@@ -0,0 +1,50 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4
-*- */
+/* NetworkManager -- Network link manager
+ *
+ * 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 Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Copyright (C) 2006 - 2008 Red Hat, Inc.
+ * Copyright (C) 2006 - 2008 Novell, Inc.
+ */
+
+#ifndef __NM_URFKILL_MANAGER_H__
+#define __NM_URFKILL_MANAGER_H__
+
+#include <glib-object.h>
+
+#include "nm-glib.h"
+
+G_BEGIN_DECLS
+
+#define NM_TYPE_URFKILL_MANAGER (nm_urfkill_manager_get_type ())
+#define NM_URFKILL_MANAGER(o) (G_TYPE_CHECK_INSTANCE_CAST ((o),
NM_TYPE_URFKILL_MANAGER, NMUrfkillManager))
+#define NM_IS_URFKILL_MANAGER(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o),
NM_TYPE_URFKILL_MANAGER))
+
+#define NM_URFKILL_MANAGER_WLAN_STATE_CHANGED "wlan-state-changed"
+#define NM_URFKILL_MANAGER_WWAN_STATE_CHANGED "wwan-state-changed"
+
+typedef struct _NMUrfkillManager NMUrfkillManager;
+
+GType nm_urfkill_manager_get_type (void);
+
+NMUrfkillManager *nm_urfkill_manager_new (void);
+
+gboolean nm_urfkill_get_wlan_state (NMUrfkillManager *self);
+gboolean nm_urfkill_get_wwan_state (NMUrfkillManager *self);
+
+G_END_DECLS
+
+#endif /* __NM_URFKILL_MANAGER_H__ */
+


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]