[dconf/wip/reorg: 515/523] clean up and factor out the 'shm' code
- From: Ryan Lortie <ryanl src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [dconf/wip/reorg: 515/523] clean up and factor out the 'shm' code
- Date: Mon, 9 Jul 2012 02:50:41 +0000 (UTC)
commit dd9c1a0cad35082724bba59235af031246c06ce5
Author: Ryan Lortie <desrt desrt ca>
Date: Sun Jul 8 14:41:33 2012 -0400
clean up and factor out the 'shm' code
Remove the shm code from the engine and the service and put it in a
separate convenience library in shm/.
Remove the vestigial shmdir weirdness from the service (since shmdir is
now always relative to XDG_RUNTIME_DIR and has been for some time).
The purpose of this is so that dconf-engine can be properly unit-tested.
dconf-engine now has five points of contact with the world (excluding
the users of the engine themselves):
- the DCONF_PROFILE environment variable
- fopen() of profile files
- shm
- gvdb
- dbus
The environment variable is quite easily controlled. fopen() is
intercepted in the engine testcase with a interpose of the libc symbol.
With this commit now each of dbus, gvdb and shm are implemented in
separate utility modules that can be mocked from the testcases.
Makefile.am | 2 +-
client/Makefile.am | 2 +-
common/Makefile.am | 4 +-
common/dconf-shmdir.c | 35 --------
configure.ac | 1 +
dbus-1/dconf-engine.c | 14 +++-
engine/Makefile.am | 2 +-
engine/dconf-engine-source-user.c | 61 +------------
gsettings/Makefile.am | 2 +-
service/Makefile.am | 4 +-
service/dconf-interfaces.c | 10 +--
service/dconf-state.c | 15 ---
service/dconf-state.h | 1 -
service/dconf-writer.c | 29 +------
service/service.c | 20 +----
shm/.gitignore | 2 +
shm/Makefile.am | 12 +++
shm/dconf-shm.c | 138 ++++++++++++++++++++++++++++++
common/dconf-shmdir.h => shm/dconf-shm.h | 12 ++-
tests/Makefile.am | 1 +
20 files changed, 193 insertions(+), 174 deletions(-)
---
diff --git a/Makefile.am b/Makefile.am
index c2a1d75..aa0d525 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -2,7 +2,7 @@ include Makefile.gtester
ACLOCAL_AMFLAGS = -I m4 ${ACLOCAL_FLAGS}
-SUBDIRS = gvdb common engine service gdbus gsettings dbus-1 client bin docs tests
+SUBDIRS = shm gvdb common engine service gdbus gsettings dbus-1 client bin docs tests
if ENABLE_EDITOR
SUBDIRS += editor
diff --git a/client/Makefile.am b/client/Makefile.am
index 80bfcf3..73bdccc 100644
--- a/client/Makefile.am
+++ b/client/Makefile.am
@@ -24,7 +24,7 @@ dconfinclude_HEADERS = \
dconf-client.h \
dconf.h
-libdconf_so_0_0_0_LDADD = ../common/libdconf-common-shared.a ../engine/libdconf-engine.a ../gdbus/libdconf-gdbus.a $(gio_LIBS)
+libdconf_so_0_0_0_LDADD = ../common/libdconf-common-shared.a ../engine/libdconf-engine.a ../gdbus/libdconf-gdbus.a ../shm/libdconf-shm-shared.a $(gio_LIBS)
libdconf_so_0_0_0_LDFLAGS = -shared -Wl,-soname=libdconf.so.0
libdconf_so_0_0_0_SOURCES = \
dconf-client.c
diff --git a/common/Makefile.am b/common/Makefile.am
index 29c4667..324f0fe 100644
--- a/common/Makefile.am
+++ b/common/Makefile.am
@@ -10,9 +10,7 @@ INCLUDES = $(glib_CFLAGS)
libdconf_common_a_SOURCES = \
dconf-changeset.h \
dconf-changeset.c \
- dconf-paths.c \
- dconf-shmdir.h \
- dconf-shmdir.c
+ dconf-paths.c
libdconf_common_shared_a_CFLAGS = -fPIC -DPIC $(libdconf_common_a_CFLAGS)
libdconf_common_shared_a_SOURCES = $(libdconf_common_a_SOURCES)
diff --git a/configure.ac b/configure.ac
index 25c32da..72c35d8 100644
--- a/configure.ac
+++ b/configure.ac
@@ -62,6 +62,7 @@ fi
AC_CONFIG_FILES([
common/Makefile
+ shm/Makefile
gvdb/Makefile
engine/Makefile
gdbus/Makefile
diff --git a/dbus-1/dconf-engine.c b/dbus-1/dconf-engine.c
index aedabcf..f6a1f6a 100644
--- a/dbus-1/dconf-engine.c
+++ b/dbus-1/dconf-engine.c
@@ -20,7 +20,6 @@
*/
#define _XOPEN_SOURCE 600
-#include "dconf-shmdir.h"
#include "dconf-engine.h"
#include <gvdb-reader.h>
#include <string.h>
@@ -56,6 +55,19 @@ dconf_engine_message_copy (DConfEngineMessage *orig,
copy->parameters[i] = NULL;
}
+static gchar *
+dconf_shmdir_from_environment (void)
+{
+ gchar *result;
+
+ result = g_build_filename (g_get_user_runtime_dir (), "dconf", NULL);
+
+ if (g_mkdir_with_parents (result, 0700) != 0)
+ g_critical ("unable to create '%s'; dconf will not work properly.", result);
+
+ return result;
+}
+
static const gchar *
dconf_engine_get_session_dir (void)
{
diff --git a/engine/Makefile.am b/engine/Makefile.am
index bead7f1..2bab9fa 100644
--- a/engine/Makefile.am
+++ b/engine/Makefile.am
@@ -3,7 +3,7 @@ include $(top_srcdir)/Makefile.gtester
noinst_LIBRARIES = libdconf-engine.a
libdconf_engine_a_CFLAGS = -fPIC -DPIC -Wall
-INCLUDES = $(glib_CFLAGS) -I$(top_srcdir)/gvdb -I$(top_srcdir)/common
+INCLUDES = $(glib_CFLAGS) -I$(top_srcdir)/gvdb -I$(top_srcdir)/common -I$(top_srcdir)/shm
libdconf_engine_a_SOURCES = \
../gvdb/gvdb-reader.c \
diff --git a/engine/dconf-engine-source-user.c b/engine/dconf-engine-source-user.c
index 7c44b79..4f2d49b 100644
--- a/engine/dconf-engine-source-user.c
+++ b/engine/dconf-engine-source-user.c
@@ -22,6 +22,7 @@
#include "dconf-engine-source-private.h"
+#include "dconf-shm.h"
#include <sys/mman.h>
#include <fcntl.h>
#include <errno.h>
@@ -33,57 +34,6 @@ typedef struct
guint8 *shm;
} DConfEngineSourceUser;
-static guint8 *
-dconf_engine_source_user_open_shm (const gchar *name)
-{
- static gchar *shmdir;
- gchar *filename;
- void *memory;
- gint fd;
-
- if (g_once_init_enter (&shmdir))
- g_once_init_leave (&shmdir, g_build_filename (g_get_user_runtime_dir (), "dconf", NULL));
-
- filename = g_build_filename (shmdir, name, NULL);
- memory = NULL;
- fd = -1;
-
- if (g_mkdir_with_parents (shmdir, 0700) != 0)
- {
- g_critical ("unable to create directory '%s': %s. dconf will not work properly.", shmdir, g_strerror (errno));
- goto out;
- }
-
- fd = open (filename, O_RDWR | O_CREAT, 0600);
- if (fd == -1)
- {
- g_critical ("unable to create file '%s': %s. dconf will not work properly.", filename, g_strerror (errno));
- goto out;
- }
-
- if (ftruncate (fd, 1) != 0)
- {
- g_critical ("failed to allocate file '%s': %s. dconf will not work properly.", filename, g_strerror (errno));
- goto out;
- }
-
- memory = mmap (NULL, 1, PROT_READ, MAP_SHARED, fd, 0);
- g_assert (memory != NULL);
-
- if (memory == MAP_FAILED)
- {
- g_critical ("failed to mmap file '%s': %s. dconf will not work properly.", filename, g_strerror (errno));
- memory = NULL;
- goto out;
- }
-
- out:
- g_free (filename);
- close (fd);
-
- return memory;
-}
-
static GvdbTable *
dconf_engine_source_user_open_gvdb (const gchar *name)
{
@@ -106,7 +56,7 @@ dconf_engine_source_user_init (DConfEngineSource *source)
DConfEngineSourceUser *user_source = (DConfEngineSourceUser *) source;
guint8 *shm;
- shm = dconf_engine_source_user_open_shm (source->name);
+ shm = dconf_shm_open (source->name);
if (shm == NULL)
return FALSE;
@@ -135,8 +85,8 @@ dconf_engine_source_user_reopen (DConfEngineSource *source)
{
DConfEngineSourceUser *user_source = (DConfEngineSourceUser *) source;
- munmap (user_source->shm, 1);
- user_source->shm = dconf_engine_source_user_open_shm (source->name);
+ dconf_shm_close (user_source->shm);
+ user_source->shm = dconf_shm_open (source->name);
if (user_source->shm)
return dconf_engine_source_user_open_gvdb (source->name);
@@ -149,8 +99,7 @@ dconf_engine_source_user_finalize (DConfEngineSource *source)
{
DConfEngineSourceUser *user_source = (DConfEngineSourceUser *) source;
- if (user_source->shm)
- munmap (user_source->shm, 1);
+ dconf_shm_close (user_source->shm);
}
G_GNUC_INTERNAL
diff --git a/gsettings/Makefile.am b/gsettings/Makefile.am
index 1abcf36..d218f7e 100644
--- a/gsettings/Makefile.am
+++ b/gsettings/Makefile.am
@@ -5,7 +5,7 @@ INCLUDES = -I$(top_srcdir)/common -I$(top_srcdir)/gvdb -I$(top_srcdir)/engine $(
giomodules_PROGRAMS = libdconfsettings.so
-libdconfsettings_so_LDADD = $(gio_LIBS) ../engine/libdconf-engine.a ../gdbus/libdconf-gdbus.a ../common/libdconf-common-shared.a
+libdconfsettings_so_LDADD = $(gio_LIBS) ../engine/libdconf-engine.a ../gdbus/libdconf-gdbus.a ../common/libdconf-common-shared.a ../shm/libdconf-shm-shared.a
libdconfsettings_so_LDFLAGS = -shared
libdconfsettings_so_SOURCES = \
../gvdb/gvdb-reader.c \
diff --git a/service/Makefile.am b/service/Makefile.am
index eed852a..96ad94e 100644
--- a/service/Makefile.am
+++ b/service/Makefile.am
@@ -1,12 +1,12 @@
include $(top_srcdir)/Makefile.gtester
-AM_CFLAGS = $(gio_CFLAGS) -I$(top_srcdir)/gvdb -I$(top_srcdir)/common -Wall -Wmissing-prototypes -Wwrite-strings
+AM_CFLAGS = $(gio_CFLAGS) -I$(top_srcdir)/gvdb -I$(top_srcdir)/common -I$(top_srcdir)/shm -Wall -Wmissing-prototypes -Wwrite-strings
libexec_PROGRAMS = dconf-service
dbusservice_DATA = ca.desrt.dconf.service
-dconf_service_LDADD = $(gio_LIBS) ../common/libdconf-common.a
+dconf_service_LDADD = $(gio_LIBS) ../common/libdconf-common.a ../shm/libdconf-shm.a
dconf_service_SOURCES = \
../gvdb/gvdb-builder.c \
../gvdb/gvdb-reader.c \
diff --git a/service/dconf-interfaces.c b/service/dconf-interfaces.c
index e824aca..45c6593 100644
--- a/service/dconf-interfaces.c
+++ b/service/dconf-interfaces.c
@@ -67,10 +67,6 @@ static const GDBusMethodInfo blame_method = {
(GDBusArgInfo **) blame_out
};
-static const GDBusPropertyInfo shmdir_property = {
- -1, (gchar *) "ShmDirectory", (gchar *) "s", G_DBUS_PROPERTY_INFO_FLAGS_READABLE
-};
-
static const GDBusMethodInfo *writer_methods[] = {
&change_method, &write_method, &writemany_method, NULL
};
@@ -83,10 +79,6 @@ static const GDBusMethodInfo *writer_info_methods[] = {
&blame_method, NULL
};
-static const GDBusPropertyInfo *writer_info_properties[] = {
- &shmdir_property, NULL
-};
-
const GDBusInterfaceInfo ca_desrt_dconf_Writer = {
-1, (gchar *) "ca.desrt.dconf.Writer",
(GDBusMethodInfo **) writer_methods,
@@ -98,5 +90,5 @@ const GDBusInterfaceInfo ca_desrt_dconf_WriterInfo = {
-1, (gchar *) "ca.desrt.dconf.WriterInfo",
(GDBusMethodInfo **) writer_info_methods,
(GDBusSignalInfo **) NULL,
- (GDBusPropertyInfo **) writer_info_properties
+ (GDBusPropertyInfo **) NULL
};
diff --git a/service/dconf-state.c b/service/dconf-state.c
index a30054c..79e9753 100644
--- a/service/dconf-state.c
+++ b/service/dconf-state.c
@@ -1,7 +1,5 @@
#include "dconf-state.h"
-#include "dconf-shmdir.h"
-
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
@@ -51,19 +49,6 @@ dconf_state_init_session (DConfState *state)
g_error ("Can not create directory '%s': %s",
state->db_dir, g_strerror (errno));
}
-
- state->shm_dir = dconf_shmdir_from_environment ();
-
- if (state->shm_dir == NULL)
- {
- const gchar *tmpdir = g_get_tmp_dir ();
- gchar *shmdir;
-
- shmdir = g_build_filename (tmpdir, "dconf.XXXXXX", NULL);
-
- if ((state->shm_dir = mkdtemp (shmdir)) == NULL)
- g_error ("Can not create reasonable shm directory");
- }
}
static gboolean
diff --git a/service/dconf-state.h b/service/dconf-state.h
index 6a2ac58..f68ac3e 100644
--- a/service/dconf-state.h
+++ b/service/dconf-state.h
@@ -11,7 +11,6 @@ typedef struct
GMainLoop *main_loop;
guint64 serial;
gchar *db_dir;
- gchar *shm_dir;
gchar *id;
} DConfState;
diff --git a/service/dconf-writer.c b/service/dconf-writer.c
index 4218977..6c44c40 100644
--- a/service/dconf-writer.c
+++ b/service/dconf-writer.c
@@ -23,6 +23,7 @@
#include "dconf-rebuilder.h"
#include "dconf-state.h"
+#include "dconf-shm.h"
#include <stdlib.h>
#include <unistd.h>
@@ -35,7 +36,6 @@ struct OPAQUE_TYPE__DConfWriter
DConfState *state;
gchar *name;
gchar *path;
- gchar *shm;
};
/* Each element must only contain the ASCII characters "[A-Z][a-z][0-9]_"
@@ -77,26 +77,6 @@ dconf_writer_list_existing (void)
return (gchar **) g_ptr_array_free (array, FALSE);
}
-static void
-dconf_writer_touch_shm (DConfWriter *writer)
-{
- gchar one = 1;
- gint fd;
-
- fd = open (writer->shm, O_WRONLY);
-
- if (fd >= 0)
- {
- write (fd, &one, sizeof one);
- close (fd);
-
- unlink (writer->shm);
- }
-
- else if (errno != ENOENT)
- unlink (writer->shm);
-}
-
gboolean
dconf_writer_write (DConfWriter *writer,
const gchar *name,
@@ -106,7 +86,7 @@ dconf_writer_write (DConfWriter *writer,
if (!dconf_rebuilder_rebuild (writer->path, "", &name, &value, 1, error))
return FALSE;
- dconf_writer_touch_shm (writer);
+ dconf_shm_flag (writer->name);
return TRUE;
}
@@ -123,7 +103,7 @@ dconf_writer_write_many (DConfWriter *writer,
values, n_items, error))
return FALSE;
- dconf_writer_touch_shm (writer);
+ dconf_shm_flag (writer->name);
return TRUE;
}
@@ -146,7 +126,7 @@ dconf_writer_change (DConfWriter *writer,
if (!dconf_rebuilder_rebuild (writer->path, prefix, keys, values, n_items, error))
return FALSE;
- dconf_writer_touch_shm (writer);
+ dconf_shm_flag (writer->name);
return TRUE;
}
@@ -172,7 +152,6 @@ dconf_writer_new (DConfState *state,
writer = g_slice_new (DConfWriter);
writer->state = state;
writer->path = g_build_filename (state->db_dir, name, NULL);
- writer->shm = g_build_filename (state->shm_dir, name, NULL);
writer->name = g_strdup (name);
return writer;
diff --git a/service/service.c b/service/service.c
index 13dcc88..19eadb2 100644
--- a/service/service.c
+++ b/service/service.c
@@ -447,24 +447,6 @@ writer_info_method (GDBusConnection *connection,
g_assert_not_reached ();
}
-static GVariant *
-writer_info_get_property (GDBusConnection *connection,
- const gchar *sender,
- const gchar *object_path,
- const gchar *interface_name,
- const gchar *property_name,
- GError **error,
- gpointer user_data)
-{
- DConfState *state = user_data;
-
- /* debugging... */
- if G_UNLIKELY (state->blame_mode)
- gather_blame_info (state, connection, sender, object_path, "GetProperty", NULL);
-
- return g_variant_new_string (state->shm_dir);
-}
-
static const GDBusInterfaceVTable *
subtree_dispatch (GDBusConnection *connection,
const gchar *sender,
@@ -506,7 +488,7 @@ subtree_dispatch (GDBusConnection *connection,
else if (strcmp (interface_name, "ca.desrt.dconf.WriterInfo") == 0)
{
static const GDBusInterfaceVTable vtable = {
- writer_info_method, writer_info_get_property, NULL
+ writer_info_method, NULL
};
*out_user_data = state;
diff --git a/shm/.gitignore b/shm/.gitignore
new file mode 100644
index 0000000..16d8712
--- /dev/null
+++ b/shm/.gitignore
@@ -0,0 +1,2 @@
+libdconf-shm.a
+libdconf-shm-shared.a
diff --git a/shm/Makefile.am b/shm/Makefile.am
new file mode 100644
index 0000000..b3068cb
--- /dev/null
+++ b/shm/Makefile.am
@@ -0,0 +1,12 @@
+include $(top_srcdir)/Makefile.gtester
+
+noinst_LIBRARIES = libdconf-shm.a libdconf-shm-shared.a
+libdconf_shm_a_CFLAGS = -Wall
+INCLUDES = $(glib_CFLAGS)
+
+libdconf_shm_a_SOURCES = \
+ dconf-shm.h \
+ dconf-shm.c
+
+libdconf_shm_shared_a_CFLAGS = -fPIC -DPIC $(libdconf_shm_a_CFLAGS)
+libdconf_shm_shared_a_SOURCES = $(libdconf_shm_a_SOURCES)
diff --git a/shm/dconf-shm.c b/shm/dconf-shm.c
new file mode 100644
index 0000000..62439a0
--- /dev/null
+++ b/shm/dconf-shm.c
@@ -0,0 +1,138 @@
+/*
+ * Copyright  2010 Codethink Limited
+ * Copyright  2012 Canonical Limited
+ *
+ * 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 licence, 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.
+ *
+ * Author: Ryan Lortie <desrt desrt ca>
+ */
+
+#include "dconf-shm.h"
+
+#include <sys/mman.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <errno.h>
+
+static gchar *
+dconf_shm_get_shmdir (void)
+{
+ static gchar *shmdir;
+
+ if (g_once_init_enter (&shmdir))
+ g_once_init_leave (&shmdir, g_build_filename (g_get_user_runtime_dir (), "dconf", NULL));
+
+ return shmdir;
+}
+
+void
+dconf_shm_close (guint8 *shm)
+{
+ if (shm)
+ munmap (shm, 1);
+}
+
+guint8 *
+dconf_shm_open (const gchar *name)
+{
+ const gchar *shmdir;
+ gchar *filename;
+ void *memory;
+ gint fd;
+
+ shmdir = dconf_shm_get_shmdir ();
+ filename = g_build_filename (shmdir, name, NULL);
+ memory = NULL;
+ fd = -1;
+
+ if (g_mkdir_with_parents (shmdir, 0700) != 0)
+ {
+ g_critical ("unable to create directory '%s': %s. dconf will not work properly.", shmdir, g_strerror (errno));
+ goto out;
+ }
+
+ fd = open (filename, O_RDWR | O_CREAT, 0600);
+ if (fd == -1)
+ {
+ g_critical ("unable to create file '%s': %s. dconf will not work properly.", filename, g_strerror (errno));
+ goto out;
+ }
+
+ if (ftruncate (fd, 1) != 0)
+ {
+ g_critical ("failed to allocate file '%s': %s. dconf will not work properly.", filename, g_strerror (errno));
+ goto out;
+ }
+
+ memory = mmap (NULL, 1, PROT_READ, MAP_SHARED, fd, 0);
+ g_assert (memory != NULL);
+
+ if (memory == MAP_FAILED)
+ {
+ g_critical ("failed to mmap file '%s': %s. dconf will not work properly.", filename, g_strerror (errno));
+ memory = NULL;
+ goto out;
+ }
+
+ out:
+ g_free (filename);
+ close (fd);
+
+ return memory;
+}
+
+void
+dconf_shm_flag (const gchar *name)
+{
+ const gchar *shmdir;
+ gchar *filename;
+ gint fd;
+
+ shmdir = dconf_shm_get_shmdir ();
+ filename = g_build_filename (shmdir, name, NULL);
+
+ fd = open (filename, O_WRONLY);
+ if (fd >= 0)
+ {
+ guint8 *shm;
+
+ /* Easiest thing to do here would be write(fd, "\1", 1); but this
+ * causes problems on kernels (ie: OpenBSD) that don't sync up
+ * their filesystem cache with mmap()ed regions.
+ *
+ * Using mmap() works everywhere.
+ */
+ shm = mmap (NULL, 1, PROT_WRITE, MAP_SHARED, fd, 0);
+
+ if (shm != MAP_FAILED)
+ {
+ *shm = 1;
+
+ munmap (shm, 1);
+ }
+ else
+ g_warning ("failed to invalidate mmap file '%s': %s.", filename, g_strerror (errno));
+
+ close (fd);
+
+ unlink (filename);
+ }
+
+ else if (errno != ENOENT)
+ unlink (filename);
+
+ g_free (filename);
+}
diff --git a/common/dconf-shmdir.h b/shm/dconf-shm.h
similarity index 69%
rename from common/dconf-shmdir.h
rename to shm/dconf-shm.h
index 3b08de6..407375a 100644
--- a/common/dconf-shmdir.h
+++ b/shm/dconf-shm.h
@@ -19,12 +19,16 @@
* Author: Ryan Lortie <desrt desrt ca>
*/
-#ifndef __dconf_shmdir_h__
-#define __dconf_shmdir_h__
+#ifndef __dconf_shm_h__
+#define __dconf_shm_h__
#include <glib.h>
G_GNUC_INTERNAL
-gchar *dconf_shmdir_from_environment (void);
+guint8 * dconf_shm_open (const gchar *name);
+G_GNUC_INTERNAL
+void dconf_shm_close (guint8 *shm);
+G_GNUC_INTERNAL
+void dconf_shm_flag (const gchar *name);
-#endif /* __dconf_shmdir_h__ */
+#endif /* __dconf_shm_h__ */
diff --git a/tests/Makefile.am b/tests/Makefile.am
index 6e6b415..fc4e551 100644
--- a/tests/Makefile.am
+++ b/tests/Makefile.am
@@ -19,6 +19,7 @@ changeset_SOURCES = changeset.c
engine_LIBS = \
../engine/libdconf-engine.a \
+ ../shm/libdconf-shm.a \
../common/libdconf-common.a
engine_with_stub_LIBS = \
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]