Add Ruby bindings.
authorRichard W.M. Jones <rjones@redhat.com>
Mon, 15 Aug 2011 07:37:09 +0000 (08:37 +0100)
committerRichard W.M. Jones <rjones@redhat.com>
Mon, 15 Aug 2011 09:34:23 +0000 (10:34 +0100)
15 files changed:
.gitignore
Makefile.am
configure.ac
generator/generator.ml
ruby/Makefile.am [new file with mode: 0644]
ruby/README.rdoc [new file with mode: 0644]
ruby/Rakefile.in [new file with mode: 0644]
ruby/doc/site/index.html [new file with mode: 0644]
ruby/ext/hivex/extconf.rb [new file with mode: 0644]
ruby/lib/hivex.rb [new file with mode: 0644]
ruby/run-ruby-tests [new file with mode: 0755]
ruby/tests/tc_010_load.rb [new file with mode: 0644]
ruby/tests/tc_021_close.rb [new file with mode: 0644]
ruby/tests/tc_200_write.rb [new file with mode: 0644]
ruby/tests/tc_210_setvalue.rb [new file with mode: 0644]

index cf49940..40a4780 100644 (file)
@@ -21,7 +21,7 @@ config.sub
 configure
 depcomp
 .deps
-generator/.pod2text.data
+generator/.pod2text.data.version.2
 generator/stamp-generator
 hivex.pc
 hivex-*.tar.gz
@@ -101,6 +101,13 @@ python/hivex-py.c
 python/hivex.py
 python/run-python-tests
 regedit/hivexregedit.1
+ruby/doc/site/api
+ruby/ext/hivex/extconf.h
+ruby/ext/hivex/_hivex.bundle
+ruby/ext/hivex/_hivex.c
+ruby/ext/hivex/_hivex.so
+ruby/ext/hivex/mkmf.log
+ruby/Rakefile
 sh/*.1
 sh/hivexsh
 stamp-h1
index 0d59bbf..8d69c55 100644 (file)
@@ -34,6 +34,10 @@ if HAVE_PYTHON
 SUBDIRS += python
 endif
 
+if HAVE_RUBY
+SUBDIRS += ruby
+endif
+
 EXTRA_DIST = hivex.pc hivex.pc.in README LICENSE
 
 # Generate the ChangeLog automatically from the gitlog.
index 6c38cb1..7ee6c0b 100644 (file)
@@ -269,12 +269,12 @@ AC_SUBST(PYTHON_SITE_PACKAGES)
 AM_CONDITIONAL([HAVE_PYTHON],
     [test "x$PYTHON_INCLUDEDIR" != "x" && test "x$PYTHON_SITE_PACKAGES" != "x"])
 
-dnl dnl Check for Ruby and rake (optional, for Ruby bindings).
-dnl AC_CHECK_LIB([ruby],[ruby_init],[HAVE_LIBRUBY=1],[HAVE_LIBRUBY=0])
-dnl AC_CHECK_PROG([RAKE],[rake],[rake],[no])
+dnl Check for Ruby and rake (optional, for Ruby bindings).
+AC_CHECK_LIB([ruby],[ruby_init],[HAVE_LIBRUBY=1],[HAVE_LIBRUBY=0])
+AC_CHECK_PROG([RAKE],[rake],[rake],[no])
 
-dnl AM_CONDITIONAL([HAVE_RUBY],
-dnl     [test "x$RAKE" != "xno" && test -n "$HAVE_LIBRUBY"])
+AM_CONDITIONAL([HAVE_RUBY],
+    [test "x$RAKE" != "xno" && test -n "$HAVE_LIBRUBY"])
 
 dnl dnl Check for Java.
 dnl AC_ARG_WITH(java_home,
@@ -437,6 +437,7 @@ AC_CONFIG_FILES([Makefile
                  python/Makefile
                  po/Makefile.in
                  regedit/Makefile
+                 ruby/Makefile ruby/Rakefile
                  sh/Makefile
                  xml/Makefile])
 AC_CONFIG_FILES([python/run-python-tests], [chmod +x python/run-python-tests])
@@ -456,8 +457,8 @@ echo -n "Perl bindings ....................... "
 if test "x$HAVE_PERL_TRUE" = "x"; then echo "yes"; else echo "no"; fi
 echo -n "Python bindings ..................... "
 if test "x$HAVE_PYTHON_TRUE" = "x"; then echo "yes"; else echo "no"; fi
-dnl echo -n "Ruby bindings ....................... "
-dnl if test "x$HAVE_RUBY_TRUE" = "x"; then echo "yes"; else echo "no"; fi
+echo -n "Ruby bindings ....................... "
+if test "x$HAVE_RUBY_TRUE" = "x"; then echo "yes"; else echo "no"; fi
 dnl echo -n "Java bindings ....................... "
 dnl if test "x$HAVE_JAVA_TRUE" = "x"; then echo "yes"; else echo "no"; fi
 dnl echo -n "Haskell bindings .................... "
index d8efc04..de103ed 100755 (executable)
@@ -334,21 +334,6 @@ new key is added.  Key matching is case insensitive.
 C<node> is the node to modify.";
 ]
 
-(* Used to memoize the result of pod2text. *)
-let pod2text_memo_filename = "generator/.pod2text.data"
-let pod2text_memo : ((int * string * string), string list) Hashtbl.t =
-  try
-    let chan = open_in pod2text_memo_filename in
-    let v = input_value chan in
-    close_in chan;
-    v
-  with
-    _ -> Hashtbl.create 13
-let pod2text_memo_updated () =
-  let chan = open_out pod2text_memo_filename in
-  output_value chan pod2text_memo;
-  close_out chan
-
 (* Useful functions.
  * Note we don't want to use any external OCaml libraries which
  * makes this a bit harder than it should be.
@@ -395,6 +380,64 @@ let trimr ?(test = isspace) str =
 let trim ?(test = isspace) str =
   trimr ~test (triml ~test str)
 
+(* Used to memoize the result of pod2text. *)
+let pod2text_memo_filename = "generator/.pod2text.data.version.2"
+let pod2text_memo : ((int option * bool * bool * string * string), string list) Hashtbl.t =
+  try
+    let chan = open_in pod2text_memo_filename in
+    let v = input_value chan in
+    close_in chan;
+    v
+  with
+    _ -> Hashtbl.create 13
+let pod2text_memo_updated () =
+  let chan = open_out pod2text_memo_filename in
+  output_value chan pod2text_memo;
+  close_out chan
+
+(* Useful if you need the longdesc POD text as plain text.  Returns a
+ * list of lines.
+ *
+ * Because this is very slow (the slowest part of autogeneration),
+ * we memoize the results.
+ *)
+let pod2text ?width ?(trim = true) ?(discard = true) name longdesc =
+  let key = width, trim, discard, name, longdesc in
+  try Hashtbl.find pod2text_memo key
+  with Not_found ->
+    let filename, chan = Filename.open_temp_file "gen" ".tmp" in
+    fprintf chan "=head1 %s\n\n%s\n" name longdesc;
+    close_out chan;
+    let cmd =
+      match width with
+      | Some width ->
+          sprintf "pod2text -w %d %s" width (Filename.quote filename)
+      | None ->
+          sprintf "pod2text %s" (Filename.quote filename) in
+    let chan = open_process_in cmd in
+    let lines = ref [] in
+    let rec loop i =
+      let line = input_line chan in
+      if i = 1 && discard then  (* discard the first line of output *)
+        loop (i+1)
+      else (
+        let line = if trim then triml line else line in
+        lines := line :: !lines;
+        loop (i+1)
+      ) in
+    let lines = try loop 1 with End_of_file -> List.rev !lines in
+    unlink filename;
+    (match close_process_in chan with
+     | WEXITED 0 -> ()
+     | WEXITED i ->
+         failwithf "pod2text: process exited with non-zero status (%d)" i
+     | WSIGNALED i | WSTOPPED i ->
+         failwithf "pod2text: process signalled or stopped by signal %d" i
+    );
+    Hashtbl.add pod2text_memo key lines;
+    pod2text_memo_updated ();
+    lines
+
 let rec find s sub =
   let len = String.length s in
   let sublen = String.length sub in
@@ -894,7 +937,7 @@ If you just want to export or modify the Registry of a Windows
 virtual machine, you should look at L<virt-win-reg(1)>.
 
 Hivex is also comes with language bindings for
-OCaml, Perl and Python.
+OCaml, Perl, Python and Ruby.
 
 =head1 TYPES
 
@@ -3116,6 +3159,354 @@ class Hivex:
       )
   ) functions
 
+and generate_ruby_c () =
+  generate_header CStyle LGPLv2plus;
+
+  pr "\
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdint.h>
+
+#include <ruby.h>
+
+#include \"hivex.h\"
+
+#include \"extconf.h\"
+
+/* For Ruby < 1.9 */
+#ifndef RARRAY_LEN
+#define RARRAY_LEN(r) (RARRAY((r))->len)
+#endif
+
+static VALUE m_hivex;                  /* hivex module */
+static VALUE c_hivex;                  /* hive_h handle */
+static VALUE e_Error;                  /* used for all errors */
+
+static void
+ruby_hivex_free (void *hvp)
+{
+  hive_h *h = hvp;
+
+  if (h)
+    hivex_close (h);
+}
+
+static void
+get_value (VALUE valv, hive_set_value *val)
+{
+  VALUE key = rb_hash_lookup (valv, ID2SYM (rb_intern (\"key\")));
+  VALUE type = rb_hash_lookup (valv, ID2SYM (rb_intern (\"type\")));
+  VALUE value = rb_hash_lookup (valv, ID2SYM (rb_intern (\"value\")));
+
+  val->key = StringValueCStr (key);
+  val->t = NUM2ULL (type);
+  val->len = RSTRING (value)->len;
+  val->value = RSTRING (value)->ptr;
+}
+
+static hive_set_value *
+get_values (VALUE valuesv, size_t *nr_values)
+{
+  size_t i;
+  hive_set_value *ret;
+
+  *nr_values = RARRAY_LEN (valuesv);
+  ret = malloc (sizeof (*ret) * *nr_values);
+  if (ret == NULL)
+    abort ();
+
+  for (i = 0; i < *nr_values; ++i) {
+    VALUE v = rb_ary_entry (valuesv, i);
+    get_value (v, &ret[i]);
+  }
+
+  return ret;
+}
+
+";
+
+  List.iter (
+    fun (name, (ret, args), shortdesc, longdesc) ->
+      let () =
+        (* Generate rdoc. *)
+        let doc = replace_str longdesc "C<hivex_" "C<h." in
+        let doc = pod2text ~width:60 name doc in
+        let doc = String.concat "\n * " doc in
+        let doc = trim doc in
+
+        let call, args =
+          match args with
+          | AHive :: args -> "h." ^ name, args
+          | args -> "Hivex::" ^ name, args in
+        let args = filter_map (
+          function
+          | AUnusedFlags -> None
+          | args -> Some (name_of_argt args)
+        ) args in
+        let args = String.concat ", " args in
+
+        let ret =
+          match ret with
+          | RErr | RErrDispose -> "nil"
+          | RHive -> "Hivex::Hivex"
+          | RNode | RNodeNotFound -> "integer"
+          | RNodeList -> "list"
+          | RValue -> "integer"
+          | RValueList -> "list"
+          | RString -> "string"
+          | RStringList -> "list"
+          | RLenType -> "hash"
+          | RLenTypeVal -> "hash"
+          | RInt32 -> "integer"
+          | RInt64 -> "integer" in
+
+        pr "\
+/*
+ * call-seq:
+ *   %s(%s) -> %s
+ *
+ * %s
+ *
+ * %s
+ *
+ * (For the C API documentation for this function, see
+ * +hivex_%s+[http://libguestfs.org/hivex.3.html#hivex_%s]).
+ */
+" call args ret shortdesc doc name name in
+
+      (* Generate the function. *)
+      pr "static VALUE\n";
+      pr "ruby_hivex_%s (" name;
+
+      let () =
+        (* If the first argument is not AHive, then this is a module-level
+         * function, and Ruby passes an implicit module argument which we
+         * must ignore.  Otherwise the first argument is the hive handle.
+         *)
+        let args =
+          match args with
+          | AHive :: args -> pr "VALUE hv"; args
+          | args -> pr "VALUE modulev"; args in
+        List.iter (
+          function
+          | AUnusedFlags -> ()
+          | arg ->
+            pr ", VALUE %sv" (name_of_argt arg)
+        ) args;
+        pr ")\n" in
+
+      pr "{\n";
+
+      List.iter (
+        function
+        | AHive ->
+          pr "  hive_h *h;\n";
+          pr "  Data_Get_Struct (hv, hive_h, h);\n";
+          pr "  if (!h)\n";
+          pr "    rb_raise (rb_eArgError, \"%%s: used handle after closing it\",\n";
+          pr "              \"%s\");\n" name;
+        | ANode n ->
+          pr "  hive_node_h %s = NUM2ULL (%sv);\n" n n
+        | AValue n ->
+          pr "  hive_value_h %s = NUM2ULL (%sv);\n" n n
+        | AString n ->
+          pr "  const char *%s = StringValueCStr (%sv);\n" n n;
+        | AStringNullable n ->
+          pr "  const char *%s =\n" n;
+          pr "    !NIL_P (%sv) ? StringValueCStr (%sv) : NULL;\n" n n
+        | AOpenFlags ->
+          pr "  int flags = 0;\n";
+          List.iter (
+            fun (n, flag, _) ->
+              pr "  if (RTEST (rb_hash_lookup (flagsv, ID2SYM (rb_intern (\"%s\")))))\n"
+                (String.lowercase flag);
+              pr "    flags += %d;\n" n
+          ) open_flags
+        | AUnusedFlags -> ()
+        | ASetValues ->
+          pr "  size_t nr_values;\n";
+          pr "  hive_set_value *values;\n";
+          pr "  values = get_values (valuesv, &nr_values);\n"
+        | ASetValue ->
+          pr "  hive_set_value val;\n";
+          pr "  get_value (valv, &val);\n"
+      ) args;
+      pr "\n";
+
+      let error_code =
+       match ret with
+        | RErr -> pr "  int r;\n"; "-1"
+       | RErrDispose -> pr "  int r;\n"; "-1"
+       | RHive -> pr "  hive_h *r;\n"; "NULL"
+        | RNode -> pr "  hive_node_h r;\n"; "0"
+        | RNodeNotFound ->
+            pr "  errno = 0;\n";
+            pr "  hive_node_h r;\n";
+            "0 && errno != 0"
+        | RNodeList -> pr "  hive_node_h *r;\n"; "NULL"
+        | RValue -> pr "  hive_value_h r;\n"; "0"
+        | RValueList -> pr "  hive_value_h *r;\n"; "NULL"
+        | RString -> pr "  char *r;\n"; "NULL"
+        | RStringList -> pr "  char **r;\n"; "NULL"
+        | RLenType ->
+            pr "  int r;\n";
+            pr "  size_t len;\n";
+            pr "  hive_type t;\n";
+            "-1"
+        | RLenTypeVal ->
+            pr "  char *r;\n";
+            pr "  size_t len;\n";
+            pr "  hive_type t;\n";
+            "NULL"
+        | RInt32 ->
+            pr "  errno = 0;\n";
+            pr "  int32_t r;\n";
+            "-1 && errno != 0"
+        | RInt64 ->
+            pr "  errno = 0;\n";
+            pr "  int64_t r;\n";
+            "-1 && errno != 0" in
+      pr "\n";
+
+      let c_params =
+        List.map (function
+                  | ASetValues -> ["nr_values"; "values"]
+                  | ASetValue -> ["&val"]
+                  | AUnusedFlags -> ["0"]
+                  | arg -> [name_of_argt arg]) args in
+      let c_params =
+        match ret with
+        | RLenType | RLenTypeVal -> c_params @ [["&t"; "&len"]]
+        | _ -> c_params in
+      let c_params = List.concat c_params in
+
+      pr "  r = hivex_%s (%s" name (List.hd c_params);
+      List.iter (pr ", %s") (List.tl c_params);
+      pr ");\n";
+      pr "\n";
+
+      (* Dispose of the hive handle (even if hivex_close returns error). *)
+      (match ret with
+       | RErrDispose ->
+           pr "  /* So we don't double-free in the finalizer. */\n";
+           pr "  DATA_PTR (hv) = NULL;\n";
+           pr "\n";
+       | _ -> ()
+      );
+
+      List.iter (
+        function
+        | AHive
+        | ANode _
+        | AValue _
+        | AString _
+        | AStringNullable _
+        | AOpenFlags
+        | AUnusedFlags -> ()
+        | ASetValues ->
+          pr "  free (values);\n"
+        | ASetValue -> ()
+      ) args;
+
+      (* Check for errors from C library. *)
+      pr "  if (r == %s)\n" error_code;
+      pr "    rb_raise (e_Error, \"%%s\", strerror (errno));\n";
+      pr "\n";
+
+      (match ret with
+      | RErr | RErrDispose ->
+        pr "  return Qnil;\n"
+      | RHive ->
+        pr "  return Data_Wrap_Struct (c_hivex, NULL, ruby_hivex_free, r);\n"
+      | RNode
+      | RValue
+      | RInt64 ->
+        pr "  return ULL2NUM (r);\n"
+      | RInt32 ->
+        pr "  return INT2NUM (r);\n"
+      | RNodeNotFound ->
+        pr "  if (r)\n";
+        pr "    return ULL2NUM (r);\n";
+        pr "  else\n";
+        pr "    return Qnil;\n"
+      | RNodeList
+      | RValueList ->
+        pr "  size_t i, len = 0;\n";
+        pr "  for (i = 0; r[i] != 0; ++i) len++;\n";
+        pr "  VALUE rv = rb_ary_new2 (len);\n";
+        pr "  for (i = 0; r[i] != 0; ++i)\n";
+        pr "    rb_ary_push (rv, ULL2NUM (r[i]));\n";
+        pr "  free (r);\n";
+        pr "  return rv;\n"
+      | RString ->
+        pr "  VALUE rv = rb_str_new2 (r);\n";
+        pr "  free (r);\n";
+        pr "  return rv;\n"
+      | RStringList ->
+        pr "  size_t i, len = 0;\n";
+        pr "  for (i = 0; r[i] != NULL; ++i) len++;\n";
+        pr "  VALUE rv = rb_ary_new2 (len);\n";
+        pr "  for (i = 0; r[i] != NULL; ++i) {\n";
+        pr "    rb_ary_push (rv, rb_str_new2 (r[i]));\n";
+        pr "    free (r[i]);\n";
+        pr "  }\n";
+        pr "  free (r);\n";
+        pr "  return rv;\n"
+      | RLenType ->
+        pr "  VALUE rv = rb_hash_new ();\n";
+        pr "  rb_hash_aset (rv, ID2SYM (rb_intern (\"len\")), INT2NUM (len));\n";
+        pr "  rb_hash_aset (rv, ID2SYM (rb_intern (\"type\")), INT2NUM (t));\n";
+        pr "  return rv;\n"
+      | RLenTypeVal ->
+        pr "  VALUE rv = rb_hash_new ();\n";
+        pr "  rb_hash_aset (rv, ID2SYM (rb_intern (\"len\")), INT2NUM (len));\n";
+        pr "  rb_hash_aset (rv, ID2SYM (rb_intern (\"type\")), INT2NUM (t));\n";
+        pr "  rb_hash_aset (rv, ID2SYM (rb_intern (\"value\")), rb_str_new (r, len));\n";
+        pr "  free (r);\n";
+        pr "  return rv;\n"
+      );
+
+      pr "}\n";
+      pr "\n"
+  ) functions;
+
+  pr "\
+/* Initialize the module. */
+void Init__hivex ()
+{
+  m_hivex = rb_define_module (\"Hivex\");
+  c_hivex = rb_define_class_under (m_hivex, \"Hivex\", rb_cObject);
+  e_Error = rb_define_class_under (m_hivex, \"Error\", rb_eStandardError);
+
+  /* XXX How to pass arguments? */
+#if 0
+#ifdef HAVE_RB_DEFINE_ALLOC_FUNC
+  rb_define_alloc_func (c_hivex, ruby_hivex_open);
+#endif
+#endif
+
+";
+
+  (* Methods. *)
+  List.iter (
+    fun (name, (_, args), _, _) ->
+      let args = List.filter (
+        function
+        | AUnusedFlags -> false
+        | _ -> true
+      ) args in
+      let nr_args = List.length args in
+      match args with
+      | AHive :: _ ->
+        pr "  rb_define_method (c_hivex, \"%s\",\n" name;
+        pr "                    ruby_hivex_%s, %d);\n" name (nr_args-1)
+      | args -> (* class function *)
+        pr "  rb_define_module_function (m_hivex, \"%s\",\n" name;
+        pr "                             ruby_hivex_%s, %d);\n" name nr_args
+  ) functions;
+
+  pr "}\n"
+
 let output_to filename k =
   let filename_new = filename ^ ".new" in
   chan := open_out filename_new;
@@ -3183,6 +3574,8 @@ Run it from the top source directory using the command
   output_to "python/hivex.py" generate_python_py;
   output_to "python/hivex-py.c" generate_python_c;
 
+  output_to "ruby/ext/hivex/_hivex.c" generate_ruby_c;
+
   (* Always generate this file last, and unconditionally.  It's used
    * by the Makefile to know when we must re-run the generator.
    *)
diff --git a/ruby/Makefile.am b/ruby/Makefile.am
new file mode 100644 (file)
index 0000000..b323d7e
--- /dev/null
@@ -0,0 +1,58 @@
+# hivex Ruby bindings
+# Copyright (C) 2009-2011 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., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+EXTRA_DIST = \
+       Rakefile.in \
+       README.rdoc \
+       doc/site/index.html \
+       ext/hivex/extconf.rb \
+       ext/hivex/_hivex.c \
+       lib/hivex.rb \
+       run-ruby-tests \
+       tests/tc_*.rb
+
+CLEANFILES = \
+       lib/*~ \
+       tests/*~ \
+       ext/hivex/*~ \
+       ext/hivex/extconf.h \
+       ext/hivex/_hivex.o \
+       ext/hivex/_hivex.so \
+       ext/hivex/mkmf.log \
+       ext/hivex/Makefile
+
+if HAVE_RUBY
+
+TESTS = run-ruby-tests
+
+TESTS_ENVIRONMENT = \
+       LD_LIBRARY_PATH=$(top_builddir)/src/.libs
+
+all:
+       rake build
+       rake rdoc
+
+RUBY_SITELIB := $(shell ruby -rrbconfig -e "puts Config::CONFIG['sitelibdir']")
+RUBY_SITEARCH := $(shell ruby -rrbconfig -e "puts Config::CONFIG['sitearchdir']")
+
+install:
+       $(MKDIR_P) $(DESTDIR)$(RUBY_SITELIB)
+       $(MKDIR_P) $(DESTDIR)$(RUBY_SITEARCH)
+       $(INSTALL) -p -m 0644 lib/hivex.rb $(DESTDIR)$(RUBY_SITELIB)
+       $(INSTALL) -p -m 0755 ext/hivex/_hivex.so $(DESTDIR)$(RUBY_SITEARCH)
+
+endif
diff --git a/ruby/README.rdoc b/ruby/README.rdoc
new file mode 100644 (file)
index 0000000..7965650
--- /dev/null
@@ -0,0 +1,4 @@
+= Ruby bindings for hivex
+
+The module Hivex provides Ruby bindings for
+{the hivex API}[http://libguestfs.org/hivex.3.html].
diff --git a/ruby/Rakefile.in b/ruby/Rakefile.in
new file mode 100644 (file)
index 0000000..da9f3f1
--- /dev/null
@@ -0,0 +1,114 @@
+# hivex Ruby bindings -*- ruby -*-
+# @configure_input@
+# Copyright (C) 2009-2011 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., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+require 'rake/clean'
+require 'rake/rdoctask'
+require 'rake/testtask'
+require 'rake/gempackagetask'
+
+PKG_NAME='@PACKAGE_NAME@'
+PKG_VERSION='@PACKAGE_VERSION@'
+
+EXT_CONF='@srcdir@/ext/hivex/extconf.rb'
+MAKEFILE='@builddir@/ext/hivex/Makefile'
+HIVEX_MODULE='@builddir@/ext/hivex/_hivex.so'
+HIVEX_SRC='@srcdir@/ext/hivex/_hivex.c'
+
+CLEAN.include [ "@builddir@/ext/**/*.o", HIVEX_MODULE,
+                "@builddir@/ext/**/depend" ]
+
+CLOBBER.include [ "@builddir@/config.save", "@builddir@/ext/**/mkmf.log",
+                  MAKEFILE ]
+
+# Build locally
+
+file MAKEFILE => EXT_CONF do |t|
+     unless sh "top_srcdir=$(pwd)/@top_srcdir@; top_builddir=$(pwd)/@top_builddir@; export ARCHFLAGS=\"-arch $(uname -m)\"; cd #{File::dirname(EXT_CONF)}; ruby #{File::basename(EXT_CONF)} --with-_hivex-include=$top_srcdir/lib --with-_hivex-lib=$top_builddir/lib/.libs"
+         $stderr.puts "Failed to run extconf"
+         break
+     end
+end
+file HIVEX_MODULE => [ MAKEFILE, HIVEX_SRC ] do |t|
+    Dir::chdir(File::dirname(EXT_CONF)) do
+         unless sh "make"
+             $stderr.puts "make failed"
+             break
+         end
+     end
+end
+desc "Build the native library"
+task :build => HIVEX_MODULE
+
+Rake::TestTask.new(:test) do |t|
+    t.test_files = FileList['tests/tc_*.rb']
+    t.libs = [ 'lib', 'ext/hivex' ]
+end
+task :test => :build
+
+RDOC_FILES = FileList[
+    "README.rdoc",
+    "lib/**/*.rb",
+    "ext/**/*.[ch]"
+]
+
+Rake::RDocTask.new do |rd|
+    rd.main = "README.rdoc"
+    rd.rdoc_dir = "doc/site/api"
+    rd.rdoc_files.include(RDOC_FILES)
+end
+
+Rake::RDocTask.new(:ri) do |rd|
+    rd.main = "README.rdoc"
+    rd.rdoc_dir = "doc/ri"
+    rd.options << "--ri-system"
+    rd.rdoc_files.include(RDOC_FILES)
+end
+
+# Package tasks
+
+PKG_FILES = FileList[
+  "Rakefile", "COPYING", "README", "NEWS", "README.rdoc",
+  "lib/**/*.rb",
+  "ext/**/*.[ch]", "ext/**/MANIFEST", "ext/**/extconf.rb",
+  "tests/**/*",
+  "spec/**/*"
+]
+
+DIST_FILES = FileList[
+  "pkg/*.src.rpm",  "pkg/*.gem",  "pkg/*.zip", "pkg/*.tgz"
+]
+
+SPEC = Gem::Specification.new do |s|
+    s.name = PKG_NAME
+    s.version = PKG_VERSION
+    s.email = "rjones@redhat.com"
+    s.homepage = "http://libguestfs.org/"
+    s.summary = "Ruby bindings for hivex"
+    s.files = PKG_FILES
+    s.autorequire = "hivex"
+    s.required_ruby_version = '>= 1.8.1'
+    s.extensions = "ext/hivex/extconf.rb"
+    s.description = <<EOF
+Ruby bindings for hivex.
+EOF
+end
+
+Rake::GemPackageTask.new(SPEC) do |pkg|
+    pkg.need_tar = true
+    pkg.need_zip = true
+end
diff --git a/ruby/doc/site/index.html b/ruby/doc/site/index.html
new file mode 100644 (file)
index 0000000..c97f0e0
--- /dev/null
@@ -0,0 +1,8 @@
+<html>
+<head><title>Ruby documentation for hivex</title></head>
+<body>
+<p>
+  <a href="api/index.html">Ruby API documentation for hivex</a>
+</p>
+</body>
+</html>
diff --git a/ruby/ext/hivex/extconf.rb b/ruby/ext/hivex/extconf.rb
new file mode 100644 (file)
index 0000000..c144c4b
--- /dev/null
@@ -0,0 +1,33 @@
+# hivex Ruby bindings -*- ruby -*-
+# @configure_input@
+# Copyright (C) 2009-2011 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., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+require 'mkmf'
+
+extension_name = '_hivex'
+
+dir_config(extension_name)
+
+unless have_header("hivex.h")
+  raise "<hivex.h> not found"
+end
+unless have_library("hivex", "hivex_open", "hivex.h")
+  raise "hivex library not found"
+end
+
+create_header
+create_makefile(extension_name)
diff --git a/ruby/lib/hivex.rb b/ruby/lib/hivex.rb
new file mode 100644 (file)
index 0000000..a142db1
--- /dev/null
@@ -0,0 +1,18 @@
+# hivex Ruby bindings
+# Copyright (C) 2009-2011 Red Hat Inc.
+#
+# 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+
+require '_hivex'
diff --git a/ruby/run-ruby-tests b/ruby/run-ruby-tests
new file mode 100755 (executable)
index 0000000..1ef116c
--- /dev/null
@@ -0,0 +1,27 @@
+#!/bin/sh -
+# hivex Ruby bindings
+# Copyright (C) 2009-2011 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., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+set -e
+
+# Run them one at a time, otherwise rake runs them in parallel (which
+# is bound to fail because they all use a single test image file).
+
+for f in tests/tc_*.rb; do
+    echo rake test "$@" TEST="$f"
+    rake test "$@" TEST="$f"
+done
diff --git a/ruby/tests/tc_010_load.rb b/ruby/tests/tc_010_load.rb
new file mode 100644 (file)
index 0000000..113ab69
--- /dev/null
@@ -0,0 +1,28 @@
+# hivex Ruby bindings -*- ruby -*-
+# Copyright (C) 2009-2011 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., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+require 'test/unit'
+$:.unshift(File::join(File::dirname(__FILE__), "..", "lib"))
+$:.unshift(File::join(File::dirname(__FILE__), "..", "ext", "hivex"))
+require 'hivex'
+
+class TestLoad < Test::Unit::TestCase
+  def test_load
+    h = Hivex::open("../images/minimal", {})
+    assert_not_nil (h)
+  end
+end
diff --git a/ruby/tests/tc_021_close.rb b/ruby/tests/tc_021_close.rb
new file mode 100644 (file)
index 0000000..a089cf3
--- /dev/null
@@ -0,0 +1,29 @@
+# hivex Ruby bindings -*- ruby -*-
+# Copyright (C) 2009-2011 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., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+require 'test/unit'
+$:.unshift(File::join(File::dirname(__FILE__), "..", "lib"))
+$:.unshift(File::join(File::dirname(__FILE__), "..", "ext", "hivex"))
+require 'hivex'
+
+class TestClose < Test::Unit::TestCase
+  def test_close
+    h = Hivex::open("../images/minimal", {})
+    assert_not_nil (h)
+    h.close()
+  end
+end
diff --git a/ruby/tests/tc_200_write.rb b/ruby/tests/tc_200_write.rb
new file mode 100644 (file)
index 0000000..b46dc7b
--- /dev/null
@@ -0,0 +1,46 @@
+# hivex Ruby bindings -*- ruby -*-
+# Copyright (C) 2009-2011 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., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+require 'test/unit'
+$:.unshift(File::join(File::dirname(__FILE__), "..", "lib"))
+$:.unshift(File::join(File::dirname(__FILE__), "..", "ext", "hivex"))
+require 'hivex'
+
+class TestWrite < Test::Unit::TestCase
+  def test_write
+    h = Hivex::open("../images/minimal", {:write => 1})
+    assert_not_nil (h)
+
+    root = h.root()
+    assert (root)
+
+    h.node_add_child(root, "A")
+    h.node_add_child(root, "B")
+    b = h.node_get_child(root, "B")
+    assert (b)
+
+    values = [
+              { :key => "Key1", :type => 3, :value => "ABC" },
+              { :key => "Key2", :type => 3, :value => "DEF" }
+             ]
+    h.node_set_values(b, values)
+
+    # Don't actually commit here because that would overwrite
+    # the original file.
+    # h.commit()
+  end
+end
diff --git a/ruby/tests/tc_210_setvalue.rb b/ruby/tests/tc_210_setvalue.rb
new file mode 100644 (file)
index 0000000..e55e5fe
--- /dev/null
@@ -0,0 +1,68 @@
+# hivex Ruby bindings -*- ruby -*-
+# Copyright (C) 2009-2011 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., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+require 'test/unit'
+$:.unshift(File::join(File::dirname(__FILE__), "..", "lib"))
+$:.unshift(File::join(File::dirname(__FILE__), "..", "ext", "hivex"))
+require 'hivex'
+
+class TestSetValue < Test::Unit::TestCase
+  def test_set_value
+    h = Hivex::open("../images/minimal", {:write => 1})
+    assert_not_nil (h)
+
+    root = h.root()
+    assert (root)
+
+    h.node_add_child(root, "B")
+    b = h.node_get_child(root, "B")
+
+    values = [
+              { :key => "Key1", :type => 3, :value => "ABC" },
+              { :key => "Key2", :type => 2, :value => "DEF" }
+             ]
+    h.node_set_values(b, values)
+
+    value1 = { :key => "Key3", :type => 3, :value => "GHI" }
+    h.node_set_value(b, value1)
+
+    value2 = { :key => "Key1", :type => 3, :value => "JKL" }
+    h.node_set_value(b, value2)
+
+    val = h.node_get_value(b, "Key1")
+    hash = h.value_value(val)
+    assert (hash[:type] == 3)
+    assert (hash[:value] == "JKL")
+    assert (hash[:len] == 3)
+
+    val = h.node_get_value(b, "Key2")
+    hash = h.value_value(val)
+    assert (hash[:type] == 2)
+    assert (hash[:value] == "DEF")
+    assert (hash[:len] == 3)
+
+    val = h.node_get_value(b, "Key3")
+    hash = h.value_value(val)
+    assert (hash[:type] == 3)
+    assert (hash[:value] == "GHI")
+    assert (hash[:len] == 3)
+
+    # Don't actually commit here because that would overwrite
+    # the original file.
+    # h.commit()
+  end
+end