From 49b89eec45780eceea5b72ad1a466f9549e788eb Mon Sep 17 00:00:00 2001 From: "Richard W.M. Jones" Date: Mon, 15 Aug 2011 08:37:09 +0100 Subject: [PATCH] Add Ruby bindings. --- .gitignore | 9 +- Makefile.am | 4 + configure.ac | 15 +- generator/generator.ml | 425 ++++++++++++++++++++++++++++++++++++++++-- ruby/Makefile.am | 58 ++++++ ruby/README.rdoc | 4 + ruby/Rakefile.in | 114 +++++++++++ ruby/doc/site/index.html | 8 + ruby/ext/hivex/extconf.rb | 33 ++++ ruby/lib/hivex.rb | 18 ++ ruby/run-ruby-tests | 27 +++ ruby/tests/tc_010_load.rb | 28 +++ ruby/tests/tc_021_close.rb | 29 +++ ruby/tests/tc_200_write.rb | 46 +++++ ruby/tests/tc_210_setvalue.rb | 68 +++++++ 15 files changed, 862 insertions(+), 24 deletions(-) create mode 100644 ruby/Makefile.am create mode 100644 ruby/README.rdoc create mode 100644 ruby/Rakefile.in create mode 100644 ruby/doc/site/index.html create mode 100644 ruby/ext/hivex/extconf.rb create mode 100644 ruby/lib/hivex.rb create mode 100755 ruby/run-ruby-tests create mode 100644 ruby/tests/tc_010_load.rb create mode 100644 ruby/tests/tc_021_close.rb create mode 100644 ruby/tests/tc_200_write.rb create mode 100644 ruby/tests/tc_210_setvalue.rb diff --git a/.gitignore b/.gitignore index cf49940..40a4780 100644 --- a/.gitignore +++ b/.gitignore @@ -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 diff --git a/Makefile.am b/Makefile.am index 0d59bbf..8d69c55 100644 --- a/Makefile.am +++ b/Makefile.am @@ -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. diff --git a/configure.ac b/configure.ac index 6c38cb1..7ee6c0b 100644 --- a/configure.ac +++ b/configure.ac @@ -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 .................... " diff --git a/generator/generator.ml b/generator/generator.ml index d8efc04..de103ed 100755 --- a/generator/generator.ml +++ b/generator/generator.ml @@ -334,21 +334,6 @@ new key is added. Key matching is case insensitive. C 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. 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 +#include +#include + +#include + +#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 "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 index 0000000..b323d7e --- /dev/null +++ b/ruby/Makefile.am @@ -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 index 0000000..7965650 --- /dev/null +++ b/ruby/README.rdoc @@ -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 index 0000000..da9f3f1 --- /dev/null +++ b/ruby/Rakefile.in @@ -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 = < +Ruby documentation for hivex + +

+ Ruby API documentation for hivex +

+ + diff --git a/ruby/ext/hivex/extconf.rb b/ruby/ext/hivex/extconf.rb new file mode 100644 index 0000000..c144c4b --- /dev/null +++ b/ruby/ext/hivex/extconf.rb @@ -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 " 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 index 0000000..a142db1 --- /dev/null +++ b/ruby/lib/hivex.rb @@ -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 index 0000000..1ef116c --- /dev/null +++ b/ruby/run-ruby-tests @@ -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 index 0000000..113ab69 --- /dev/null +++ b/ruby/tests/tc_010_load.rb @@ -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 index 0000000..a089cf3 --- /dev/null +++ b/ruby/tests/tc_021_close.rb @@ -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 index 0000000..b46dc7b --- /dev/null +++ b/ruby/tests/tc_200_write.rb @@ -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 index 0000000..e55e5fe --- /dev/null +++ b/ruby/tests/tc_210_setvalue.rb @@ -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 -- 1.8.3.1