Use mount-options instead of mount to avoid implicit -o sync.
[libguestfs.git] / src / generator.ml
index 8d2a075..44dc8a5 100755 (executable)
@@ -1326,7 +1326,11 @@ as necessary.  This is like the C<mkdir -p> shell command.");
    "change file mode",
    "\
 Change the mode (permissions) of C<path> to C<mode>.  Only
-numeric modes are supported.");
+numeric modes are supported.
+
+I<Note>: When using this command from guestfish, C<mode>
+by default would be decimal, unless you prefix it with
+C<0> to get octal, ie. use C<0700> not C<700>.");
 
   ("chown", (RErr, [Int "owner"; Int "group"; Pathname "path"]), 35, [],
    [], (* XXX Need stat command to test *)
@@ -1428,7 +1432,7 @@ on the volume group C<volgroup>, with C<size> megabytes.");
    [InitEmpty, Always, TestOutput (
       [["part_disk"; "/dev/sda"; "mbr"];
        ["mkfs"; "ext2"; "/dev/sda1"];
-       ["mount"; "/dev/sda1"; "/"];
+       ["mount_options"; ""; "/dev/sda1"; "/"];
        ["write_file"; "/new"; "new file contents"; "0"];
        ["cat"; "/new"]], "new file contents")],
    "make a filesystem",
@@ -1504,12 +1508,12 @@ use C<guestfs_upload>.");
    [InitEmpty, Always, TestOutputListOfDevices (
       [["part_disk"; "/dev/sda"; "mbr"];
        ["mkfs"; "ext2"; "/dev/sda1"];
-       ["mount"; "/dev/sda1"; "/"];
+       ["mount_options"; ""; "/dev/sda1"; "/"];
        ["mounts"]], ["/dev/sda1"]);
     InitEmpty, Always, TestOutputList (
       [["part_disk"; "/dev/sda"; "mbr"];
        ["mkfs"; "ext2"; "/dev/sda1"];
-       ["mount"; "/dev/sda1"; "/"];
+       ["mount_options"; ""; "/dev/sda1"; "/"];
        ["umount"; "/"];
        ["mounts"]], [])],
    "unmount a filesystem",
@@ -1540,11 +1544,11 @@ See also: C<guestfs_mountpoints>");
        ["mkfs"; "ext2"; "/dev/sda1"];
        ["mkfs"; "ext2"; "/dev/sda2"];
        ["mkfs"; "ext2"; "/dev/sda3"];
-       ["mount"; "/dev/sda1"; "/"];
+       ["mount_options"; ""; "/dev/sda1"; "/"];
        ["mkdir"; "/mp1"];
-       ["mount"; "/dev/sda2"; "/mp1"];
+       ["mount_options"; ""; "/dev/sda2"; "/mp1"];
        ["mkdir"; "/mp1/mp2"];
-       ["mount"; "/dev/sda3"; "/mp1/mp2"];
+       ["mount_options"; ""; "/dev/sda3"; "/mp1/mp2"];
        ["mkdir"; "/mp1/mp2/mp3"];
        ["umount_all"];
        ["mounts"]], [])],
@@ -2401,11 +2405,11 @@ the human-readable, canonical hex dump of the file.");
    [InitNone, Always, TestOutput (
       [["part_disk"; "/dev/sda"; "mbr"];
        ["mkfs"; "ext3"; "/dev/sda1"];
-       ["mount"; "/dev/sda1"; "/"];
+       ["mount_options"; ""; "/dev/sda1"; "/"];
        ["write_file"; "/new"; "test file"; "0"];
        ["umount"; "/dev/sda1"];
        ["zerofree"; "/dev/sda1"];
-       ["mount"; "/dev/sda1"; "/"];
+       ["mount_options"; ""; "/dev/sda1"; "/"];
        ["cat"; "/new"]], "test file")],
    "zero unused inodes and disk blocks on ext2/3 filesystem",
    "\
@@ -2506,13 +2510,13 @@ are activated or deactivated.");
        ["vgcreate"; "VG"; "/dev/sda1"];
        ["lvcreate"; "LV"; "VG"; "10"];
        ["mkfs"; "ext2"; "/dev/VG/LV"];
-       ["mount"; "/dev/VG/LV"; "/"];
+       ["mount_options"; ""; "/dev/VG/LV"; "/"];
        ["write_file"; "/new"; "test content"; "0"];
        ["umount"; "/"];
        ["lvresize"; "/dev/VG/LV"; "20"];
        ["e2fsck_f"; "/dev/VG/LV"];
        ["resize2fs"; "/dev/VG/LV"];
-       ["mount"; "/dev/VG/LV"; "/"];
+       ["mount_options"; ""; "/dev/VG/LV"; "/"];
        ["cat"; "/new"]], "test content")],
    "resize an LVM logical volume",
    "\
@@ -3556,7 +3560,7 @@ and C<guestfs_setcon>");
    [InitEmpty, Always, TestOutput (
       [["part_disk"; "/dev/sda"; "mbr"];
        ["mkfs_b"; "ext2"; "4096"; "/dev/sda1"];
-       ["mount"; "/dev/sda1"; "/"];
+       ["mount_options"; ""; "/dev/sda1"; "/"];
        ["write_file"; "/new"; "new file contents"; "0"];
        ["cat"; "/new"]], "new file contents")],
    "make a filesystem with block size",
@@ -3571,7 +3575,7 @@ are C<1024>, C<2048> or C<4096> only.");
       [["sfdiskM"; "/dev/sda"; ",100 ,"];
        ["mke2journal"; "4096"; "/dev/sda1"];
        ["mke2fs_J"; "ext2"; "4096"; "/dev/sda2"; "/dev/sda1"];
-       ["mount"; "/dev/sda2"; "/"];
+       ["mount_options"; ""; "/dev/sda2"; "/"];
        ["write_file"; "/new"; "new file contents"; "0"];
        ["cat"; "/new"]], "new file contents")],
    "make ext2/3/4 external journal",
@@ -3586,7 +3590,7 @@ to the command:
       [["sfdiskM"; "/dev/sda"; ",100 ,"];
        ["mke2journal_L"; "4096"; "JOURNAL"; "/dev/sda1"];
        ["mke2fs_JL"; "ext2"; "4096"; "/dev/sda2"; "JOURNAL"];
-       ["mount"; "/dev/sda2"; "/"];
+       ["mount_options"; ""; "/dev/sda2"; "/"];
        ["write_file"; "/new"; "new file contents"; "0"];
        ["cat"; "/new"]], "new file contents")],
    "make ext2/3/4 external journal with label",
@@ -3599,7 +3603,7 @@ This creates an ext2 external journal on C<device> with label C<label>.");
        [["sfdiskM"; "/dev/sda"; ",100 ,"];
         ["mke2journal_U"; "4096"; uuid; "/dev/sda1"];
         ["mke2fs_JU"; "ext2"; "4096"; "/dev/sda2"; uuid];
-        ["mount"; "/dev/sda2"; "/"];
+        ["mount_options"; ""; "/dev/sda2"; "/"];
         ["write_file"; "/new"; "new file contents"; "0"];
         ["cat"; "/new"]], "new file contents")]),
    "make ext2/3/4 external journal with UUID",
@@ -4188,6 +4192,55 @@ If the destination is a device, it must be as large or larger
 than the source file or device, otherwise the copy will fail.
 This command cannot do partial copies.");
 
+  ("filesize", (RInt64 "size", [Pathname "file"]), 218, [],
+   [InitBasicFS, Always, TestOutputInt (
+      [["write_file"; "/file"; "hello, world"; "0"];
+       ["filesize"; "/file"]], 12)],
+   "return the size of the file in bytes",
+   "\
+This command returns the size of C<file> in bytes.
+
+To get other stats about a file, use C<guestfs_stat>, C<guestfs_lstat>,
+C<guestfs_is_dir>, C<guestfs_is_file> etc.
+To get the size of block devices, use C<guestfs_blockdev_getsize64>.");
+
+  ("lvrename", (RErr, [String "logvol"; String "newlogvol"]), 219, [],
+   [InitBasicFSonLVM, Always, TestOutputList (
+      [["lvrename"; "/dev/VG/LV"; "/dev/VG/LV2"];
+       ["lvs"]], ["/dev/VG/LV2"])],
+   "rename an LVM logical volume",
+   "\
+Rename a logical volume C<logvol> with the new name C<newlogvol>.");
+
+  ("vgrename", (RErr, [String "volgroup"; String "newvolgroup"]), 220, [],
+   [InitBasicFSonLVM, Always, TestOutputList (
+      [["umount"; "/"];
+       ["vg_activate"; "false"; "VG"];
+       ["vgrename"; "VG"; "VG2"];
+       ["vg_activate"; "true"; "VG2"];
+       ["mount_options"; ""; "/dev/VG2/LV"; "/"];
+       ["vgs"]], ["VG2"])],
+   "rename an LVM volume group",
+   "\
+Rename a volume group C<volgroup> with the new name C<newvolgroup>.");
+
+  ("initrd_cat", (RBufferOut "content", [Pathname "initrdpath"; String "filename"]), 221, [],
+   [InitISOFS, Always, TestOutputBuffer (
+      [["initrd_cat"; "/initrd"; "known-4"]], "abc\ndef\nghi")],
+   "list the contents of a single file in an initrd",
+   "\
+This command unpacks the file C<filename> from the initrd file
+called C<initrdpath>.  The filename must be given I<without> the
+initial C</> character.
+
+For example, in guestfish you could use the following command
+to examine the boot script (usually called C</init>)
+contained in a Linux initrd or initramfs image:
+
+ initrd-cat /boot/initrd-<version>.img init
+
+See also C<guestfs_initrd_list>.");
+
 ]
 
 let all_functions = non_daemon_functions @ daemon_functions
@@ -4577,6 +4630,13 @@ let mapi f xs =
   in
   loop 0 xs
 
+let count_chars c str =
+  let count = ref 0 in
+  for i = 0 to String.length str - 1 do
+    if c = String.unsafe_get str i then incr count
+  done;
+  !count
+
 let name_of_argt = function
   | Pathname n | Device n | Dev_or_Path n | String n | OptString n
   | StringList n | DeviceList n | Bool n | Int n | Int64 n
@@ -4808,23 +4868,32 @@ let check_functions () =
 
 (* 'pr' prints to the current output file. *)
 let chan = ref Pervasives.stdout
-let pr fs = ksprintf (output_string !chan) fs
+let lines = ref 0
+let pr fs =
+  ksprintf
+    (fun str ->
+       let i = count_chars '\n' str in
+       lines := !lines + i;
+       output_string !chan str
+    ) fs
 
 let copyright_years =
   let this_year = 1900 + (localtime (time ())).tm_year in
   if this_year > 2009 then sprintf "2009-%04d" this_year else "2009"
 
 (* Generate a header block in a number of standard styles. *)
-type comment_style = CStyle | HashStyle | OCamlStyle | HaskellStyle
+type comment_style =
+    CStyle | CPlusPlusStyle | HashStyle | OCamlStyle | HaskellStyle
 type license = GPLv2plus | LGPLv2plus
 
 let generate_header ?(extra_inputs = []) comment license =
   let inputs = "src/generator.ml" :: extra_inputs in
   let c = match comment with
-    | CStyle ->     pr "/* "; " *"
-    | HashStyle ->  pr "# ";  "#"
-    | OCamlStyle -> pr "(* "; " *"
-    | HaskellStyle -> pr "{- "; "  " in
+    | CStyle ->         pr "/* "; " *"
+    | CPlusPlusStyle -> pr "// "; "//"
+    | HashStyle ->      pr "# ";  "#"
+    | OCamlStyle ->     pr "(* "; " *"
+    | HaskellStyle ->   pr "{- "; "  " in
   pr "libguestfs generated file\n";
   pr "%s WARNING: THIS FILE IS GENERATED FROM:\n" c;
   List.iter (pr "%s   %s\n" c) inputs;
@@ -4865,6 +4934,7 @@ let generate_header ?(extra_inputs = []) comment license =
   );
   (match comment with
    | CStyle -> pr " */\n"
+   | CPlusPlusStyle
    | HashStyle -> ()
    | OCamlStyle -> pr " *)\n"
    | HaskellStyle -> pr "-}\n"
@@ -5590,6 +5660,51 @@ and generate_daemon_actions_h () =
         name style;
   ) daemon_functions
 
+(* 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 = [
+    "guestfs_create";
+    "guestfs_close";
+    "guestfs_get_error_handler";
+    "guestfs_get_out_of_memory_handler";
+    "guestfs_last_error";
+    "guestfs_set_error_handler";
+    "guestfs_set_launch_done_callback";
+    "guestfs_set_log_message_callback";
+    "guestfs_set_out_of_memory_handler";
+    "guestfs_set_subprocess_quit_callback";
+
+    (* Unofficial parts of the API: the bindings code use these
+     * functions, so it is useful to export them.
+     *)
+    "guestfs_safe_calloc";
+    "guestfs_safe_malloc";
+  ] in
+  let functions =
+    List.map (fun (name, _, _, _, _, _, _) -> "guestfs_" ^ name)
+      all_functions in
+  let structs =
+    List.concat (
+      List.map (fun (typ, _) ->
+                  ["guestfs_free_" ^ typ; "guestfs_free_" ^ typ ^ "_list"])
+        structs
+    ) in
+  let globals = List.sort compare (globals @ functions @ structs) in
+
+  pr "{\n";
+  pr "    global:\n";
+  List.iter (pr "        %s;\n") globals;
+  pr "\n";
+
+  pr "    local:\n";
+  pr "        *;\n";
+  pr "};\n"
+
 (* Generate the server-side stubs. *)
 and generate_daemon_actions () =
   generate_header CStyle GPLv2plus;
@@ -6385,7 +6500,7 @@ and generate_one_test_body name i test_name init test =
           ["lvm_remove_all"];
           ["part_disk"; "/dev/sda"; "mbr"];
           ["mkfs"; "ext2"; "/dev/sda1"];
-          ["mount"; "/dev/sda1"; "/"]]
+          ["mount_options"; ""; "/dev/sda1"; "/"]]
    | InitBasicFSonLVM ->
        pr "  /* InitBasicFSonLVM for %s: create ext2 on /dev/VG/LV */\n"
          test_name;
@@ -6398,7 +6513,7 @@ and generate_one_test_body name i test_name init test =
           ["vgcreate"; "VG"; "/dev/sda1"];
           ["lvcreate"; "LV"; "VG"; "8"];
           ["mkfs"; "ext2"; "/dev/VG/LV"];
-          ["mount"; "/dev/VG/LV"; "/"]]
+          ["mount_options"; ""; "/dev/VG/LV"; "/"]]
    | InitISOFS ->
        pr "  /* InitISOFS for %s */\n" test_name;
        List.iter (generate_test_command_call test_name)
@@ -6787,6 +6902,8 @@ and generate_fish_cmds () =
       fun (_, _, _, flags, _, _, _) -> not (List.mem NotInFish flags)
     ) all_functions_sorted in
 
+  pr "#include <config.h>\n";
+  pr "\n";
   pr "#include <stdio.h>\n";
   pr "#include <stdlib.h>\n";
   pr "#include <string.h>\n";
@@ -6794,6 +6911,8 @@ and generate_fish_cmds () =
   pr "\n";
   pr "#include <guestfs.h>\n";
   pr "#include \"c-ctype.h\"\n";
+  pr "#include \"full-write.h\"\n";
+  pr "#include \"xstrtol.h\"\n";
   pr "#include \"fish.h\"\n";
   pr "\n";
 
@@ -7005,6 +7124,34 @@ and generate_fish_cmds () =
       pr "    fprintf (stderr, _(\"type 'help %%s' for help on %%s\\n\"), cmd, cmd);\n";
       pr "    return -1;\n";
       pr "  }\n";
+
+      let parse_integer fn fntyp rtyp range name i =
+        pr "  {\n";
+        pr "    strtol_error xerr;\n";
+        pr "    %s r;\n" fntyp;
+        pr "\n";
+        pr "    xerr = %s (argv[%d], NULL, 0, &r, \"\");\n" fn i;
+        pr "    if (xerr != LONGINT_OK) {\n";
+        pr "      fprintf (stderr,\n";
+        pr "               _(\"%%s: %%s: invalid integer parameter (%%s returned %%d)\\n\"),\n";
+        pr "               cmd, \"%s\", \"%s\", xerr);\n" name fn;
+        pr "      return -1;\n";
+        pr "    }\n";
+        (match range with
+         | None -> ()
+         | Some (min, max, comment) ->
+             pr "    /* %s */\n" comment;
+             pr "    if (r < %s || r > %s) {\n" min max;
+             pr "      fprintf (stderr, _(\"%%s: %%s: integer out of range\\n\"), cmd, \"%s\");\n"
+               name;
+             pr "      return -1;\n";
+             pr "    }\n";
+             pr "    /* The check above should ensure this assignment does not overflow. */\n";
+        );
+        pr "    %s = r;\n" name;
+        pr "  }\n";
+      in
+
       iteri (
         fun i ->
           function
@@ -7030,9 +7177,15 @@ and generate_fish_cmds () =
           | Bool name ->
               pr "  %s = is_true (argv[%d]) ? 1 : 0;\n" name i
           | Int name ->
-              pr "  %s = atoi (argv[%d]);\n" name i
+              let range =
+                let min = "(-(2LL<<30))"
+                and max = "((2LL<<30)-1)"
+                and comment =
+                  "The Int type in the generator is a signed 31 bit int." in
+                Some (min, max, comment) in
+              parse_integer "xstrtol" "long" "int" range name i
           | Int64 name ->
-              pr "  %s = atoll (argv[%d]);\n" name i
+              parse_integer "xstrtoll" "long long" "int64_t" None name i
       ) (snd style);
 
       (* Call C API function. *)
@@ -7103,7 +7256,11 @@ and generate_fish_cmds () =
            pr "  return 0;\n"
        | RBufferOut _ ->
            pr "  if (r == NULL) return -1;\n";
-           pr "  fwrite (r, size, 1, stdout);\n";
+           pr "  if (full_write (1, r, size) != size) {\n";
+           pr "    perror (\"write\");\n";
+           pr "    free (r);\n";
+           pr "    return -1;\n";
+           pr "  }\n";
            pr "  free (r);\n";
            pr "  return 0;\n"
       );
@@ -9918,6 +10075,242 @@ and generate_haskell_prototype ~handle ?(hs = false) style =
   );
   pr ")"
 
+and generate_csharp () =
+  generate_header CPlusPlusStyle LGPLv2plus;
+
+  (* XXX Make this configurable by the C# assembly users. *)
+  let library = "libguestfs.so.0" in
+
+  pr "\
+// These C# bindings are highly experimental at present.
+//
+// Firstly they only work on Linux (ie. Mono).  In order to get them
+// to work on Windows (ie. .Net) you would need to port the library
+// itself to Windows first.
+//
+// The second issue is that some calls are known to be incorrect and
+// can cause Mono to segfault.  Particularly: calls which pass or
+// return string[], or return any structure value.  This is because
+// we haven't worked out the correct way to do this from C#.
+//
+// The third issue is that when compiling you get a lot of warnings.
+// We are not sure whether the warnings are important or not.
+//
+// Fourthly we do not routinely build or test these bindings as part
+// of the make && make check cycle, which means that regressions might
+// go unnoticed.
+//
+// Suggestions and patches are welcome.
+
+// To compile:
+//
+// gmcs Libguestfs.cs
+// mono Libguestfs.exe
+//
+// (You'll probably want to add a Test class / static main function
+// otherwise this won't do anything useful).
+
+using System;
+using System.IO;
+using System.Runtime.InteropServices;
+using System.Runtime.Serialization;
+using System.Collections;
+
+namespace Guestfs
+{
+  class Error : System.ApplicationException
+  {
+    public Error (string message) : base (message) {}
+    protected Error (SerializationInfo info, StreamingContext context) {}
+  }
+
+  class Guestfs
+  {
+    IntPtr _handle;
+
+    [DllImport (\"%s\")]
+    static extern IntPtr guestfs_create ();
+
+    public Guestfs ()
+    {
+      _handle = guestfs_create ();
+      if (_handle == IntPtr.Zero)
+        throw new Error (\"could not create guestfs handle\");
+    }
+
+    [DllImport (\"%s\")]
+    static extern void guestfs_close (IntPtr h);
+
+    ~Guestfs ()
+    {
+      guestfs_close (_handle);
+    }
+
+    [DllImport (\"%s\")]
+    static extern string guestfs_last_error (IntPtr h);
+
+" library library library;
+
+  (* Generate C# structure bindings.  We prefix struct names with
+   * underscore because C# cannot have conflicting struct names and
+   * method names (eg. "class stat" and "stat").
+   *)
+  List.iter (
+    fun (typ, cols) ->
+      pr "    [StructLayout (LayoutKind.Sequential)]\n";
+      pr "    public class _%s {\n" typ;
+      List.iter (
+        function
+        | name, FChar -> pr "      char %s;\n" name
+        | name, FString -> pr "      string %s;\n" name
+        | name, FBuffer ->
+            pr "      uint %s_len;\n" name;
+            pr "      string %s;\n" name
+        | name, FUUID ->
+            pr "      [MarshalAs (UnmanagedType.ByValTStr, SizeConst=16)]\n";
+            pr "      string %s;\n" name
+        | name, FUInt32 -> pr "      uint %s;\n" name
+        | name, FInt32 -> pr "      int %s;\n" name
+        | name, (FUInt64|FBytes) -> pr "      ulong %s;\n" name
+        | name, FInt64 -> pr "      long %s;\n" name
+        | name, FOptPercent -> pr "      float %s; /* [0..100] or -1 */\n" name
+      ) cols;
+      pr "    }\n";
+      pr "\n"
+  ) structs;
+
+  (* Generate C# function bindings. *)
+  List.iter (
+    fun (name, style, _, _, _, shortdesc, _) ->
+      let rec csharp_return_type () =
+       match fst style with
+       | RErr -> "void"
+       | RBool n -> "bool"
+       | RInt n -> "int"
+       | RInt64 n -> "long"
+       | RConstString n
+       | RConstOptString n
+       | RString n
+       | RBufferOut n -> "string"
+       | RStruct (_,n) -> "_" ^ n
+       | RHashtable n -> "Hashtable"
+       | RStringList n -> "string[]"
+       | RStructList (_,n) -> sprintf "_%s[]" n
+
+      and c_return_type () =
+       match fst style with
+       | RErr
+       | RBool _
+       | RInt _ -> "int"
+       | RInt64 _ -> "long"
+       | RConstString _
+       | RConstOptString _
+       | RString _
+       | RBufferOut _ -> "string"
+       | RStruct (_,n) -> "_" ^ n
+       | RHashtable _
+       | RStringList _ -> "string[]"
+       | RStructList (_,n) -> sprintf "_%s[]" n
+    
+      and c_error_comparison () =
+       match fst style with
+       | RErr
+       | RBool _
+       | RInt _
+       | RInt64 _ -> "== -1"
+       | RConstString _
+       | RConstOptString _
+       | RString _
+       | RBufferOut _
+       | RStruct (_,_)
+       | RHashtable _
+       | RStringList _
+       | RStructList (_,_) -> "== null"
+    
+      and generate_extern_prototype () =
+       pr "    static extern %s guestfs_%s (IntPtr h"
+         (c_return_type ()) name;
+       List.iter (
+         function
+         | Pathname n | Device n | Dev_or_Path n | String n | OptString n
+         | FileIn n | FileOut n ->
+              pr ", [In] string %s" n
+         | StringList n | DeviceList n ->
+              pr ", [In] string[] %s" n
+         | Bool n ->
+             pr ", bool %s" n
+         | Int n ->
+             pr ", int %s" n
+         | Int64 n ->
+             pr ", long %s" n
+       ) (snd style);
+       pr ");\n"
+
+      and generate_public_prototype () =
+       pr "    public %s %s (" (csharp_return_type ()) name;
+       let comma = ref false in
+       let next () =
+         if !comma then pr ", ";
+         comma := true
+       in
+       List.iter (
+         function
+         | Pathname n | Device n | Dev_or_Path n | String n | OptString n
+         | FileIn n | FileOut n ->
+              next (); pr "string %s" n
+         | StringList n | DeviceList n ->
+              next (); pr "string[] %s" n
+         | Bool n ->
+             next (); pr "bool %s" n
+         | Int n ->
+             next (); pr "int %s" n
+         | Int64 n ->
+             next (); pr "long %s" n
+       ) (snd style);
+       pr ")\n"
+
+      and generate_call () =
+       pr "guestfs_%s (_handle" name;
+       List.iter (fun arg -> pr ", %s" (name_of_argt arg)) (snd style);
+       pr ");\n";
+      in
+
+      pr "    [DllImport (\"%s\")]\n" library;
+      generate_extern_prototype ();
+      pr "\n";
+      pr "    /// <summary>\n";
+      pr "    /// %s\n" shortdesc;
+      pr "    /// </summary>\n";
+      generate_public_prototype ();
+      pr "    {\n";
+      pr "      %s r;\n" (c_return_type ());
+      pr "      r = ";
+      generate_call ();
+      pr "      if (r %s)\n" (c_error_comparison ());
+      pr "        throw new Error (\"%s: \" + guestfs_last_error (_handle));\n"
+        name;
+      (match fst style with
+       | RErr -> ()
+       | RBool _ ->
+           pr "      return r != 0 ? true : false;\n"
+       | RHashtable _ ->
+           pr "      Hashtable rr = new Hashtable ();\n";
+           pr "      for (int i = 0; i < r.Length; i += 2)\n";
+           pr "        rr.Add (r[i], r[i+1]);\n";
+           pr "      return rr;\n"
+       | RInt _ | RInt64 _ | RConstString _ | RConstOptString _
+       | RString _ | RBufferOut _ | RStruct _ | RStringList _
+       | RStructList _ ->
+           pr "      return r;\n"
+      );
+      pr "    }\n";
+      pr "\n";
+  ) all_functions_sorted;
+
+  pr "  }
+}
+"
+
 and generate_bindtests () =
   generate_header CStyle LGPLv2plus;
 
@@ -10884,6 +11277,11 @@ Run it from the top source directory using the command
   output_to "src/guestfs-internal-actions.h" generate_internal_actions_h;
   output_to "src/guestfs-actions.c" generate_client_actions;
   output_to "src/guestfs-bindtests.c" generate_bindtests;
+  output_to "src/guestfs-structs.pod" generate_structs_pod;
+  output_to "src/guestfs-actions.pod" generate_actions_pod;
+  output_to "src/guestfs-availability.pod" generate_availability_pod;
+  output_to "src/MAX_PROC_NR" generate_max_proc_nr;
+  output_to "src/libguestfs.syms" generate_linker_script;
   output_to "daemon/actions.h" generate_daemon_actions_h;
   output_to "daemon/stubs.c" generate_daemon_actions;
   output_to "daemon/names.c" generate_daemon_names;
@@ -10892,10 +11290,7 @@ Run it from the top source directory using the command
   output_to "capitests/tests.c" generate_tests;
   output_to "fish/cmds.c" generate_fish_cmds;
   output_to "fish/completion.c" generate_fish_completion;
-  output_to "guestfs-structs.pod" generate_structs_pod;
-  output_to "guestfs-actions.pod" generate_actions_pod;
-  output_to "guestfs-availability.pod" generate_availability_pod;
-  output_to "guestfish-actions.pod" generate_fish_actions_pod;
+  output_to "fish/guestfish-actions.pod" generate_fish_actions_pod;
   output_to "ocaml/guestfs.mli" generate_ocaml_mli;
   output_to "ocaml/guestfs.ml" generate_ocaml_ml;
   output_to "ocaml/guestfs_c_actions.c" generate_ocaml_c;
@@ -10924,11 +11319,13 @@ Run it from the top source directory using the command
   output_to "java/Bindtests.java" generate_java_bindtests;
   output_to "haskell/Guestfs.hs" generate_haskell_hs;
   output_to "haskell/Bindtests.hs" generate_haskell_bindtests;
-  output_to "src/MAX_PROC_NR" generate_max_proc_nr;
+  output_to "csharp/Libguestfs.cs" generate_csharp;
 
   (* Always generate this file last, and unconditionally.  It's used
    * by the Makefile to know when we must re-run the generator.
    *)
   let chan = open_out "src/stamp-generator" in
   fprintf chan "1\n";
-  close_out chan
+  close_out chan;
+
+  printf "generated %d lines of code\n" !lines