Version 1.2.4.
[hivex.git] / generator / generator.ml
index 8696dec..37e5b5c 100755 (executable)
@@ -36,8 +36,6 @@
 
 #load "unix.cma";;
 #load "str.cma";;
 
 #load "unix.cma";;
 #load "str.cma";;
-#directory "+xml-light";;
-#load "xml-light.cma";;
 
 open Unix
 open Printf
 
 open Unix
 open Printf
@@ -71,6 +69,7 @@ and argt =                              (* Note, cannot be NULL/0 unless it
   | AOpenFlags                          (* HIVEX_OPEN_* flags list. *)
   | AUnusedFlags                        (* Flags arg that is always 0 *)
   | ASetValues                          (* See hivex_node_set_values. *)
   | AOpenFlags                          (* HIVEX_OPEN_* flags list. *)
   | AUnusedFlags                        (* Flags arg that is always 0 *)
   | ASetValues                          (* See hivex_node_set_values. *)
+  | ASetValue                           (* See hivex_node_set_value. *)
 
 (* Hive types, from:
  * https://secure.wikimedia.org/wikipedia/en/wiki/Windows_Registry#Keys_and_values
 
 (* Hive types, from:
  * https://secure.wikimedia.org/wikipedia/en/wiki/Windows_Registry#Keys_and_values
@@ -251,7 +250,8 @@ C<hive_t_string>, C<hive_t_expand_string> or C<hive_t_link>.";
     "return value as multiple strings",
     "\
 If this value is a multiple-string, return the strings reencoded
     "return value as multiple strings",
     "\
 If this value is a multiple-string, return the strings reencoded
-as UTF-8 (as a NULL-terminated array of C strings).  This only
+as UTF-8 (in C, as a NULL-terminated array of C strings, in other
+language bindings, as a list of strings).  This only
 works for values which have type C<hive_t_multiple_strings>.";
 
   "value_dword", (RInt32, [AHive; AValue "val"]),
 works for values which have type C<hive_t_multiple_strings>.";
 
   "value_dword", (RInt32, [AHive; AValue "val"]),
@@ -271,14 +271,14 @@ works for values which have type C<hive_t_qword>.";
     "\
 Commit (write) any changes which have been made.
 
     "\
 Commit (write) any changes which have been made.
 
-C<filename> is the new file to write.  If C<filename> is NULL then we
-overwrite the original file (ie. the file name that was passed to
-C<hivex_open>).  C<flags> is not used, always pass 0.
+C<filename> is the new file to write.  If C<filename> is null/undefined
+then we overwrite the original file (ie. the file name that was passed to
+C<hivex_open>).
 
 Note this does not close the hive handle.  You can perform further
 operations on the hive after committing, including making more
 
 Note this does not close the hive handle.  You can perform further
 operations on the hive after committing, including making more
-modifications.  If you no longer wish to use the hive, call
-C<hivex_close> after this.";
+modifications.  If you no longer wish to use the hive, then you
+should close the handle after committing.";
 
   "node_add_child", (RNode, [AHive; ANode "parent"; AString "name"]),
     "add child node",
 
   "node_add_child", (RNode, [AHive; ANode "parent"; AString "name"]),
     "add child node",
@@ -302,18 +302,19 @@ subnodes become invalid.  You cannot delete the root node.";
   "node_set_values", (RErr, [AHive; ANode "node"; ASetValues; AUnusedFlags]),
     "set (key, value) pairs at a node",
     "\
   "node_set_values", (RErr, [AHive; ANode "node"; ASetValues; AUnusedFlags]),
     "set (key, value) pairs at a node",
     "\
-This call can be used to set all the (key, value) pairs stored in C<node>.
+This call can be used to set all the (key, value) pairs
+stored in C<node>.
 
 
-C<node> is the node to modify.  C<values> is an array of (key, value)
-pairs.  There should be C<nr_values> elements in this array.  C<flags>
-is not used, always pass 0.
+C<node> is the node to modify.";
 
 
-Any existing values stored at the node are discarded, and their
-C<hive_value_h> handles become invalid.  Thus you can remove all
-values stored at C<node> by passing C<nr_values = 0>.
+  "node_set_value", (RErr, [AHive; ANode "node"; ASetValue; AUnusedFlags]),
+    "set a single (key, value) pair at a given node",
+    "\
+This call can be used to replace a single (key, value) pair
+stored in C<node>. If the key does not already exist, then a
+new key is added. Key matching is case insensitive.
 
 
-Note that this library does not offer a way to modify just a single
-key at a node.  We don't implement a way to do this efficiently.";
+C<node> is the node to modify.";
 ]
 
 (* Used to memoize the result of pod2text. *)
 ]
 
 (* Used to memoize the result of pod2text. *)
@@ -465,6 +466,7 @@ let name_of_argt = function
   | ANode n | AValue n | AString n | AStringNullable n -> n
   | AOpenFlags | AUnusedFlags -> "flags"
   | ASetValues -> "values"
   | ANode n | AValue n | AString n | AStringNullable n -> n
   | AOpenFlags | AUnusedFlags -> "flags"
   | ASetValues -> "values"
+  | ASetValue -> "val"
 
 (* Check function names etc. for consistency. *)
 let check_functions () =
 
 (* Check function names etc. for consistency. *)
 let check_functions () =
@@ -812,6 +814,7 @@ and generate_c_prototype ?(extern = false) name style =
       | AString n | AStringNullable n -> pr "const char *%s" n
       | AOpenFlags | AUnusedFlags -> pr "int flags"
       | ASetValues -> pr "size_t nr_values, const hive_set_value *values"
       | AString n | AStringNullable n -> pr "const char *%s" n
       | AOpenFlags | AUnusedFlags -> pr "int flags"
       | ASetValues -> pr "size_t nr_values, const hive_set_value *values"
+      | ASetValue -> pr "const hive_set_value *val"
   ) (snd style);
   (match fst style with
    | RLenType | RLenTypeVal -> pr ", hive_type *t, size_t *len"
   ) (snd style);
   (match fst style with
    | RLenType | RLenTypeVal -> pr ", hive_type *t, size_t *len"
@@ -931,6 +934,23 @@ here.  Often it's not documented at all.
       pr "\n";
       pr "%s\n" longdesc;
       pr "\n";
       pr "\n";
       pr "%s\n" longdesc;
       pr "\n";
+
+      if List.mem AUnusedFlags (snd style) then
+       pr "The flags parameter is unused.  Always pass 0.\n\n";
+
+      if List.mem ASetValues (snd style) then
+       pr "C<values> is an array of (key, value) pairs.  There
+should be C<nr_values> elements in this array.
+
+Any existing values stored at the node are discarded, and their
+C<hive_value_h> handles become invalid.  Thus you can remove all
+values stored at C<node> by passing C<nr_values = 0>.\n\n";
+
+      if List.mem ASetValue (snd style) then
+       pr "C<value> is a single (key, value) pair.
+
+Existing C<hive_value_h> handles become invalid.\n\n";
+
       (match fst style with
        | RErr ->
            pr "\
       (match fst style with
        | RErr ->
            pr "\
@@ -984,7 +1004,7 @@ On error this returns NULL and sets errno.\n\n"
        | RLenType ->
            pr "\
 Returns 0 on success.
        | RLenType ->
            pr "\
 Returns 0 on success.
-On error this returns NULL and sets errno.\n\n"
+On error this returns -1 and sets errno.\n\n"
        | RLenTypeVal ->
            pr "\
 The value is returned as an array of bytes (of length C<len>).
        | RLenTypeVal ->
            pr "\
 The value is returned as an array of bytes (of length C<len>).
@@ -1315,6 +1335,32 @@ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 Lesser General Public License for more details.
 "
 
 Lesser General Public License for more details.
 "
 
+(* Generate the linker script which controls the visibility of
+ * symbols in the public ABI and ensures no other symbols get
+ * exported accidentally.
+ *)
+and generate_linker_script () =
+  generate_header HashStyle GPLv2plus;
+
+  let globals = [
+    "hivex_visit";
+    "hivex_visit_node"
+  ] in
+
+  let functions =
+    List.map (fun (name, _, _, _) -> "hivex_" ^ name)
+      functions in
+  let globals = List.sort compare (globals @ functions) in
+
+  pr "{\n";
+  pr "    global:\n";
+  List.iter (pr "        %s;\n") globals;
+  pr "\n";
+
+  pr "    local:\n";
+  pr "        *;\n";
+  pr "};\n"
+
 and generate_ocaml_interface () =
   generate_header OCamlStyle LGPLv2plus;
 
 and generate_ocaml_interface () =
   generate_header OCamlStyle LGPLv2plus;
 
@@ -1446,6 +1492,7 @@ and generate_ocaml_prototype ?(is_external = false) name style =
     | AOpenFlags -> pr "open_flag list -> "
     | AUnusedFlags -> ()
     | ASetValues -> pr "set_value array -> "
     | AOpenFlags -> pr "open_flag list -> "
     | AUnusedFlags -> ()
     | ASetValues -> pr "set_value array -> "
+    | ASetValue -> pr "set_value -> "
   ) (snd style);
   (match fst style with
    | RErr -> pr "unit" (* all errors are turned into exceptions *)
   ) (snd style);
   (match fst style with
    | RErr -> pr "unit" (* all errors are turned into exceptions *)
@@ -1487,13 +1534,36 @@ and generate_ocaml_c () =
 #include <caml/memory.h>
 #include <caml/mlvalues.h>
 #include <caml/signals.h>
 #include <caml/memory.h>
 #include <caml/mlvalues.h>
 #include <caml/signals.h>
+
+#ifdef HAVE_CAML_UNIXSUPPORT_H
 #include <caml/unixsupport.h>
 #include <caml/unixsupport.h>
+#else
+extern value unix_error_of_code (int errcode);
+#endif
+
+#ifndef HAVE_CAML_RAISE_WITH_ARGS
+static void
+caml_raise_with_args (value tag, int nargs, value args[])
+{
+  CAMLparam1 (tag);
+  CAMLxparamN (args, nargs);
+  value bucket;
+  int i;
+
+  bucket = caml_alloc_small (1 + nargs, 0);
+  Field(bucket, 0) = tag;
+  for (i = 0; i < nargs; i++) Field(bucket, 1 + i) = args[i];
+  caml_raise(bucket);
+  CAMLnoreturn;
+}
+#endif
 
 #include <hivex.h>
 
 #define Hiveh_val(v) (*((hive_h **)Data_custom_val(v)))
 static value Val_hiveh (hive_h *);
 static int HiveOpenFlags_val (value);
 
 #include <hivex.h>
 
 #define Hiveh_val(v) (*((hive_h **)Data_custom_val(v)))
 static value Val_hiveh (hive_h *);
 static int HiveOpenFlags_val (value);
+static hive_set_value *HiveSetValue_val (value);
 static hive_set_value *HiveSetValues_val (value);
 static hive_type HiveType_val (value);
 static value Val_hive_type (hive_type);
 static hive_set_value *HiveSetValues_val (value);
 static hive_type HiveType_val (value);
 static value Val_hive_type (hive_type);
@@ -1567,6 +1637,8 @@ static void raise_closed (const char *) Noreturn;
         | ASetValues ->
             pr "  int nrvalues = Wosize_val (valuesv);\n";
             pr "  hive_set_value *values = HiveSetValues_val (valuesv);\n"
         | ASetValues ->
             pr "  int nrvalues = Wosize_val (valuesv);\n";
             pr "  hive_set_value *values = HiveSetValues_val (valuesv);\n"
+       | ASetValue ->
+           pr "  hive_set_value *val = HiveSetValue_val (valv);\n"
       ) (snd style);
       pr "\n";
 
       ) (snd style);
       pr "\n";
 
@@ -1634,6 +1706,9 @@ static void raise_closed (const char *) Noreturn;
         | ASetValues ->
             pr "  free (values);\n";
             pr "\n";
         | ASetValues ->
             pr "  free (values);\n";
             pr "\n";
+       | ASetValue ->
+           pr "  free (val);\n";
+           pr "\n";
       ) (snd style);
 
       (* Check for errors. *)
       ) (snd style);
 
       (* Check for errors. *)
@@ -1696,6 +1771,19 @@ HiveOpenFlags_val (value v)
 }
 
 static hive_set_value *
 }
 
 static hive_set_value *
+HiveSetValue_val (value v)
+{
+  hive_set_value *val = malloc (sizeof (hive_set_value));
+
+  val->key = String_val (Field (v, 0));
+  val->t = HiveType_val (Field (v, 1));
+  val->len = caml_string_length (Field (v, 2));
+  val->value = String_val (Field (v, 2));
+
+  return val;
+}
+
+static hive_set_value *
 HiveSetValues_val (value v)
 {
   size_t nr_values = Wosize_val (v);
 HiveSetValues_val (value v)
 {
   size_t nr_values = Wosize_val (v);
@@ -1852,16 +1940,1143 @@ Val_hiveh (hive_h *h)
 " max_hive_type
 
 and generate_perl_pm () =
 " max_hive_type
 
 and generate_perl_pm () =
-  generate_header HashStyle LGPLv2plus
+  generate_header HashStyle LGPLv2plus;
+
+  pr "\
+=pod
+
+=head1 NAME
+
+Win::Hivex - Perl bindings for reading and writing Windows Registry hive files
+
+=head1 SYNOPSIS
+
+ use Win::Hivex;
+
+ $h = Win::Hivex->open ('SOFTWARE');
+ $root_node = $h->root ();
+ print $h->node_name ($root_node);
+
+=head1 DESCRIPTION
+
+The C<Win::Hivex> module provides a Perl XS binding to the
+L<hivex(3)> API for reading and writing Windows Registry binary
+hive files.
+
+=head1 ERRORS
+
+All errors turn into calls to C<croak> (see L<Carp(3)>).
+
+=head1 METHODS
+
+=over 4
+
+=cut
+
+package Win::Hivex;
+
+use strict;
+use warnings;
+
+require XSLoader;
+XSLoader::load ('Win::Hivex');
+
+=item open
+
+ $h = Win::Hivex->open ($filename,";
+
+  List.iter (
+    fun (_, flag, _) ->
+      pr "\n                        [%s => 1,]" (String.lowercase flag)
+  ) open_flags;
+
+  pr ")
+
+Open a Windows Registry binary hive file.
+
+The C<verbose> and C<debug> flags enable different levels of
+debugging messages.
+
+The C<write> flag is required if you will be modifying the
+hive file (see L<hivex(3)/WRITING TO HIVE FILES>).
+
+This function returns a hive handle.  The hive handle is
+closed automatically when its reference count drops to 0.
+
+=cut
+
+sub open {
+  my $proto = shift;
+  my $class = ref ($proto) || $proto;
+  my $filename = shift;
+  my %%flags = @_;
+  my $flags = 0;
+
+";
+
+  List.iter (
+    fun (n, flag, description) ->
+      pr "  # %s\n" description;
+      pr "  $flags += %d if $flags{%s};\n" n (String.lowercase flag)
+  ) open_flags;
+
+  pr "\
+
+  my $self = Win::Hivex::_open ($filename, $flags);
+  bless $self, $class;
+  return $self;
+}
+
+";
+
+  List.iter (
+    fun (name, style, _, longdesc) ->
+      (* The close call isn't explicit in Perl: handles are closed
+       * when their reference count drops to 0.
+       *
+       * The open call is coded specially in Perl.
+       *
+       * Therefore we don't generate prototypes for these two calls:
+       *)
+      if fst style <> RErrDispose && List.hd (snd style) = AHive then (
+       let longdesc = replace_str longdesc "C<hivex_" "C<" in
+       pr "=item %s\n\n " name;
+       generate_perl_prototype name style;
+       pr "\n\n";
+       pr "%s\n\n" longdesc;
+
+       (match fst style with
+        | RErr
+        | RErrDispose
+        | RHive
+        | RString
+        | RStringList
+        | RLenType
+        | RLenTypeVal
+        | RInt32
+        | RInt64 -> ()
+        | RNode ->
+            pr "\
+This returns a node handle.\n\n"
+        | RNodeNotFound ->
+            pr "\
+This returns a node handle, or C<undef> if the node was not found.\n\n"
+        | RNodeList ->
+            pr "\
+This returns a list of node handles.\n\n"
+        | RValue ->
+            pr "\
+This returns a value handle.\n\n"
+        | RValueList ->
+            pr "\
+This returns a list of value handles.\n\n"
+       );
+
+       if List.mem ASetValues (snd style) then
+         pr "C<@values> is an array of (keys, value) pairs.
+Each element should be a hashref containing C<key>, C<t> (type)
+and C<data>.
+
+Any existing values stored at the node are discarded, and their
+C<value> handles become invalid.  Thus you can remove all
+values stored at C<node> by passing C<@values = []>.\n\n"
+      )
+  ) functions;
+
+  pr "\
+=cut
+
+1;
+
+=back
+
+=head1 COPYRIGHT
+
+Copyright (C) %s Red Hat Inc.
+
+=head1 LICENSE
+
+Please see the file COPYING.LIB for the full license.
+
+=head1 SEE ALSO
+
+L<hivex(3)>,
+L<hivexsh(1)>,
+L<http://libguestfs.org>,
+L<Sys::Guestfs(3)>.
+
+=cut
+" copyright_years
+
+and generate_perl_prototype name style =
+  (* Return type. *)
+  (match fst style with
+   | RErr
+   | RErrDispose -> ()
+   | RHive -> pr "$h = "
+   | RNode
+   | RNodeNotFound -> pr "$node = "
+   | RNodeList -> pr "@nodes = "
+   | RValue -> pr "$value = "
+   | RValueList -> pr "@values = "
+   | RString -> pr "$string = "
+   | RStringList -> pr "@strings = "
+   | RLenType -> pr "($type, $len) = "
+   | RLenTypeVal -> pr "($type, $data) = "
+   | RInt32 -> pr "$int32 = "
+   | RInt64 -> pr "$int64 = "
+  );
+
+  let args = List.tl (snd style) in
+
+  (* AUnusedFlags is dropped in the bindings. *)
+  let args = List.filter ((<>) AUnusedFlags) args in
+
+  pr "$h->%s (" name;
+
+  let comma = ref false in
+  List.iter (
+    fun arg ->
+      if !comma then pr ", "; comma := true;
+      match arg with
+      | AHive -> pr "$h"
+      | ANode n
+      | AValue n
+      | AString n -> pr "$%s" n
+      | AStringNullable n -> pr "[$%s|undef]" n
+      | AOpenFlags -> pr "[flags]"
+      | AUnusedFlags -> assert false
+      | ASetValues -> pr "\\@values"
+      | ASetValue -> pr "$val"
+  ) args;
+
+  pr ")"
 
 and generate_perl_xs () =
 
 and generate_perl_xs () =
-  generate_header CStyle LGPLv2plus
+  generate_header CStyle LGPLv2plus;
 
 
-and generate_python_py () =
-  generate_header HashStyle LGPLv2plus
+  pr "\
+#include \"EXTERN.h\"
+#include \"perl.h\"
+#include \"XSUB.h\"
+
+#include <string.h>
+#include <hivex.h>
+
+#ifndef PRId64
+#define PRId64 \"lld\"
+#endif
+
+static SV *
+my_newSVll(long long val) {
+#ifdef USE_64_BIT_ALL
+  return newSViv(val);
+#else
+  char buf[100];
+  int len;
+  len = snprintf(buf, 100, \"%%\" PRId64, val);
+  return newSVpv(buf, len);
+#endif
+}
+
+#ifndef PRIu64
+#define PRIu64 \"llu\"
+#endif
+
+#if 0
+static SV *
+my_newSVull(unsigned long long val) {
+#ifdef USE_64_BIT_ALL
+  return newSVuv(val);
+#else
+  char buf[100];
+  int len;
+  len = snprintf(buf, 100, \"%%\" PRIu64, val);
+  return newSVpv(buf, len);
+#endif
+}
+#endif
+
+#if 0
+/* http://www.perlmonks.org/?node_id=680842 */
+static char **
+XS_unpack_charPtrPtr (SV *arg) {
+  char **ret;
+  AV *av;
+  I32 i;
+
+  if (!arg || !SvOK (arg) || !SvROK (arg) || SvTYPE (SvRV (arg)) != SVt_PVAV)
+    croak (\"array reference expected\");
+
+  av = (AV *)SvRV (arg);
+  ret = malloc ((av_len (av) + 1 + 1) * sizeof (char *));
+  if (!ret)
+    croak (\"malloc failed\");
+
+  for (i = 0; i <= av_len (av); i++) {
+    SV **elem = av_fetch (av, i, 0);
+
+    if (!elem || !*elem)
+      croak (\"missing element in list\");
+
+    ret[i] = SvPV_nolen (*elem);
+  }
+
+  ret[i] = NULL;
+
+  return ret;
+}
+#endif
+
+/* Handle set_values parameter. */
+typedef struct pl_set_values {
+  size_t nr_values;
+  hive_set_value *values;
+} pl_set_values;
+
+static pl_set_values
+unpack_pl_set_values (SV *sv)
+{
+  pl_set_values ret;
+  AV *av;
+  I32 i;
+
+  if (!sv || !SvOK (sv) || !SvROK (sv) || SvTYPE (SvRV (sv)) != SVt_PVAV)
+    croak (\"array reference expected\");
+
+  av = (AV *)SvRV(sv);
+  ret.nr_values = av_len (av) + 1;
+  ret.values = malloc (ret.nr_values * sizeof (hive_set_value));
+  if (!ret.values)
+    croak (\"malloc failed\");
+
+  for (i = 0; i <= av_len (av); i++) {
+    SV **hvp = av_fetch (av, i, 0);
+
+    if (!hvp || !*hvp || !SvROK (*hvp) || SvTYPE (SvRV (*hvp)) != SVt_PVHV)
+      croak (\"missing element in list or not a hash ref\");
+
+    HV *hv = (HV *)SvRV(*hvp);
+
+    SV **svp;
+    svp = hv_fetch (hv, \"key\", 3, 0);
+    if (!svp || !*svp)
+      croak (\"missing 'key' in hash\");
+    ret.values[i].key = SvPV_nolen (*svp);
+
+    svp = hv_fetch (hv, \"t\", 1, 0);
+    if (!svp || !*svp)
+      croak (\"missing 't' in hash\");
+    ret.values[i].t = SvIV (*svp);
+
+    svp = hv_fetch (hv, \"value\", 5, 0);
+    if (!svp || !*svp)
+      croak (\"missing 'value' in hash\");
+    ret.values[i].value = SvPV (*svp, ret.values[i].len);
+  }
+
+  return ret;
+}
+
+static hive_set_value *
+unpack_set_value (SV *sv)
+{
+  hive_set_value *ret;
+
+  if (!sv || !SvROK (sv) || SvTYPE (SvRV (sv)) != SVt_PVHV)
+    croak (\"not a hash ref\");
+
+  ret = malloc (sizeof (hive_set_value));
+  if (ret == NULL)
+    croak (\"malloc failed\");
+
+  HV *hv = (HV *)SvRV(sv);
+
+  SV **svp;
+  svp = hv_fetch (hv, \"key\", 3, 0);
+  if (!svp || !*svp)
+    croak (\"missing 'key' in hash\");
+  ret->key = SvPV_nolen (*svp);
+
+  svp = hv_fetch (hv, \"t\", 1, 0);
+  if (!svp || !*svp)
+    croak (\"missing 't' in hash\");
+  ret->t = SvIV (*svp);
+
+  svp = hv_fetch (hv, \"value\", 5, 0);
+  if (!svp || !*svp)
+    croak (\"missing 'value' in hash\");
+  ret->value = SvPV (*svp, ret->len);
+
+  return ret;
+}
+
+MODULE = Win::Hivex  PACKAGE = Win::Hivex
+
+PROTOTYPES: ENABLE
+
+hive_h *
+_open (filename, flags)
+      char *filename;
+      int flags;
+   CODE:
+      RETVAL = hivex_open (filename, flags);
+      if (!RETVAL)
+        croak (\"hivex_open: %%s: %%s\", filename, strerror (errno));
+ OUTPUT:
+      RETVAL
+
+void
+DESTROY (h)
+      hive_h *h;
+ PPCODE:
+      if (hivex_close (h) == -1)
+        croak (\"hivex_close: %%s\", strerror (errno));
+
+";
+
+  List.iter (
+    fun (name, style, _, longdesc) ->
+      (* The close and open calls are handled specially above. *)
+      if fst style <> RErrDispose && List.hd (snd style) = AHive then (
+       (match fst style with
+        | RErr -> pr "void\n"
+        | RErrDispose -> failwith "perl bindings cannot handle a call which disposes of the handle"
+        | RHive -> failwith "perl bindings cannot handle a call which returns a handle"
+        | RNode
+        | RNodeNotFound
+        | RValue
+        | RString -> pr "SV *\n"
+        | RNodeList
+        | RValueList
+        | RStringList
+        | RLenType
+        | RLenTypeVal -> pr "void\n"
+        | RInt32 -> pr "SV *\n"
+        | RInt64 -> pr "SV *\n"
+       );
+
+       (* Call and arguments. *)
+       let perl_params =
+         filter_map (function
+                     | AUnusedFlags -> None
+                     | arg -> Some (name_of_argt arg)) (snd style) in
+
+       let c_params =
+         List.map (function
+                   | AUnusedFlags -> "0"
+                   | ASetValues -> "values.nr_values, values.values"
+                   | arg -> name_of_argt arg) (snd style) in
+
+       pr "%s (%s)\n" name (String.concat ", " perl_params);
+       iteri (
+          fun i ->
+            function
+            | AHive ->
+               pr "      hive_h *h;\n"
+           | ANode n
+           | AValue n ->
+               pr "      int %s;\n" n
+           | AString n ->
+               pr "      char *%s;\n" n
+            | AStringNullable n ->
+               (* http://www.perlmonks.org/?node_id=554277 *)
+               pr "      char *%s = SvOK(ST(%d)) ? SvPV_nolen(ST(%d)) : NULL;\n" n i i
+           | AOpenFlags ->
+               pr "      int flags;\n"
+           | AUnusedFlags -> ()
+           | ASetValues ->
+               pr "      pl_set_values values = unpack_pl_set_values (ST(%d));\n" i
+           | ASetValue ->
+               pr "      hive_set_value *val = unpack_set_value (ST(%d));\n" i
+       ) (snd style);
+
+       let free_args () =
+         List.iter (
+           function
+           | ASetValues ->
+               pr "      free (values.values);\n"
+           | ASetValue ->
+               pr "      free (val);\n"
+           | AHive | ANode _ | AValue _ | AString _ | AStringNullable _
+           | AOpenFlags | AUnusedFlags -> ()
+         ) (snd style)
+       in
+
+       (* Code. *)
+       (match fst style with
+        | RErr ->
+             pr "PREINIT:\n";
+             pr "      int r;\n";
+             pr " PPCODE:\n";
+             pr "      r = hivex_%s (%s);\n"
+              name (String.concat ", " c_params);
+            free_args ();
+             pr "      if (r == -1)\n";
+             pr "        croak (\"%%s: %%s\", \"%s\", strerror (errno));\n"
+              name;
+
+        | RErrDispose -> assert false
+        | RHive -> assert false
+
+        | RNode
+        | RValue ->
+             pr "PREINIT:\n";
+            pr "      /* hive_node_h = hive_value_h = size_t so we cheat\n";
+            pr "         here to simplify the generator */\n";
+             pr "      size_t r;\n";
+             pr "   CODE:\n";
+             pr "      r = hivex_%s (%s);\n"
+              name (String.concat ", " c_params);
+            free_args ();
+             pr "      if (r == 0)\n";
+             pr "        croak (\"%%s: %%s\", \"%s\", strerror (errno));\n"
+              name;
+             pr "      RETVAL = newSViv (r);\n";
+             pr " OUTPUT:\n";
+             pr "      RETVAL\n"
+
+        | RNodeNotFound ->
+             pr "PREINIT:\n";
+             pr "      hive_node_h r;\n";
+             pr "   CODE:\n";
+            pr "      errno = 0;\n";
+             pr "      r = hivex_%s (%s);\n"
+              name (String.concat ", " c_params);
+            free_args ();
+             pr "      if (r == 0 && errno != 0)\n";
+             pr "        croak (\"%%s: %%s\", \"%s\", strerror (errno));\n"
+              name;
+            pr "      if (r == 0)\n";
+             pr "        RETVAL = &PL_sv_undef;\n";
+            pr "      else\n";
+             pr "        RETVAL = newSViv (r);\n";
+             pr " OUTPUT:\n";
+             pr "      RETVAL\n"
+
+        | RString ->
+             pr "PREINIT:\n";
+             pr "      char *r;\n";
+             pr "   CODE:\n";
+             pr "      r = hivex_%s (%s);\n"
+              name (String.concat ", " c_params);
+            free_args ();
+             pr "      if (r == NULL)\n";
+             pr "        croak (\"%%s: %%s\", \"%s\", strerror (errno));\n"
+              name;
+             pr "      RETVAL = newSVpv (r, 0);\n";
+            pr "      free (r);\n";
+             pr " OUTPUT:\n";
+             pr "      RETVAL\n"
+
+        | RNodeList
+        | RValueList ->
+             pr "PREINIT:\n";
+             pr "      size_t *r;\n";
+             pr "      int i, n;\n";
+             pr " PPCODE:\n";
+             pr "      r = hivex_%s (%s);\n"
+              name (String.concat ", " c_params);
+            free_args ();
+             pr "      if (r == NULL)\n";
+             pr "        croak (\"%%s: %%s\", \"%s\", strerror (errno));\n"
+              name;
+             pr "      for (n = 0; r[n] != 0; ++n) /**/;\n";
+             pr "      EXTEND (SP, n);\n";
+             pr "      for (i = 0; i < n; ++i)\n";
+             pr "        PUSHs (sv_2mortal (newSViv (r[i])));\n";
+             pr "      free (r);\n";
+
+        | RStringList ->
+             pr "PREINIT:\n";
+             pr "      char **r;\n";
+             pr "      int i, n;\n";
+             pr " PPCODE:\n";
+             pr "      r = hivex_%s (%s);\n"
+              name (String.concat ", " c_params);
+            free_args ();
+             pr "      if (r == NULL)\n";
+             pr "        croak (\"%%s: %%s\", \"%s\", strerror (errno));\n"
+              name;
+             pr "      for (n = 0; r[n] != NULL; ++n) /**/;\n";
+             pr "      EXTEND (SP, n);\n";
+             pr "      for (i = 0; i < n; ++i) {\n";
+             pr "        PUSHs (sv_2mortal (newSVpv (r[i], 0)));\n";
+             pr "        free (r[i]);\n";
+             pr "      }\n";
+             pr "      free (r);\n";
+
+        | RLenType ->
+            pr "PREINIT:\n";
+            pr "      int r;\n";
+            pr "      size_t len;\n";
+            pr "      hive_type type;\n";
+            pr " PPCODE:\n";
+             pr "      r = hivex_%s (%s, &type, &len);\n"
+              name (String.concat ", " c_params);
+            free_args ();
+             pr "      if (r == -1)\n";
+             pr "        croak (\"%%s: %%s\", \"%s\", strerror (errno));\n"
+              name;
+            pr "      EXTEND (SP, 2);\n";
+            pr "      PUSHs (sv_2mortal (newSViv (type)));\n";
+            pr "      PUSHs (sv_2mortal (newSViv (len)));\n";
+
+        | RLenTypeVal ->
+            pr "PREINIT:\n";
+            pr "      char *r;\n";
+            pr "      size_t len;\n";
+            pr "      hive_type type;\n";
+            pr " PPCODE:\n";
+             pr "      r = hivex_%s (%s, &type, &len);\n"
+              name (String.concat ", " c_params);
+            free_args ();
+             pr "      if (r == NULL)\n";
+             pr "        croak (\"%%s: %%s\", \"%s\", strerror (errno));\n"
+              name;
+            pr "      EXTEND (SP, 2);\n";
+            pr "      PUSHs (sv_2mortal (newSViv (type)));\n";
+            pr "      PUSHs (sv_2mortal (newSVpvn (r, len)));\n";
+            pr "      free (r);\n";
+
+        | RInt32 ->
+             pr "PREINIT:\n";
+             pr "      int32_t r;\n";
+             pr "   CODE:\n";
+            pr "      errno = 0;\n";
+             pr "      r = hivex_%s (%s);\n"
+              name (String.concat ", " c_params);
+            free_args ();
+             pr "      if (r == -1 && errno != 0)\n";
+             pr "        croak (\"%%s: %%s\", \"%s\", strerror (errno));\n"
+              name;
+             pr "      RETVAL = newSViv (r);\n";
+             pr " OUTPUT:\n";
+             pr "      RETVAL\n"
+
+        | RInt64 ->
+             pr "PREINIT:\n";
+             pr "      int64_t r;\n";
+             pr "   CODE:\n";
+            pr "      errno = 0;\n";
+             pr "      r = hivex_%s (%s);\n"
+              name (String.concat ", " c_params);
+            free_args ();
+             pr "      if (r == -1 && errno != 0)\n";
+             pr "        croak (\"%%s: %%s\", \"%s\", strerror (errno));\n"
+              name;
+             pr "      RETVAL = my_newSVll (r);\n";
+             pr " OUTPUT:\n";
+             pr "      RETVAL\n"
+       );
+       pr "\n"
+      )
+  ) functions
 
 and generate_python_c () =
 
 and generate_python_c () =
-  generate_header CStyle LGPLv2plus
+  generate_header CStyle LGPLv2plus;
+
+  pr "\
+#define PY_SSIZE_T_CLEAN 1
+#include <Python.h>
+
+#if PY_VERSION_HEX < 0x02050000
+typedef int Py_ssize_t;
+#define PY_SSIZE_T_MAX INT_MAX
+#define PY_SSIZE_T_MIN INT_MIN
+#endif
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <assert.h>
+
+#include \"hivex.h\"
+
+#ifndef HAVE_PYCAPSULE_NEW
+typedef struct {
+  PyObject_HEAD
+  hive_h *h;
+} Pyhivex_Object;
+#endif
+
+static hive_h *
+get_handle (PyObject *obj)
+{
+  assert (obj);
+  assert (obj != Py_None);
+#ifndef HAVE_PYCAPSULE_NEW
+  return ((Pyhivex_Object *) obj)->h;
+#else
+  return (hive_h *) PyCapsule_GetPointer(obj, \"hive_h\");
+#endif
+}
+
+static PyObject *
+put_handle (hive_h *h)
+{
+  assert (h);
+#ifndef HAVE_PYCAPSULE_NEW
+  return
+    PyCObject_FromVoidPtrAndDesc ((void *) h, (char *) \"hive_h\", NULL);
+#else
+  return PyCapsule_New ((void *) h, \"hive_h\", NULL);
+#endif
+}
+
+/* This returns pointers into the Python objects, which should
+ * not be freed.
+ */
+static int
+get_value (PyObject *v, hive_set_value *ret)
+{
+  PyObject *obj;
+
+  obj = PyDict_GetItemString (v, \"key\");
+  if (!obj) {
+    PyErr_SetString (PyExc_RuntimeError, \"no 'key' element in dictionary\");
+    return -1;
+  }
+  if (!PyString_Check (obj)) {
+    PyErr_SetString (PyExc_RuntimeError, \"'key' element is not a string\");
+    return -1;
+  }
+  ret->key = PyString_AsString (obj);
+
+  obj = PyDict_GetItemString (v, \"t\");
+  if (!obj) {
+    PyErr_SetString (PyExc_RuntimeError, \"no 't' element in dictionary\");
+    return -1;
+  }
+  if (!PyInt_Check (obj)) {
+    PyErr_SetString (PyExc_RuntimeError, \"'t' element is not an integer\");
+    return -1;
+  }
+  ret->t = PyInt_AsLong (obj);
+
+  obj = PyDict_GetItemString (v, \"value\");
+  if (!obj) {
+    PyErr_SetString (PyExc_RuntimeError, \"no 'value' element in dictionary\");
+    return -1;
+  }
+  if (!PyString_Check (obj)) {
+    PyErr_SetString (PyExc_RuntimeError, \"'value' element is not a string\");
+    return -1;
+  }
+  ret->value = PyString_AsString (obj);
+  ret->len = PyString_Size (obj);
+
+  return 0;
+}
+
+typedef struct py_set_values {
+  size_t nr_values;
+  hive_set_value *values;
+} py_set_values;
+
+static int
+get_values (PyObject *v, py_set_values *ret)
+{
+  Py_ssize_t slen;
+  size_t len, i;
+
+  if (!PyList_Check (v)) {
+    PyErr_SetString (PyExc_RuntimeError, \"expecting a list parameter\");
+    return -1;
+  }
+
+  slen = PyList_Size (v);
+  if (slen < 0) {
+    PyErr_SetString (PyExc_RuntimeError, \"get_string_list: PyList_Size failure\");
+    return -1;
+  }
+  len = (size_t) slen;
+  ret->nr_values = len;
+  ret->values = malloc (len * sizeof (hive_set_value));
+  if (!ret->values) {
+    PyErr_SetString (PyExc_RuntimeError, strerror (errno));
+    return -1;
+  }
+
+  for (i = 0; i < len; ++i) {
+    if (get_value (PyList_GetItem (v, i), &(ret->values[i])) == -1) {
+      free (ret->values);
+      return -1;
+    }
+  }
+
+  return 0;
+}
+
+static PyObject *
+put_string_list (char * const * const argv)
+{
+  PyObject *list;
+  size_t argc, i;
+
+  for (argc = 0; argv[argc] != NULL; ++argc)
+    ;
+
+  list = PyList_New (argc);
+  for (i = 0; i < argc; ++i)
+    PyList_SetItem (list, i, PyString_FromString (argv[i]));
+
+  return list;
+}
+
+static void
+free_strings (char **argv)
+{
+  size_t argc;
+
+  for (argc = 0; argv[argc] != NULL; ++argc)
+    free (argv[argc]);
+  free (argv);
+}
+
+/* Since hive_node_t is the same as hive_value_t this also works for values. */
+static PyObject *
+put_node_list (hive_node_h *nodes)
+{
+  PyObject *list;
+  size_t argc, i;
+
+  for (argc = 0; nodes[argc] != 0; ++argc)
+    ;
+
+  list = PyList_New (argc);
+  for (i = 0; i < argc; ++i)
+    PyList_SetItem (list, i, PyLong_FromLongLong ((long) nodes[i]));
+
+  return list;
+}
+
+static PyObject *
+put_len_type (size_t len, hive_type t)
+{
+  PyObject *r = PyTuple_New (2);
+  PyTuple_SetItem (r, 0, PyInt_FromLong ((long) t));
+  PyTuple_SetItem (r, 1, PyLong_FromLongLong ((long) len));
+  return r;
+}
+
+static PyObject *
+put_val_type (char *val, size_t len, hive_type t)
+{
+  PyObject *r = PyTuple_New (2);
+  PyTuple_SetItem (r, 0, PyInt_FromLong ((long) t));
+  PyTuple_SetItem (r, 1, PyString_FromStringAndSize (val, len));
+  return r;
+}
+
+";
+
+  (* Generate functions. *)
+  List.iter (
+    fun (name, style, _, longdesc) ->
+      pr "static PyObject *\n";
+      pr "py_hivex_%s (PyObject *self, PyObject *args)\n" name;
+      pr "{\n";
+      pr "  PyObject *py_r;\n";
+
+      let error_code =
+       match fst style 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
+
+      (* Call and arguments. *)
+      let c_params =
+       List.map (function
+                 | AUnusedFlags -> "0"
+                 | ASetValues -> "values.nr_values, values.values"
+                  | ASetValue -> "&val"
+                 | arg -> name_of_argt arg) (snd style) in
+      let c_params =
+        match fst style with
+        | RLenType | RLenTypeVal -> c_params @ ["&t"; "&len"]
+        | _ -> c_params in
+
+      List.iter (
+        function
+        | AHive ->
+           pr "  hive_h *h;\n";
+            pr "  PyObject *py_h;\n"
+       | ANode n
+       | AValue n ->
+           pr "  long %s;\n" n
+       | AString n
+        | AStringNullable n ->
+           pr "  char *%s;\n" n
+       | AOpenFlags ->
+           pr "  int flags;\n"
+       | AUnusedFlags -> ()
+       | ASetValues ->
+           pr "  py_set_values values;\n";
+           pr "  PyObject *py_values;\n"
+       | ASetValue ->
+           pr "  hive_set_value val;\n";
+           pr "  PyObject *py_val;\n"
+      ) (snd style);
+
+      pr "\n";
+
+      (* Convert the required parameters. *)
+      pr "  if (!PyArg_ParseTuple (args, (char *) \"";
+      List.iter (
+        function
+        | AHive ->
+           pr "O"
+       | ANode n
+       | AValue n ->
+           pr "L"
+       | AString n ->
+           pr "s"
+        | AStringNullable n ->
+           pr "z"
+       | AOpenFlags ->
+           pr "i"
+       | AUnusedFlags -> ()
+       | ASetValues
+       | ASetValue ->
+            pr "O"
+      ) (snd style);
+
+      pr ":hivex_%s\"" name;
+
+      List.iter (
+        function
+        | AHive ->
+           pr ", &py_h"
+       | ANode n
+       | AValue n ->
+           pr ", &%s" n
+       | AString n
+        | AStringNullable n ->
+           pr ", &%s" n
+       | AOpenFlags ->
+           pr ", &flags"
+       | AUnusedFlags -> ()
+       | ASetValues ->
+            pr ", &py_values"
+       | ASetValue ->
+            pr ", &py_val"
+        ) (snd style);
+
+      pr "))\n";
+      pr "    return NULL;\n";
+
+      (* Convert some Python argument types to C. *)
+      List.iter (
+        function
+        | AHive ->
+            pr "  h = get_handle (py_h);\n"
+       | ANode _
+       | AValue _
+       | AString _
+        | AStringNullable _
+       | AOpenFlags
+       | AUnusedFlags -> ()
+       | ASetValues ->
+            pr "  if (get_values (py_values, &values) == -1)\n";
+            pr "    return NULL;\n"
+       | ASetValue ->
+            pr "  if (get_value (py_val, &val) == -1)\n";
+            pr "    return NULL;\n"
+      ) (snd style);
+
+      (* Call the C function. *)
+      pr "  r = hivex_%s (%s);\n" name (String.concat ", " c_params);
+
+      (* Free up arguments. *)
+      List.iter (
+        function
+       | AHive | ANode _ | AValue _
+        | AString _ | AStringNullable _
+       | AOpenFlags | AUnusedFlags -> ()
+       | ASetValues ->
+           pr "  free (values.values);\n"
+       | ASetValue -> ()
+      ) (snd style);
+
+      (* Check for errors from C library. *)
+      pr "  if (r == %s) {\n" error_code;
+      pr "    PyErr_SetString (PyExc_RuntimeError,\n";
+      pr "                     strerror (errno));\n";
+      pr "    return NULL;\n";
+      pr "  }\n";
+      pr "\n";
+
+      (* Convert return value to Python. *)
+      (match fst style with
+       | RErr
+       | RErrDispose ->
+           pr "  Py_INCREF (Py_None);\n";
+           pr "  py_r = Py_None;\n"
+       | RHive ->
+           pr "  py_r = put_handle (r);\n"
+       | RNode ->
+           pr "  py_r = PyLong_FromLongLong (r);\n"
+       | RNodeNotFound ->
+           pr "  if (r)\n";
+           pr "    py_r = PyLong_FromLongLong (r);\n";
+           pr "  else {\n";
+           pr "    Py_INCREF (Py_None);\n";
+           pr "    py_r = Py_None;\n";
+           pr "  }\n";
+       | RNodeList
+       | RValueList ->
+           pr "  py_r = put_node_list (r);\n";
+           pr "  free (r);\n"
+       | RValue ->
+           pr "  py_r = PyLong_FromLongLong (r);\n"
+       | RString ->
+           pr "  py_r = PyString_FromString (r);\n";
+           pr "  free (r);"
+       | RStringList ->
+           pr "  py_r = put_string_list (r);\n";
+           pr "  free_strings (r);\n"
+       | RLenType ->
+           pr "  py_r = put_len_type (len, t);\n"
+       | RLenTypeVal ->
+           pr "  py_r = put_val_type (r, len, t);\n";
+           pr "  free (r);\n"
+       | RInt32 ->
+           pr "  py_r = PyInt_FromLong ((long) r);\n"
+       | RInt64 ->
+           pr "  py_r = PyLong_FromLongLong (r);\n"
+      );
+      pr "  return py_r;\n";
+      pr "}\n";
+      pr "\n"
+  ) functions;
+
+  (* Table of functions. *)
+  pr "static PyMethodDef methods[] = {\n";
+  List.iter (
+    fun (name, _, _, _) ->
+      pr "  { (char *) \"%s\", py_hivex_%s, METH_VARARGS, NULL },\n"
+        name name
+  ) functions;
+  pr "  { NULL, NULL, 0, NULL }\n";
+  pr "};\n";
+  pr "\n";
+
+  (* Init function. *)
+  pr "\
+void
+initlibhivexmod (void)
+{
+  static int initialized = 0;
+
+  if (initialized) return;
+  Py_InitModule ((char *) \"libhivexmod\", methods);
+  initialized = 1;
+}
+"
+  
+and generate_python_py () =
+  generate_header HashStyle LGPLv2plus;
+
+  pr "\
+u\"\"\"Python bindings for hivex
+
+import hivex
+h = hivex.Hivex (filename)
+
+The hivex module provides Python bindings to the hivex API for
+examining and modifying Windows Registry 'hive' files.
+
+Read the hivex(3) man page to find out how to use the API.
+\"\"\"
+
+import libhivexmod
+
+class Hivex:
+    \"\"\"Instances of this class are hivex API handles.\"\"\"
+
+    def __init__ (self, filename";
+
+  List.iter (
+    fun (_, flag, _) -> pr ", %s = False" (String.lowercase flag)
+  ) open_flags;
+
+  pr "):
+        \"\"\"Create a new hivex handle.\"\"\"
+        flags = 0
+";
+
+  List.iter (
+    fun (n, flag, description) ->
+      pr "        # %s\n" description;
+      pr "        if %s: flags += %d\n" (String.lowercase flag) n
+  ) open_flags;
+
+  pr "        self._o = libhivexmod.open (filename, flags)
+
+    def __del__ (self):
+        libhivexmod.close (self._o)
+
+";
+
+  List.iter (
+    fun (name, style, shortdesc, _) ->
+      (* The close and open calls are handled specially above. *)
+      if fst style <> RErrDispose && List.hd (snd style) = AHive then (
+        let args = List.tl (snd style) in
+        let args = List.filter (
+          function AOpenFlags | AUnusedFlags -> false
+          | _ -> true
+        ) args in
+
+        pr "    def %s (self" name;
+        List.iter (fun arg -> pr ", %s" (name_of_argt arg)) args;
+        pr "):\n";
+        pr "        u\"\"\"%s\"\"\"\n" shortdesc;
+        pr "        return libhivexmod.%s (self._o" name;
+        List.iter (
+          fun arg ->
+            pr ", ";
+            match arg with
+           | AHive -> assert false
+            | ANode n | AValue n
+            | AString n | AStringNullable n -> pr "%s" n
+           | AOpenFlags
+            | AUnusedFlags -> assert false
+           | ASetValues -> pr "values"
+           | ASetValue -> pr "val"
+        ) args;
+        pr ")\n";
+        pr "\n"
+      )
+  ) functions
 
 let output_to filename k =
   let filename_new = filename ^ ".new" in
 
 let output_to filename k =
   let filename_new = filename ^ ".new" in
@@ -1918,6 +3133,8 @@ Run it from the top source directory using the command
   output_to "lib/hivex.h" generate_c_header;
   output_to "lib/hivex.pod" generate_c_pod;
 
   output_to "lib/hivex.h" generate_c_header;
   output_to "lib/hivex.pod" generate_c_pod;
 
+  output_to "lib/hivex.syms" generate_linker_script;
+
   output_to "ocaml/hivex.mli" generate_ocaml_interface;
   output_to "ocaml/hivex.ml" generate_ocaml_implementation;
   output_to "ocaml/hivex_c.c" generate_ocaml_c;
   output_to "ocaml/hivex.mli" generate_ocaml_interface;
   output_to "ocaml/hivex.ml" generate_ocaml_implementation;
   output_to "ocaml/hivex_c.c" generate_ocaml_c;