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

Re: [Libguestfs] Outline Rust bindings



Updated patch.  This one doesn't compile, with some kind of obscure
Rust error that I can't be bothered with right now.

Instructions in the previous message.

Rich.

-- 
Richard Jones, Virtualization Group, Red Hat http://people.redhat.com/~rjones
Read my programming and virtualization blog: http://rwmj.wordpress.com
virt-p2v converts physical machines to virtual machines.  Boot with a
live CD or over the network (PXE) and turn machines into KVM guests.
http://libguestfs.org/virt-v2v
>From 558998bcb59198a45c5f008a192802813b339547 Mon Sep 17 00:00:00 2001
From: "Richard W.M. Jones" <rjones redhat com>
Date: Wed, 21 Oct 2015 13:48:39 +0100
Subject: [PATCH] Add Rust (language) bindings.

On Fedora you will need to install the Rust copr from:
https://copr.fedoraproject.org/coprs/fabiand/rust-binary/
until the package is added to Fedora:
https://bugzilla.redhat.com/show_bug.cgi?id=915043
---
 .gitignore            |   5 +
 Makefile.am           |   3 +
 README                |   3 +
 configure.ac          |  17 ++
 generator/Makefile.am |   2 +
 generator/main.ml     |   3 +
 generator/rust.ml     | 441 ++++++++++++++++++++++++++++++++++++++++++++++++++
 rust/Cargo.toml.in    |   9 ++
 rust/Makefile.am      |  39 +++++
 rust/src/.gitignore   |   0
 src/guestfs.pod       |   2 +
 11 files changed, 524 insertions(+)
 create mode 100644 generator/rust.ml
 create mode 100644 rust/Cargo.toml.in
 create mode 100644 rust/Makefile.am
 create mode 100644 rust/src/.gitignore

diff --git a/.gitignore b/.gitignore
index d5c5d1e..dcf9d63 100644
--- a/.gitignore
+++ b/.gitignore
@@ -468,6 +468,11 @@ Makefile.in
 /ruby/ext/guestfs/mkmf.log
 /ruby/Rakefile
 /ruby/stamp-rdoc
+/rust/Cargo.lock
+/rust/Cargo.toml
+/rust/src/ffi.rs
+/rust/src/lib.rs
+/rust/target
 /run
 /sparsify/.depend
 /sparsify/stamp-virt-sparsify.pod
diff --git a/Makefile.am b/Makefile.am
index 713df42..aa96fc3 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -123,6 +123,9 @@ endif
 if HAVE_GOLANG
 SUBDIRS += golang golang/examples
 endif
+if HAVE_RUST
+SUBDIRS += rust
+endif
 
 # Unconditional because nothing is built yet.
 SUBDIRS += csharp
diff --git a/README b/README
index 19a1fb2..e8b2e9b 100644
--- a/README
+++ b/README
@@ -223,6 +223,9 @@ The full requirements are described below.
 +--------------+-------------+---+-----------------------------------------+
 | golang       | 1.1.1       | O | For the Go bindings.                    |
 +--------------+-------------+---+-----------------------------------------+
+| rustc        | 1.0         | O | For the Rust bindings.                  |
+| cargo        |             |   |                                         |
++--------------+-------------+---+-----------------------------------------+
 | valgrind     |             | O | For testing for memory problems.        |
 +--------------+-------------+---+-----------------------------------------+
 | Sys::Virt    |             | O | Perl bindings for libvirt.              |
diff --git a/configure.ac b/configure.ac
index 4f6650e..e5b8ade 100644
--- a/configure.ac
+++ b/configure.ac
@@ -1578,6 +1578,21 @@ AS_IF([test "x$enable_golang" != "xno"],[
 ],[GOLANG=no])
 AM_CONDITIONAL([HAVE_GOLANG],[test "x$GOLANG" != "xno"])
 
+dnl Rust compiler.
+AC_ARG_ENABLE([rust],
+    AS_HELP_STRING([--disable-rust], [disable Rust language bindings]),
+        [],
+        [enable_rust=yes])
+AS_IF([test "x$enable_rust" != "xno"],[
+    AC_CHECK_PROG([RUSTC],[rustc],[rustc],[no])
+    AC_CHECK_PROG([CARGO],[cargo],[cargo],[no])
+],[
+    RUSTC=no
+    CARGO=no
+])
+AM_CONDITIONAL([HAVE_RUST],
+               [test "x$RUSTC" != "xno" && test "x$CARGO" != "xno"])
+
 dnl Check for Perl modules needed by Perl virt tools (virt-df, etc.)
 AS_IF([test "x$PERL" != "xno"],[
     missing_perl_modules=no
@@ -1791,6 +1806,8 @@ AC_CONFIG_FILES([Makefile
                  ruby/Rakefile
                  ruby/examples/Makefile
                  ruby/ext/guestfs/extconf.rb
+                 rust/Cargo.toml
+                 rust/Makefile
                  sparsify/Makefile
                  src/Makefile
                  src/libguestfs.pc
diff --git a/generator/Makefile.am b/generator/Makefile.am
index a3fe50d..34ac5c5 100644
--- a/generator/Makefile.am
+++ b/generator/Makefile.am
@@ -48,6 +48,7 @@ sources = \
 	prepopts.mli \
 	python.ml \
 	ruby.ml \
+	rust.ml \
 	structs.ml \
 	structs.mli \
 	tests_c_api.ml \
@@ -85,6 +86,7 @@ objects = \
 	lua.cmo \
 	gobject.cmo \
 	golang.cmo \
+	rust.cmo \
 	bindtests.cmo \
 	errnostring.cmo \
 	customize.cmo \
diff --git a/generator/main.ml b/generator/main.ml
index 1e0e7d6..d56f028 100644
--- a/generator/main.ml
+++ b/generator/main.ml
@@ -43,6 +43,7 @@ open Erlang
 open Lua
 open Gobject
 open Golang
+open Rust
 open Bindtests
 open Errnostring
 open Customize
@@ -165,6 +166,8 @@ Run it from the top source directory using the command
   output_to "lua/bindtests.lua" generate_lua_bindtests;
   output_to "golang/src/libguestfs.org/guestfs/guestfs.go" generate_golang_go;
   output_to "golang/bindtests.go" generate_golang_bindtests;
+  output_to "rust/src/ffi.rs" generate_rust_ffi_rs;
+  output_to "rust/src/lib.rs" generate_rust_lib_rs;
 
   output_to "gobject/bindtests.js" generate_gobject_js_bindtests;
   output_to "gobject/Makefile.inc" generate_gobject_makefile;
diff --git a/generator/rust.ml b/generator/rust.ml
new file mode 100644
index 0000000..f73b0e7
--- /dev/null
+++ b/generator/rust.ml
@@ -0,0 +1,441 @@
+(* libguestfs
+ * Copyright (C) 2015 Red Hat 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 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
+ *)
+
+(* Please read generator/README first. *)
+
+open Printf
+
+open Types
+open Utils
+open Pr
+open Docstrings
+open Optgroups
+open Actions
+open Structs
+open C
+open Events
+
+(* Generate rust bindings. *)
+
+let generate_rust_ffi_rs () =
+  generate_header CPlusPlusStyle LGPLv2plus;
+
+  pr "\
+use libc::{
+  c_char, c_float, c_int, c_uint, c_void,
+  int32_t, int64_t,
+  size_t,
+  uint32_t, uint64_t,
+};
+
+// Represents the opaque handle (guestfs_h).
+#[allow(non_camel_case_types)]
+pub enum guestfs_h {}
+
+";
+
+  (* Generate FFI C structs. *)
+  pr "// FFI C structs.\n";
+  pr "\n";
+  List.iter (
+    fun { s_name = typ; s_cols = cols } ->
+      pr "#[repr(C)]\n";
+      pr "struct %s {\n" typ;
+      List.iter (
+        function
+        | name, FString ->
+           pr "    pub %s: *const c_char,\n" name
+        | name, FBuffer ->
+           pr "    pub %s_len: uint32_t,\n" name;
+           pr "    pub %s: *const c_char,\n" name
+        | name, FUUID ->
+           pr "    pub %s: [c_char; 32],\n" name
+        | name, (FBytes|FInt64) ->
+           pr "    pub %s: int64_t,\n" name
+        | name, FUInt64 ->
+           pr "    pub %s: uint64_t,\n" name
+        | name, FInt32 ->
+           pr "    pub %s: int32_t,\n" name
+        | name, FUInt32 ->
+           pr "    pub %s: uint32_t,\n" name
+        | name, FOptPercent ->
+           pr "    pub %s: c_float,\n" name
+        | name, FChar ->
+           pr "    pub %s: c_char,\n" name
+      ) cols;
+      pr "}\n";
+      pr "\n";
+      pr "#[repr(C)]\n";
+      pr "struct %s_list {\n" typ;
+      pr "    pub len: uint32_t,\n";
+      pr "    pub val: *mut %s,\n" typ;
+      pr "}\n";
+      pr "\n"
+  ) external_structs;
+
+  (* Generate FFI C calls. *)
+  pr "\
+#[link (name = \"guestfs\")]
+extern {
+    pub fn guestfs_create () -> *mut guestfs_h;
+    pub fn guestfs_create_flags (flags: c_uint, ...) -> *mut guestfs_h;
+    pub fn guestfs_close (g: *mut guestfs_h);
+
+    pub fn guestfs_last_error (g: *mut guestfs_h) -> *const c_char;
+
+    // FFI C calls.
+";
+
+  List.iter (
+    fun { name = name; style = ret, args, optargs } ->
+      pr "    pub fn guestfs_%s (g: *mut guestfs_h" name;
+      List.iter (
+        function
+        | Bool n
+        | Int n -> pr ", %s: c_int" n
+        | Int64 n -> pr ", %s: int64_t" n
+        | String n | Device n | Mountable n | Pathname n
+        | Dev_or_Path n | Mountable_or_Path n
+        | Key n | FileIn n | FileOut n
+        | GUID n
+        | OptString n ->
+           pr ", %s: *const c_char" n
+        | BufferIn n ->
+           pr ", %s: *const c_char, %s_size: size_t" n n
+        | StringList n
+        | DeviceList n
+        | FilenameList n ->
+           pr ", %s: *const *const c_char" n
+        | Pointer (t, n) ->
+           pr ", %s: *mut c_void" n
+      ) args;
+      (match ret with
+       | RBufferOut _ -> pr ", size_r: size_t"
+       | _ -> ()
+      );
+      if optargs <> [] then
+        pr ", ...";
+      pr ")";
+      (match ret with
+       | RErr -> pr " -> c_int"
+       | RInt _ -> pr " -> c_int"
+       | RInt64 _ -> pr " -> int64_t"
+       | RBool _ -> pr " -> c_int"
+       | RConstString _ -> pr " -> *const c_char"
+       | RConstOptString _ -> pr " -> *const c_char"
+       | RString _ -> pr " -> *mut c_char"
+       | RStringList _ -> pr " -> *mut *mut c_char"
+       | RStruct (_, typ) -> pr " -> *mut %s" typ
+       | RStructList (_, typ) -> pr " -> *mut %s_list" typ
+       | RHashtable _ -> pr " -> *mut *mut c_char"
+       | RBufferOut _ -> pr " -> *mut c_char"
+      );
+      pr ";\n"
+  ) public_functions_sorted;
+
+  pr "
+    // FFI C struct functions.
+";
+
+  List.iter (
+    fun { s_name = typ; s_cols = cols } ->
+      pr "    pub fn guestfs_free_%s (%s: *mut %s);\n" typ typ typ
+  ) external_structs;
+
+  pr "}"
+
+let generate_rust_lib_rs () =
+  generate_header CPlusPlusStyle LGPLv2plus;
+
+  pr "\
+extern crate libc;
+
+use libc::{
+  c_char, c_float, c_int, c_uint, c_void,
+  int32_t, int64_t,
+  size_t,
+  uint32_t, uint64_t,
+};
+
+use std::collections::HashMap;
+use std::ffi::CStr;
+use std::ffi::CString;
+use std::ptr;
+use std::str;
+use std::sync::Arc;
+
+// Temporarily allow dead_code in the ffi submodule.  Eventually
+// we can remove this once every ffi function has a safe wrapper. XXX
+#[allow(dead_code)]
+mod ffi;
+
+struct GuestfsHandleInternal {
+    g: *mut ffi::guestfs_h,
+}
+
+impl Drop for GuestfsHandleInternal {
+    fn drop (&mut self) {
+        unsafe { ffi::guestfs_close (self.g); }
+    }
+}
+
+pub struct GuestfsHandle {
+    rc: Arc<GuestfsHandleInternal>,
+}
+
+";
+
+  List.iter (
+    fun { s_camel_name = typ; s_cols = cols } ->
+      pr "pub struct %s {\n" typ;
+      List.iter (
+        function
+        | name, FString ->
+           pr "    pub %s: String,\n" name
+        | name, FBuffer ->
+           pr "    pub %s: Vec<u8>,\n" name
+        | name, FUUID ->
+           pr "    pub %s: [u8;32],\n" name
+        | name, (FBytes|FInt64) ->
+           pr "    pub %s: i64,\n" name
+        | name, FUInt64 ->
+           pr "    pub %s: u64,\n" name
+        | name, FInt32 ->
+           pr "    pub %s: i32,\n" name
+        | name, FUInt32 ->
+           pr "    pub %s: u32,\n" name
+        | name, FOptPercent ->
+           pr "    pub %s: f32,\n" name
+        | name, FChar ->
+           pr "    pub %s: char,\n" name
+      ) cols;
+      pr "}\n";
+      pr "type %sList = Vec<%s>;\n" typ typ;
+      pr "\n"
+  ) external_structs;
+
+  pr "\
+fn get_last_error (g: *mut ffi::guestfs_h) -> String {
+    let error_cstr = unsafe { ffi::guestfs_last_error (g) };
+    let error_buf = unsafe { CStr::from_ptr (error_cstr).to_bytes() };
+    let error = str::from_utf8 (error_buf).unwrap().to_owned();
+    return error;
+}
+
+impl GuestfsHandle {
+    pub fn create () -> Result<GuestfsHandle, String> {
+        let g = unsafe { ffi::guestfs_create () };
+        if g.is_null() {
+            //let errno = Error::last_os_error().raw_os_error();
+            // XXX convert errno to a string
+            return Err (\"XXX\".to_string());
+        }
+
+        let rc = Arc::new (GuestfsHandleInternal { g: g });
+        Ok (GuestfsHandle { rc: rc })
+    }
+
+";
+
+  List.iter (
+    function
+    | { name = name; style = _, _, (_::_) } ->
+       pr "    // pub fn %s has optional arguments, functions with\n" name;
+       pr "    // optional arguments are not yet implemented (XXX)\n"
+
+    | { name = name; style = ret, args, [] } ->
+       pr "    pub fn %s (&mut self" name;
+       List.iter (
+         function
+         | Bool n -> pr ", %s: bool" n
+         | Int n -> pr ", %s: i32" n
+         | Int64 n -> pr ", %s: i64" n
+         | String n | Device n | Mountable n | Pathname n
+         | Dev_or_Path n | Mountable_or_Path n
+         | Key n | FileIn n | FileOut n
+         | GUID n ->
+            pr ", %s: String" n
+         | OptString n ->
+            pr ", %s: Option<String>" n
+         | BufferIn n ->
+            pr ", %s: Vec<u8>" n
+         | StringList n
+         | DeviceList n
+         | FilenameList n ->
+            pr ", %s: Vec<String>" n
+         | Pointer (t, n) ->
+            pr ", %s: i64 /* not impl */" n
+       ) args;
+       pr ") -> Result<";
+       (match ret with
+        | RErr -> pr "()"
+        | RInt _ -> pr "i32"
+        | RInt64 _ -> pr "i64"
+        | RBool _ -> pr "bool"
+        | RConstString _
+        | RConstOptString _
+        | RString _ -> pr "String"
+        | RStringList _ -> pr "Vec<String>"
+        | RStruct (_, typ) -> pr "%s" (camel_name_of_struct typ)
+        | RStructList (_, typ) -> pr "Vec<%s>" (camel_name_of_struct typ)
+        | RHashtable _ -> pr "HashMap<String, String>"
+        | RBufferOut _ -> pr "Vec<u8>"
+       );
+       pr ", String> {\n";
+
+       (* Some of the arguments need some pre-conversion. *)
+       List.iter (
+         function
+         | Bool n
+         | Int n
+         | Int64 n -> ()
+         | String n | Device n | Mountable n | Pathname n
+         | Dev_or_Path n | Mountable_or_Path n
+         | Key n | FileIn n | FileOut n
+         | GUID n
+         | BufferIn n ->
+            (* http://stackoverflow.com/a/28649572 *)
+            pr "        let %s_cstr = CString::new(%s).unwrap();\n" n n;
+            pr "        let %s_cptr = %s_cstr.as_ptr();\n" n n
+         | OptString n ->
+            pr "        let %s_cstr = match %s {\n" n n;
+            pr "            None => None,\n";
+            pr "            Some (%s) => Some (CString::new(%s).unwrap()),\n"
+               n n;
+            pr "        };\n";
+            pr "        let %s_cptr = match %s_cstr {\n" n n;
+            pr "            None => ptr::null(),\n";
+            pr "            Some (%s_cstr) => %s_cstr.as_ptr(),\n" n n;
+            pr "        };\n";
+         | StringList n
+         | DeviceList n
+         | FilenameList n ->
+            pr "        let %s_1 = %s.iter()\n" n n;
+            pr "            .map(|x| CString::new(x).unwrap())\n";
+            pr "            .collect::<Vec<_>>();\n";
+            pr "        let %s_2 = %s_1.iter()\n" n n;
+            pr "            .map(|x| x.as_ptr())\n";
+            pr "            .collect();\n";
+            pr "        %s_2.push (ptr::null());\n" n;
+         | Pointer (t, n) ->
+            pr "        panic! (\"Pointer is not implemented\");\n"
+       ) args;
+
+       (match ret with
+        | RBufferOut _ -> pr "    let mut size_r : size_t = 0;\n";
+        | _ -> ()
+       );
+
+       (* Call the C function. *)
+       pr "        let r = unsafe { ffi::guestfs_%s (self.rc.g" name;
+       List.iter (
+         function
+         | Bool n -> pr ", %s as c_int" n
+         | Int n -> pr ", %s as int32_t" n
+         | Int64 n -> pr ", %s as int64_t" n
+         | String n | Device n | Mountable n | Pathname n
+         | Dev_or_Path n | Mountable_or_Path n
+         | Key n | FileIn n | FileOut n
+         | GUID n ->
+            pr ", %s_cptr" n
+         | OptString n ->
+            pr ", %s_cptr" n
+         | BufferIn n ->
+            pr ", %s.as_ptr(), %s.len() as size_t" n n
+         | StringList n
+         | DeviceList n
+         | FilenameList n ->
+            pr ", %s_2.as_ptr() as *const *const c_char" n
+         | Pointer (t, n) ->
+            pr ", 0 /* not impl */"
+       ) args;
+       (match ret with
+        | RBufferOut _ -> pr ", &mut size_r";
+        | _ -> ()
+       );
+
+       pr ") };\n";
+
+       (* Check for errors. *)
+       (match errcode_of_ret ret with
+        | `CannotReturnError -> ()
+        | `ErrorIsMinusOne ->
+           pr "        if r == -1 {\n";
+           pr "            return Err (get_last_error (self.rc.g));\n";
+           pr "        }\n";
+        | `ErrorIsNULL ->
+           pr "        if r.is_null() {\n";
+           pr "            return Err (get_last_error (self.rc.g));\n";
+           pr "        }\n";
+       );
+
+       (* Convert the result from FFI to Rust. *)
+       (match ret with
+        | RErr ->
+           pr "        Ok (())\n"
+        | RInt _ ->
+           pr "        Ok (r)\n"
+        | RInt64 _ ->
+           pr "        Ok (r)\n"
+        | RBool _ ->
+           pr "        Ok (r)\n"
+        | RConstString _
+        | RConstOptString _
+        | RString _ ->
+           pr "        let r_buf = unsafe { CStr::from_ptr (r).to_bytes() };\n";
+           pr "        Ok (str::from_utf8 (r_buf).unwrap().to_owned())\n";
+        | RStringList _ ->
+           pr "        panic! (\"not impl! XXX\");\n";
+        | RStruct (_, typ) ->
+           pr "        panic! (\"not impl! XXX\");\n";
+        | RStructList (_, typ) ->
+           pr "        panic! (\"not impl! XXX\");\n";
+        | RHashtable _ ->
+           pr "        panic! (\"not impl! XXX\");\n";
+        | RBufferOut _ ->
+           pr "        panic! (\"not impl! XXX\");\n";
+       );
+
+       pr "    }\n";
+       pr "\n";
+  ) public_functions_sorted;
+
+  pr "} // impl GuestfsHandle\n"
+
+(*
+    // XXX generate these
+    pub fn version (&mut self) -> Result<Version, String> {
+        let v = unsafe { ffi::guestfs_version (self.rc.g) };
+        if v.is_null() {
+            return Err (get_last_error (self.rc.g));
+        }
+        let major = unsafe { ( *v).major };
+        let minor = unsafe { ( *v).minor };
+        let release = unsafe { ( *v).release };
+        let extra_cstr = unsafe { ( *v).extra };
+        let extra_buf = unsafe { CStr::from_ptr (extra_cstr).to_bytes() };
+        let extra = str::from_utf8 (extra_buf).unwrap().to_owned();
+        unsafe { ffi::guestfs_free_version (v) };
+        Ok (Version {
+            major: major,
+            minor: minor,
+            release: release,
+            extra: extra,
+        })
+    }
+*)
diff --git a/rust/Cargo.toml.in b/rust/Cargo.toml.in
new file mode 100644
index 0000000..11d113e
--- /dev/null
+++ b/rust/Cargo.toml.in
@@ -0,0 +1,9 @@
+[package]
+name = "guestfs"
+version = "@PACKAGE_VERSION@"
+authors = ["libguestfs authors <libguestfs redhat com>"]
+description = "Library for accessing and modifying virtual machine images"
+repository = "https://github.com/libguestfs/libguestfs";
+
+[dependencies]
+libc = "*"
diff --git a/rust/Makefile.am b/rust/Makefile.am
new file mode 100644
index 0000000..7bcbfe6
--- /dev/null
+++ b/rust/Makefile.am
@@ -0,0 +1,39 @@
+# libguestfs Rust bindings
+# Copyright (C) 2015 Red Hat 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 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.
+
+include $(top_srcdir)/subdir-rules.mk
+
+generator_built = \
+	src/ffi.rs \
+	src/lib.rs
+
+EXTRA_DIST = \
+	$(generator_built)
+
+CLEANFILES = \
+	*~ \
+	Cargo.lock
+
+clean-local:
+	-rm -rf target
+
+if HAVE_RUST
+
+all-local:
+	$(CARGO) build
+
+endif
diff --git a/rust/src/.gitignore b/rust/src/.gitignore
new file mode 100644
index 0000000..e69de29
diff --git a/src/guestfs.pod b/src/guestfs.pod
index f8d7e2c..31b0ddf 100644
--- a/src/guestfs.pod
+++ b/src/guestfs.pod
@@ -4606,6 +4606,8 @@ L<virt-v2v(1)> command and documentation.
 
 =item F<ruby>
 
+=item F<rust>
+
 Language bindings.
 
 =back
-- 
2.5.0

Attachment: main.rs
Description: application/rls-services


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