unofficial mirror of guile-devel@gnu.org 
 help / color / mirror / Atom feed
* [PATCH] Add a `read' method for ports
@ 2008-06-01 18:58 Ludovic Courtès
  2008-06-08 21:01 ` Ludovic Courtès
  0 siblings, 1 reply; 20+ messages in thread
From: Ludovic Courtès @ 2008-06-01 18:58 UTC (permalink / raw)
  To: guile-devel

[-- Attachment #1: Type: text/plain, Size: 1490 bytes --]

Hello,

This is a followup to these threads:

  http://thread.gmane.org/gmane.lisp.guile.devel/6549
  http://thread.gmane.org/gmane.lisp.guile.devel/7155 (to a lesser extent)

The attached patch aims to allow an `scm_c_read ()' call for N bytes to
translate into a `read(2)' for N bytes in the case of unbuffered file
ports (such as sockets); it also allows the port's buffer to be bypassed
when more data is requested than can fit in the buffer.

The first patch adds a `read' method to ports, which should read up to N
bytes into a caller-supplied buffer.  It is meant as a replacement for
`fill_input', which fills in the port's buffer regardless of the
application request, that is, reads a single byte in the case of
unbuffered ports.

The change is ABI-compatible since:

  1. `scm_t_ptob_descriptor's are always allocated by libguile code, not
     by the application code, so changing its size doesn't break the
     ABI;

  2. Apart from the added `read' field, the layout of
     `scm_t_ptob_descriptor' is unchanged, so inlines and macros that
     refer to it still work;

  3. `fill_input' is still honored when provided.

Subsequent patches make use of `scm_set_port_read ()' in file and string
ports, change `uniform-vector-read!' to use `scm_c_read ()', and add a
benchmark for this.  The benchmark shows that `uniform-vector-read!' is
around 3 orders of magnitude (!) faster on unbuffered file ports with
the new method.

OK to commit to 1.8 and master?

Thanks,
Ludovic.


[-- Warning: decoded text below may be mangled, UTF-8 assumed --]
[-- Attachment #2: The patch --]
[-- Type: text/x-patch, Size: 6276 bytes --]

From 3f9bae2580bca420b20d31e1074a2bab19aa3c09 Mon Sep 17 00:00:00 2001
From: =?utf-8?q?Ludovic=20Court=C3=A8s?= <ludo@gnu.org>
Date: Sat, 31 May 2008 22:24:45 +0200
Subject: [PATCH] Add `scm_set_port_read ()'.

---
 libguile/ports.c |  120 +++++++++++++++++++++++++++++++++++++++++++++---------
 libguile/ports.h |    5 ++-
 2 files changed, 104 insertions(+), 21 deletions(-)

diff --git a/libguile/ports.c b/libguile/ports.c
index b25a7d0..f887ec9 100644
--- a/libguile/ports.c
+++ b/libguile/ports.c
@@ -83,6 +83,10 @@
 #define HAVE_FTRUNCATE 1
 #endif
 
+#ifndef SCM_MIN
+# define SCM_MIN(A, B) ((A) < (B) ? (A) : (B))
+#endif
+
 \f
 /* The port kind table --- a dynamically resized array of port types.  */
 
@@ -162,6 +166,8 @@ scm_make_port_type (char *name,
       scm_ptobs[scm_numptob].seek = 0;
       scm_ptobs[scm_numptob].truncate = 0;
 
+      scm_ptobs[scm_numptob].read = NULL;
+
       scm_numptob++;
     }
   SCM_CRITICAL_SECTION_END;
@@ -177,6 +183,15 @@ scm_make_port_type (char *name,
 }
 
 void
+scm_set_port_read (scm_t_bits tc,
+		   size_t (* read) (SCM, void *, size_t))
+{
+  /* The provided `read' method overrides `fill_input'.  */
+  scm_ptobs[SCM_TC2PTOBNUM (tc)].fill_input = NULL;
+  scm_ptobs[SCM_TC2PTOBNUM (tc)].read = read;
+}
+
+void
 scm_set_port_mark (scm_t_bits tc, SCM (*mark) (SCM))
 {
   scm_ptobs[SCM_TC2PTOBNUM (tc)].mark = mark;
@@ -946,6 +961,7 @@ int
 scm_fill_input (SCM port)
 {
   scm_t_port *pt = SCM_PTAB_ENTRY (port);
+  const scm_t_ptob_descriptor *ptob = &scm_ptobs[SCM_PTOBNUM (port)];
 
   if (pt->read_buf == pt->putback_buf)
     {
@@ -957,7 +973,23 @@ scm_fill_input (SCM port)
       if (pt->read_pos < pt->read_end)
 	return *(pt->read_pos);
     }
-  return scm_ptobs[SCM_PTOBNUM (port)].fill_input (port);
+
+  if (ptob->fill_input != NULL)
+    /* Kept for backward compatibility.  */
+    return ptob->fill_input (port);
+  else
+    {
+      size_t count;
+
+      count = ptob->read (port, pt->read_buf, pt->read_buf_size);
+      pt->read_pos = pt->read_buf;
+      pt->read_end = pt->read_buf + count;
+
+      if (count == 0)
+	return EOF;
+      else
+	return (int) *pt->read_buf;
+    }
 }
 
 
@@ -1015,45 +1047,93 @@ scm_c_read (SCM port, void *buffer, size_t size)
 {
   scm_t_port *pt;
   size_t n_read = 0, n_available;
+  const scm_t_ptob_descriptor *ptob = &scm_ptobs[SCM_PTOBNUM (port)];
 
   SCM_VALIDATE_OPINPORT (1, port);
 
   pt = SCM_PTAB_ENTRY (port);
+  ptob = &scm_ptobs[SCM_PTOBNUM (port)];
   if (pt->rw_active == SCM_PORT_WRITE)
-    scm_ptobs[SCM_PTOBNUM (port)].flush (port);
+    ptob->flush (port);
 
   if (pt->rw_random)
     pt->rw_active = SCM_PORT_READ;
 
   if (SCM_READ_BUFFER_EMPTY_P (pt))
     {
-      if (scm_fill_input (port) == EOF)
-	return 0;
+      if ((ptob->fill_input != NULL) || (size <= pt->read_buf_size))
+	{
+	  /* Fill in PORT's buffer.  */
+	  if (scm_fill_input (port) == EOF)
+	    return 0;
+	}
+      else
+	/* Read directly into BUFFER, bypassing PORT's own buffer.  */
+	return (ptob->read (port, buffer, size));
     }
-  
+
   n_available = pt->read_end - pt->read_pos;
-  
-  while (n_available < size)
+
+  if (ptob->fill_input == NULL)
     {
-      memcpy (buffer, pt->read_pos, n_available);
-      buffer = (char *) buffer + n_available;
-      pt->read_pos += n_available;
-      n_read += n_available;
-      
-      if (SCM_READ_BUFFER_EMPTY_P (pt))
+      /* Use PTOB's `read' method.  */
+      size_t count;
+
+      count = SCM_MIN (n_available, size);
+      memcpy (buffer, pt->read_pos, count);
+      pt->read_pos += count;
+      n_read += count;
+
+      if (n_read < size)
 	{
-	  if (scm_fill_input (port) == EOF)
-	    return n_read;
+	  /* PORT's buffer is now empty but we haven't yet read SIZE
+	     bytes.  */
+	  size_t remaining = size - n_read;
+
+	  pt->read_pos = pt->read_end = pt->read_buf;
+
+	  if (remaining >= pt->read_buf_size)
+	    /* Read directly into BUFFER.  */
+	    n_read += ptob->read (port, buffer + n_read, remaining);
+	  else
+	    {
+	      /* The remaining size is less than PORT's buffer size.  */
+	      count = ptob->read (port, pt->read_buf, pt->read_buf_size);
+	      pt->read_end += count;
+
+	      memcpy (buffer + n_read, pt->read_buf, SCM_MIN (remaining, count));
+	      pt->read_pos += SCM_MIN (remaining, count);
+	      n_read += SCM_MIN (remaining, count);
+	    }
 	}
 
-      size -= n_available;
-      n_available = pt->read_end - pt->read_pos;
+      return n_read;
     }
+  else
+    {
+      /* For backward compatibility: use the `fill_input' method.  */
+      while (n_available < size)
+	{
+	  memcpy (buffer, pt->read_pos, n_available);
+	  buffer = (char *) buffer + n_available;
+	  pt->read_pos += n_available;
+	  n_read += n_available;
+
+	  if (SCM_READ_BUFFER_EMPTY_P (pt))
+	    {
+	      if (scm_fill_input (port) == EOF)
+		return n_read;
+	    }
+
+	  size -= n_available;
+	  n_available = pt->read_end - pt->read_pos;
+	}
 
-  memcpy (buffer, pt->read_pos, size);
-  pt->read_pos += size;
+      memcpy (buffer, pt->read_pos, size);
+      pt->read_pos += size;
 
-  return n_read + size;
+      return n_read + size;
+    }
 }
 #undef FUNC_NAME
 
diff --git a/libguile/ports.h b/libguile/ports.h
index 084a555..17bbe09 100644
--- a/libguile/ports.h
+++ b/libguile/ports.h
@@ -178,12 +178,13 @@ typedef struct scm_t_ptob_descriptor
   void (*flush) (SCM port);
 
   void (*end_input) (SCM port, int offset);
-  int (*fill_input) (SCM port);
+  int (*fill_input) (SCM port);   /* deprecated: use `read' instead */
   int (*input_waiting) (SCM port);
 
   off_t (*seek) (SCM port, off_t OFFSET, int WHENCE);
   void (*truncate) (SCM port, off_t length);
 
+  size_t (*read) (SCM port, void *data, size_t size);
 } scm_t_ptob_descriptor;
 
 #define SCM_TC2PTOBNUM(x) (0x0ff & ((x) >> 8))
@@ -205,6 +206,8 @@ SCM_API scm_t_bits scm_make_port_type (char *name,
 				       void (*write) (SCM port, 
 						      const void *data,
 						      size_t size));
+SCM_API void scm_set_port_read (scm_t_bits tc,
+				size_t (* read) (SCM, void *, size_t));
 SCM_API void scm_set_port_mark (scm_t_bits tc, SCM (*mark) (SCM));
 SCM_API void scm_set_port_free (scm_t_bits tc, size_t (*free) (SCM));
 SCM_API void scm_set_port_print (scm_t_bits tc,
-- 
1.5.5


[-- Warning: decoded text below may be mangled, UTF-8 assumed --]
[-- Attachment #3: 0002-Use-scm_set_port_read-in-file-ports.patch --]
[-- Type: text/x-patch, Size: 1863 bytes --]

From 406d36e849d2d88452f816e2325c5607250b28dc Mon Sep 17 00:00:00 2001
From: =?utf-8?q?Ludovic=20Court=C3=A8s?= <ludo@gnu.org>
Date: Sat, 31 May 2008 22:26:29 +0200
Subject: [PATCH] Use `scm_set_port_read ()' in file ports.

---
 libguile/fports.c |   23 ++++++++---------------
 1 files changed, 8 insertions(+), 15 deletions(-)

diff --git a/libguile/fports.c b/libguile/fports.c
index efbd278..1208836 100644
--- a/libguile/fports.c
+++ b/libguile/fports.c
@@ -582,29 +582,21 @@ fport_wait_for_input (SCM port)
 
 static void fport_flush (SCM port);
 
-/* fill a port's read-buffer with a single read.  returns the first
-   char or EOF if end of file.  */
-static int
-fport_fill_input (SCM port)
+/* Read up to SIZE bytes from PORT into BUFFER.  */
+static size_t
+fport_read (SCM port, void *buffer, size_t size)
 {
   long count;
-  scm_t_port *pt = SCM_PTAB_ENTRY (port);
   scm_t_fport *fp = SCM_FSTREAM (port);
 
 #ifndef __MINGW32__
   fport_wait_for_input (port);
 #endif /* !__MINGW32__ */
-  SCM_SYSCALL (count = read (fp->fdes, pt->read_buf, pt->read_buf_size));
+  SCM_SYSCALL (count = read (fp->fdes, buffer, size));
   if (count == -1)
     scm_syserror ("fport_fill_input");
-  if (count == 0)
-    return EOF;
-  else
-    {
-      pt->read_pos = pt->read_buf;
-      pt->read_end = pt->read_buf + count;
-      return *pt->read_buf;
-    }
+
+  return (size_t) count;
 }
 
 static off_t_or_off64_t
@@ -906,8 +898,9 @@ fport_free (SCM port)
 static scm_t_bits
 scm_make_fptob ()
 {
-  scm_t_bits tc = scm_make_port_type ("file", fport_fill_input, fport_write);
+  scm_t_bits tc = scm_make_port_type ("file", NULL, fport_write);
 
+  scm_set_port_read            (tc, fport_read);
   scm_set_port_free            (tc, fport_free);
   scm_set_port_print           (tc, fport_print);
   scm_set_port_flush           (tc, fport_flush);
-- 
1.5.5


[-- Warning: decoded text below may be mangled, UTF-8 assumed --]
[-- Attachment #4: 0003-Use-scm_c_read-in-uniform-vector-read.patch --]
[-- Type: text/x-patch, Size: 3307 bytes --]

From a4e465c07d450566115f43844a042e127cb0eeea Mon Sep 17 00:00:00 2001
From: =?utf-8?q?Ludovic=20Court=C3=A8s?= <ludo@gnu.org>
Date: Sat, 31 May 2008 22:27:46 +0200
Subject: [PATCH] Use `scm_c_read ()' in `uniform-vector-read!'.

---
 libguile/srfi-4.c |   61 ++++++++++++++++++++---------------------------------
 1 files changed, 23 insertions(+), 38 deletions(-)

diff --git a/libguile/srfi-4.c b/libguile/srfi-4.c
index 7d22f8b..5bcdcd5 100644
--- a/libguile/srfi-4.c
+++ b/libguile/srfi-4.c
@@ -1,6 +1,6 @@
 /* srfi-4.c --- Uniform numeric vector datatypes.
  *
- * 	Copyright (C) 2001, 2004, 2006 Free Software Foundation, Inc.
+ * 	Copyright (C) 2001, 2004, 2006, 2008 Free Software Foundation, Inc.
  *
  * This library is free software; you can redistribute it and/or
  * modify it under the terms of the GNU Lesser General Public
@@ -824,6 +824,17 @@ SCM_DEFINE (scm_uniform_vector_length, "uniform-vector-length", 1, 0, 0,
 }
 #undef FUNC_NAME
 
+/* Release the array handle pointed to by ARG.  */
+static void
+release_array (void *arg)
+{
+  scm_t_array_handle *handle;
+
+  handle = (scm_t_array_handle *) arg;
+  scm_array_handle_release (handle);
+}
+
+
 SCM_DEFINE (scm_uniform_vector_read_x, "uniform-vector-read!", 1, 3, 0,
            (SCM uvec, SCM port_or_fd, SCM start, SCM end),
 	    "Fill the elements of @var{uvec} by reading\n"
@@ -846,7 +857,7 @@ SCM_DEFINE (scm_uniform_vector_read_x, "uniform-vector-read!", 1, 3, 0,
 #define FUNC_NAME s_scm_uniform_vector_read_x
 {
   scm_t_array_handle handle;
-  size_t vlen, sz, ans;
+  size_t vlen, sz, ans, bytes_read;
   ssize_t inc;
   size_t cstart, cend;
   size_t remaining, off;
@@ -886,52 +897,26 @@ SCM_DEFINE (scm_uniform_vector_read_x, "uniform-vector-read!", 1, 3, 0,
 
   if (SCM_NIMP (port_or_fd))
     {
-      scm_t_port *pt = SCM_PTAB_ENTRY (port_or_fd);
+      scm_dynwind_begin (0);
+      scm_dynwind_unwind_handler (release_array, &handle, 0);
 
-      if (pt->rw_active == SCM_PORT_WRITE)
-	scm_flush (port_or_fd);
+      bytes_read = scm_c_read (port_or_fd, base + off, remaining);
 
-      ans = cend - cstart;
-      while (remaining > 0)
-	{
-	  if (pt->read_pos < pt->read_end)
-	    {
-	      size_t to_copy = min (pt->read_end - pt->read_pos,
-				    remaining);
-	      
-	      memcpy (base + off, pt->read_pos, to_copy);
-	      pt->read_pos += to_copy;
-	      remaining -= to_copy;
-	      off += to_copy;
-	    }
-	  else
-	    {
-	      if (scm_fill_input (port_or_fd) == EOF)
-		{
-		  if (remaining % sz != 0)
-		    SCM_MISC_ERROR ("unexpected EOF", SCM_EOL);
-		  ans -= remaining / sz;
-		  break;
-		}
-	    }
-	}
-      
-      if (pt->rw_random)
-	pt->rw_active = SCM_PORT_READ;
+      scm_dynwind_end ();
     }
   else /* file descriptor.  */
     {
       int fd = scm_to_int (port_or_fd);
-      int n;
 
-      SCM_SYSCALL (n = read (fd, base + off, remaining));
-      if (n == -1)
+      SCM_SYSCALL (bytes_read = read (fd, base + off, remaining));
+      if (bytes_read == -1)
 	SCM_SYSERROR;
-      if (n % sz != 0)
-	SCM_MISC_ERROR ("unexpected EOF", SCM_EOL);
-      ans = n / sz;
     }
 
+  if (bytes_read % sz != 0)
+    SCM_MISC_ERROR ("unexpected EOF", SCM_EOL);
+  ans = bytes_read / sz;
+
   scm_array_handle_release (&handle);
 
   return scm_from_size_t (ans);
-- 
1.5.5


[-- Warning: decoded text below may be mangled, UTF-8 assumed --]
[-- Attachment #5: 0004-Use-scm_set_port_read-in-string-ports.patch --]
[-- Type: text/x-patch, Size: 2042 bytes --]

From 9ff77d3b9fe8384841743c4e7a3ef5b33d9a83b5 Mon Sep 17 00:00:00 2001
From: =?utf-8?q?Ludovic=20Court=C3=A8s?= <ludo@gnu.org>
Date: Sun, 1 Jun 2008 19:21:35 +0200
Subject: [PATCH] Use `scm_set_port_read ()' in string ports.

---
 libguile/strports.c |   27 ++++++++++++++++++---------
 1 files changed, 18 insertions(+), 9 deletions(-)

diff --git a/libguile/strports.c b/libguile/strports.c
index 8659ccf..f6fb24b 100644
--- a/libguile/strports.c
+++ b/libguile/strports.c
@@ -1,4 +1,4 @@
-/* Copyright (C) 1995,1996,1998,1999,2000,2001,2002, 2003, 2005, 2006 Free Software Foundation, Inc.
+/* Copyright (C) 1995,1996,1998,1999,2000,2001,2002, 2003, 2005, 2006, 2008 Free Software Foundation, Inc.
  * 
  * This library is free software; you can redistribute it and/or
  * modify it under the terms of the GNU Lesser General Public
@@ -45,6 +45,10 @@
 #include <string.h>
 #endif
 
+#ifndef SCM_MIN
+# define SCM_MIN(A, B) ((A) < (B) ? (A) : (B))
+#endif
+
 \f
 
 /* {Ports - string ports}
@@ -93,15 +97,19 @@
 scm_t_bits scm_tc16_strport;
 
 
-static int
-stfill_buffer (SCM port)
+/* Read up to SIZE bytes from PORT into BUFFER.  */
+static size_t
+st_read (SCM port, void *buffer, size_t size)
 {
+  size_t available, count;
   scm_t_port *pt = SCM_PTAB_ENTRY (port);
-  
-  if (pt->read_pos >= pt->read_end)
-    return EOF;
-  else
-    return scm_return_first_int (*pt->read_pos, port);
+
+  available = pt->read_end - pt->read_pos;
+  count = SCM_MIN (available, size);
+
+  memcpy (buffer, pt->read_pos, count);
+
+  return count;
 }
 
 /* change the size of a port's string to new_size.  this doesn't
@@ -538,8 +546,9 @@ scm_eval_string (SCM string)
 static scm_t_bits
 scm_make_stptob ()
 {
-  scm_t_bits tc = scm_make_port_type ("string", stfill_buffer, st_write);
+  scm_t_bits tc = scm_make_port_type ("string", NULL, st_write);
 
+  scm_set_port_read        (tc, st_read);
   scm_set_port_mark        (tc, scm_markstream);
   scm_set_port_end_input   (tc, st_end_input);
   scm_set_port_flush       (tc, st_flush);
-- 
1.5.5


[-- Warning: decoded text below may be mangled, UTF-8 assumed --]
[-- Attachment #6: 0005-Add-uniform-vector-read-benchmark.patch --]
[-- Type: text/x-patch, Size: 3132 bytes --]

From a627bb3639d2a1cffccddf55de679c129ee0cda1 Mon Sep 17 00:00:00 2001
From: =?utf-8?q?Ludovic=20Court=C3=A8s?= <ludo@gnu.org>
Date: Sun, 1 Jun 2008 19:31:36 +0200
Subject: [PATCH] Add `uniform-vector-read!' benchmark.

---
 benchmark-suite/Makefile.am                       |   11 ++--
 benchmark-suite/benchmarks/uniform-vector-read.bm |   53 +++++++++++++++++++++
 2 files changed, 59 insertions(+), 5 deletions(-)
 create mode 100644 benchmark-suite/benchmarks/uniform-vector-read.bm

diff --git a/benchmark-suite/Makefile.am b/benchmark-suite/Makefile.am
index a8f4719..3993faf 100644
--- a/benchmark-suite/Makefile.am
+++ b/benchmark-suite/Makefile.am
@@ -1,7 +1,8 @@
-SCM_BENCHMARKS = benchmarks/0-reference.bm	\
-	         benchmarks/continuations.bm	\
-                 benchmarks/if.bm		\
-                 benchmarks/logand.bm		\
-		 benchmarks/read.bm
+SCM_BENCHMARKS = benchmarks/0-reference.bm		\
+	         benchmarks/continuations.bm		\
+                 benchmarks/if.bm			\
+                 benchmarks/logand.bm			\
+		 benchmarks/read.bm			\
+		 benchmarks/uniform-vector-read.bm
 
 EXTRA_DIST = guile-benchmark lib.scm $(SCM_BENCHMARKS)
diff --git a/benchmark-suite/benchmarks/uniform-vector-read.bm b/benchmark-suite/benchmarks/uniform-vector-read.bm
new file mode 100644
index 0000000..d288f0b
--- /dev/null
+++ b/benchmark-suite/benchmarks/uniform-vector-read.bm
@@ -0,0 +1,53 @@
+;;; uniform-vector-read.bm --- Exercise binary I/O primitives.  -*- Scheme -*-
+;;;
+;;; Copyright (C) 2008 Free Software Foundation, Inc.
+;;;
+;;; 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, 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 software; see the file COPYING.  If not, write to
+;;; the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+;;; Boston, MA 02110-1301 USA
+
+(define-module (benchmarks uniform-vector-read)
+  :use-module (benchmark-suite lib)
+  :use-module (srfi srfi-4))
+
+(define file-name
+  (tmpnam))
+
+(define %buffer-size
+  7777)
+
+(define buf
+  (make-u8vector %buffer-size))
+
+(define str
+  (make-string %buffer-size))
+
+\f
+(with-benchmark-prefix "uniform-vector-read!"
+
+  (benchmark "uniform-vector-write" 500
+    (let ((output (open-output-file file-name)))
+      (uniform-vector-write buf output)
+      (close output)))
+
+  (benchmark "uniform-vector-read!" 500
+    (let ((input (open-input-file file-name)))
+      (setvbuf input _IONBF)
+      (uniform-vector-read! buf input)
+      (close input)))
+
+  (benchmark "string port" 5000
+    (let ((input (open-input-string str)))
+      (uniform-vector-read! buf input)
+      (close input))))
-- 
1.5.5


^ permalink raw reply related	[flat|nested] 20+ messages in thread

end of thread, other threads:[~2008-09-15 19:20 UTC | newest]

Thread overview: 20+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2008-06-01 18:58 [PATCH] Add a `read' method for ports Ludovic Courtès
2008-06-08 21:01 ` Ludovic Courtès
2008-06-09 17:01   ` Neil Jerram
2008-06-09 20:59     ` Ludovic Courtès
2008-06-11 21:38       ` Neil Jerram
2008-06-12  0:19         ` Neil Jerram
2008-06-12  7:58         ` Ludovic Courtès
2008-06-13  0:18           ` Neil Jerram
2008-06-24 20:50             ` Ludovic Courtès
2008-07-15 19:24               ` Ludovic Courtès
2008-07-15 20:05                 ` Neil Jerram
2008-07-15 22:08               ` Neil Jerram
2008-07-16 21:41                 ` Ludovic Courtès
2008-09-12 20:09                   ` Ludovic Courtès
2008-09-14 23:08                   ` Neil Jerram
2008-09-15  1:01                     ` Neil Jerram
2008-09-15  7:47                       ` Ludovic Courtès
2008-09-15  7:44                     ` Ludovic Courtès
2008-09-15 19:18                       ` Neil Jerram
2008-09-15 19:20                         ` Ludovic Courtès

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).