[Libguestfs] [PATCH 2/2] generator: Optional arguments, add-drive-opts

Richard W.M. Jones rjones at redhat.com
Thu Oct 21 18:41:50 UTC 2010


-- 
Richard Jones, Virtualization Group, Red Hat http://people.redhat.com/~rjones
Read my programming blog: http://rwmj.wordpress.com
Fedora now supports 80 OCaml packages (the OPEN alternative to F#)
http://cocan.org/getting_started_with_ocaml_on_red_hat_and_fedora
-------------- next part --------------
>From 4979f8fcadcb4d5f7d83fd1e79c9f256d69e27c0 Mon Sep 17 00:00:00 2001
From: Richard W.M. Jones <rjones at redhat.com>
Date: Wed, 20 Oct 2010 11:34:57 +0100
Subject: [PATCH 2/2] generator: Optional arguments, add-drive-opts (RHBZ#642934,CVE-2010-3851).

This large commit changes the generator so that optional arguments
can be supported for functions.

The model for arguments (known as the "style") is changed from
(ret, args) to (ret, args, optargs) where optargs is a more limited
list of arguments.

One function has been added which takes optional arguments, it is
"add-drive-opts", modelled as:

  (RErr, [String "filename"], #required
         [Bool "readonly"; String "format"; String "iface"]) #optional

Note that this function is processed in the library (does not go over
the RPC protocol to the daemon).  This has allowed us to simplify
the current implementation by omitting changes related to RPC or the
daemon, although we plan to add these at some point in the future.

>From C this function can be called in 3 different ways as in these
examples:

  guestfs_add_drive_opts (g, filename,
                          GUESTFS_ADD_DRIVE_OPTS_READONLY, 1,
			  GUESTFS_ADD_DRIVE_OPTS_FORMAT, "raw",
                          -1);

(the argument(s) between 'filename' and '-1' are the optional ones).

  guestfs_add_drive_opts_va (g, filename, args);

where 'args' is a va_list.  This works like the first version.

  struct guestfs_add_drive_opts_argv optargs = {
    .bitmask = GUESTFS_ADD_DRIVE_OPTS_READONLY_BITMASK,
    .readonly = 1,
  }
  guestfs_add_drive_opts_argv (g, filename, &optargs);

This last form lets you construct lists of optional arguments, and
is used by guestfish and the language bindings.

In guestfish optional arguments are used like this:

  add-drive-opts filename readonly:true

In OCaml these are mapped naturally to OCaml optional arguments, eg:

  g#add_drive_opts ~readonly:true filename;

In Perl these are mapped to extra arguments, eg:

  $g->add_drive_opts ($filename, readonly => 1);

In Python these are mapped to optional arguments, eg:

  g.add_drive_opts ("file", readonly = 1, format = "qcow2")

In Ruby these are mapped to a final hash argument, eg:

  g.add_drive_opts("file", {})
  g.add_drive_opts("file", :readonly => 1)
  g.add_drive_opts("file", :readonly => 1, :iface => "virtio")

In PHP these are mapped to extra parameters.  This is not quite
accurate since you cannot omit arbitrary optional parameters, but
there's not much than can be done within the limitations of PHP
as a language.

Unimplemented in: Haskell, C#, Java.
---
 .gitignore                         |    1 +
 fish/guestfish.pod                 |   15 +
 generator/generator_actions.ml     |  794 ++++++++++++++++++------------------
 generator/generator_bindtests.ml   |   10 +-
 generator/generator_c.ml           |  351 ++++++++++++++--
 generator/generator_capitests.ml   |    7 +
 generator/generator_checks.ml      |   21 +-
 generator/generator_csharp.ml      |   19 +-
 generator/generator_daemon.ml      |   30 +-
 generator/generator_fish.ml        |  144 +++++--
 generator/generator_haskell.ml     |   43 +-
 generator/generator_java.ml        |   62 ++-
 generator/generator_ocaml.ml       |   83 ++++-
 generator/generator_perl.ml        |   87 ++++-
 generator/generator_php.ml         |   91 ++++-
 generator/generator_python.ml      |  117 +++++--
 generator/generator_ruby.ml        |   55 ++-
 generator/generator_types.ml       |   47 ++-
 generator/generator_utils.ml       |    4 +-
 generator/generator_xdr.ml         |    9 +-
 ocaml/Makefile.am                  |    6 +
 ocaml/t/guestfs_080_optargs.ml     |   30 ++
 perl/t/070-optargs.t               |   34 ++
 php/extension/guestfs_php_004.phpt |   33 ++
 python/t/060-optargs.py            |   25 ++
 ruby/tests/tc_060_optargs.rb       |   33 ++
 src/guestfs.pod                    |  115 +++++-
 src/launch.c                       |  129 ++++--
 28 files changed, 1731 insertions(+), 664 deletions(-)
 create mode 100644 ocaml/t/guestfs_080_optargs.ml
 create mode 100644 perl/t/070-optargs.t
 create mode 100644 php/extension/guestfs_php_004.phpt
 create mode 100644 python/t/060-optargs.py
 create mode 100644 ruby/tests/tc_060_optargs.rb

diff --git a/.gitignore b/.gitignore
index f21ac09..bddbe25 100644
--- a/.gitignore
+++ b/.gitignore
@@ -177,6 +177,7 @@ ocaml/t/guestfs_010_launch
 ocaml/t/guestfs_050_lvcreate
 ocaml/t/guestfs_060_readdir
 ocaml/t/guestfs_070_threads
+ocaml/t/guestfs_080_optargs
 ocaml/t/guestfs_400_progress
 perl/bindtests.pl
 perl/blib
diff --git a/fish/guestfish.pod b/fish/guestfish.pod
index fc32d0a..c92953b 100644
--- a/fish/guestfish.pod
+++ b/fish/guestfish.pod
@@ -381,6 +381,21 @@ must be escaped with a backslash.
  command "/bin/echo 'foo      bar'"
  command "/bin/echo \'foo\'"
 
+=head1 OPTIONAL ARGUMENTS
+
+Some commands take optional arguments.  These arguments appear in this
+documentation as C<[argname:..]>.  You can use them as in these
+examples:
+
+ add-drive-opts filename
+
+ add-drive-opts filename readonly:true
+
+ add-drive-opts filename format:qcow2 readonly:false
+
+Each optional argument can appear at most once.  All optional
+arguments must appear after the required ones.
+
 =head1 NUMBERS
 
 This section applies to all commands which can take integers
diff --git a/generator/generator_actions.ml b/generator/generator_actions.ml
index c9890a6..695a73d 100644
--- a/generator/generator_actions.ml
+++ b/generator/generator_actions.ml
@@ -58,7 +58,7 @@ let test_all_rets = [
 ]
 
 let test_functions = [
-  ("test0", (RErr, test_all_args), -1, [NotInFish; NotInDocs],
+  ("test0", (RErr, test_all_args, []), -1, [NotInFish; NotInDocs],
    [],
    "internal test function - do not use",
    "\
@@ -72,7 +72,7 @@ You probably don't want to call this function.");
 ] @ List.flatten (
   List.map (
     fun (name, ret) ->
-      [(name, (ret, [String "val"]), -1, [NotInFish; NotInDocs],
+      [(name, (ret, [String "val"], []), -1, [NotInFish; NotInDocs],
         [],
         "internal test function - do not use",
         "\
@@ -83,7 +83,7 @@ return type correctly.
 It converts string C<val> to the return type.
 
 You probably don't want to call this function.");
-       (name ^ "err", (ret, []), -1, [NotInFish; NotInDocs],
+       (name ^ "err", (ret, [], []), -1, [NotInFish; NotInDocs],
         [],
         "internal test function - do not use",
         "\
@@ -103,7 +103,7 @@ You probably don't want to call this function.")]
  *)
 
 let non_daemon_functions = test_functions @ [
-  ("launch", (RErr, []), -1, [FishAlias "run"],
+  ("launch", (RErr, [], []), -1, [FishAlias "run"],
    [],
    "launch the qemu subprocess",
    "\
@@ -113,7 +113,7 @@ using L<qemu(1)>.
 You should call this after configuring the handle
 (eg. adding drives) but before performing any actions.");
 
-  ("wait_ready", (RErr, []), -1, [NotInFish],
+  ("wait_ready", (RErr, [], []), -1, [NotInFish],
    [],
    "wait until the qemu subprocess launches (no op)",
    "\
@@ -128,44 +128,28 @@ If you see any calls to this function in code then you can just
 remove them, unless you want to retain compatibility with older
 versions of the API.");
 
-  ("kill_subprocess", (RErr, []), -1, [],
+  ("kill_subprocess", (RErr, [], []), -1, [],
    [],
    "kill the qemu subprocess",
    "\
 This kills the qemu subprocess.  You should never need to call this.");
 
-  ("add_drive", (RErr, [String "filename"]), -1, [FishAlias "add"],
+  ("add_drive", (RErr, [String "filename"], []), -1, [],
    [],
    "add an image to examine or modify",
    "\
-This function adds a virtual machine disk image C<filename> to the
-guest.  The first time you call this function, the disk appears as IDE
-disk 0 (C</dev/sda>) in the guest, the second time as C</dev/sdb>, and
-so on.
+This function is the equivalent of calling C<guestfs_add_drive_opts>
+with no optional parameters, so the disk is added writable, with
+the format being detected automatically.
 
-You don't necessarily need to be root when using libguestfs.  However
-you obviously do need sufficient permissions to access the filename
-for whatever operations you want to perform (ie. read access if you
-just want to read the image or write access if you want to modify the
-image).
+Automatic detection of the format opens you up to a potential
+security hole when dealing with untrusted raw-format images.
+See CVE-2010-3851 and RHBZ#642934.  Specifying the format closes
+this security hole.  Therefore you should think about replacing
+calls to this function with calls to C<guestfs_add_drive_opts>,
+and specifying the format.");
 
-This is equivalent to the qemu parameter
-C<-drive file=filename,cache=off,if=...>.
-
-C<cache=off> is omitted in cases where it is not supported by
-the underlying filesystem.
-
-C<if=...> is set at compile time by the configuration option
-C<./configure --with-drive-if=...>.  In the rare case where you
-might need to change this at run time, use C<guestfs_add_drive_with_if>
-or C<guestfs_add_drive_ro_with_if>.
-
-Note that this call checks for the existence of C<filename>.  This
-stops you from specifying other types of drive which are supported
-by qemu such as C<nbd:> and C<http:> URLs.  To specify those, use
-the general C<guestfs_config> call instead.");
-
-  ("add_cdrom", (RErr, [String "filename"]), -1, [FishAlias "cdrom"],
+  ("add_cdrom", (RErr, [String "filename"], []), -1, [DeprecatedBy "add_drive_opts"],
    [],
    "add a CD-ROM disk image to examine",
    "\
@@ -192,33 +176,16 @@ should probably use C<guestfs_add_drive_ro> instead.
 
 =back");
 
-  ("add_drive_ro", (RErr, [String "filename"]), -1, [FishAlias "add-ro"],
+  ("add_drive_ro", (RErr, [String "filename"], []), -1, [FishAlias "add-ro"],
    [],
    "add a drive in snapshot mode (read-only)",
    "\
-This adds a drive in snapshot mode, making it effectively
-read-only.
-
-Note that writes to the device are allowed, and will be seen for
-the duration of the guestfs handle, but they are written
-to a temporary file which is discarded as soon as the guestfs
-handle is closed.  We don't currently have any method to enable
-changes to be committed, although qemu can support this.
-
-This is equivalent to the qemu parameter
-C<-drive file=filename,snapshot=on,if=...>.
-
-C<if=...> is set at compile time by the configuration option
-C<./configure --with-drive-if=...>.  In the rare case where you
-might need to change this at run time, use C<guestfs_add_drive_with_if>
-or C<guestfs_add_drive_ro_with_if>.
-
-Note that this call checks for the existence of C<filename>.  This
-stops you from specifying other types of drive which are supported
-by qemu such as C<nbd:> and C<http:> URLs.  To specify those, use
-the general C<guestfs_config> call instead.");
+This function is the equivalent of calling C<guestfs_add_drive_opts>
+with the optional parameter C<GUESTFS_ADD_DRIVE_OPTS_READONLY> set to 1,
+so the disk is added read-only, with the format being detected
+automatically.");
 
-  ("config", (RErr, [String "qemuparam"; OptString "qemuvalue"]), -1, [],
+  ("config", (RErr, [String "qemuparam"; OptString "qemuvalue"], []), -1, [],
    [],
    "add qemu parameters",
    "\
@@ -231,7 +198,7 @@ The first character of C<param> string must be a C<-> (dash).
 
 C<value> can be NULL.");
 
-  ("set_qemu", (RErr, [OptString "qemu"]), -1, [FishAlias "qemu"],
+  ("set_qemu", (RErr, [OptString "qemu"], []), -1, [FishAlias "qemu"],
    [],
    "set the qemu binary",
    "\
@@ -253,7 +220,7 @@ so you might see inconsistent results.  Using the environment
 variable C<LIBGUESTFS_QEMU> is safest of all since that picks
 the qemu binary at the same time as the handle is created.");
 
-  ("get_qemu", (RConstString "qemu", []), -1, [],
+  ("get_qemu", (RConstString "qemu", [], []), -1, [],
    [InitNone, Always, TestRun (
       [["get_qemu"]])],
    "get the qemu binary",
@@ -263,7 +230,7 @@ Return the current qemu binary.
 This is always non-NULL.  If it wasn't set already, then this will
 return the default qemu binary name.");
 
-  ("set_path", (RErr, [OptString "searchpath"]), -1, [FishAlias "path"],
+  ("set_path", (RErr, [OptString "searchpath"], []), -1, [FishAlias "path"],
    [],
    "set the search path",
    "\
@@ -274,7 +241,7 @@ C<LIBGUESTFS_PATH> environment variable.
 
 Setting C<path> to C<NULL> restores the default path.");
 
-  ("get_path", (RConstString "path", []), -1, [],
+  ("get_path", (RConstString "path", [], []), -1, [],
    [InitNone, Always, TestRun (
       [["get_path"]])],
    "get the search path",
@@ -284,7 +251,7 @@ Return the current search path.
 This is always non-NULL.  If it wasn't set already, then this will
 return the default path.");
 
-  ("set_append", (RErr, [OptString "append"]), -1, [FishAlias "append"],
+  ("set_append", (RErr, [OptString "append"], []), -1, [FishAlias "append"],
    [],
    "add options to kernel command line",
    "\
@@ -297,7 +264,7 @@ C<LIBGUESTFS_APPEND> environment variable.
 Setting C<append> to C<NULL> means I<no> additional options
 are passed (libguestfs always adds a few of its own).");
 
-  ("get_append", (RConstOptString "append", []), -1, [],
+  ("get_append", (RConstOptString "append", [], []), -1, [],
    (* This cannot be tested with the current framework.  The
     * function can return NULL in normal operations, which the
     * test framework interprets as an error.
@@ -310,7 +277,7 @@ guest kernel command line.
 
 If C<NULL> then no options are added.");
 
-  ("set_autosync", (RErr, [Bool "autosync"]), -1, [FishAlias "autosync"],
+  ("set_autosync", (RErr, [Bool "autosync"], []), -1, [FishAlias "autosync"],
    [],
    "set autosync mode",
    "\
@@ -322,14 +289,14 @@ C<guestfs_sync> when the handle is closed
 This is disabled by default (except in guestfish where it is
 enabled by default).");
 
-  ("get_autosync", (RBool "autosync", []), -1, [],
+  ("get_autosync", (RBool "autosync", [], []), -1, [],
    [InitNone, Always, TestRun (
       [["get_autosync"]])],
    "get autosync mode",
    "\
 Get the autosync flag.");
 
-  ("set_verbose", (RErr, [Bool "verbose"]), -1, [FishAlias "verbose"],
+  ("set_verbose", (RErr, [Bool "verbose"], []), -1, [FishAlias "verbose"],
    [],
    "set verbose mode",
    "\
@@ -338,13 +305,13 @@ If C<verbose> is true, this turns on verbose messages (to C<stderr>).
 Verbose messages are disabled unless the environment variable
 C<LIBGUESTFS_DEBUG> is defined and set to C<1>.");
 
-  ("get_verbose", (RBool "verbose", []), -1, [],
+  ("get_verbose", (RBool "verbose", [], []), -1, [],
    [],
    "get verbose mode",
    "\
 This returns the verbose messages flag.");
 
-  ("is_ready", (RBool "ready", []), -1, [],
+  ("is_ready", (RBool "ready", [], []), -1, [],
    [InitNone, Always, TestOutputTrue (
       [["is_ready"]])],
    "is ready to accept commands",
@@ -354,7 +321,7 @@ This returns true iff this handle is ready to accept commands
 
 For more information on states, see L<guestfs(3)>.");
 
-  ("is_config", (RBool "config", []), -1, [],
+  ("is_config", (RBool "config", [], []), -1, [],
    [InitNone, Always, TestOutputFalse (
       [["is_config"]])],
    "is in configuration state",
@@ -364,7 +331,7 @@ This returns true iff this handle is being configured
 
 For more information on states, see L<guestfs(3)>.");
 
-  ("is_launching", (RBool "launching", []), -1, [],
+  ("is_launching", (RBool "launching", [], []), -1, [],
    [InitNone, Always, TestOutputFalse (
       [["is_launching"]])],
    "is launching subprocess",
@@ -374,7 +341,7 @@ This returns true iff this handle is launching the subprocess
 
 For more information on states, see L<guestfs(3)>.");
 
-  ("is_busy", (RBool "busy", []), -1, [],
+  ("is_busy", (RBool "busy", [], []), -1, [],
    [InitNone, Always, TestOutputFalse (
       [["is_busy"]])],
    "is busy processing a command",
@@ -384,7 +351,7 @@ This returns true iff this handle is busy processing a command
 
 For more information on states, see L<guestfs(3)>.");
 
-  ("get_state", (RInt "state", []), -1, [],
+  ("get_state", (RInt "state", [], []), -1, [],
    [],
    "get the current state",
    "\
@@ -393,7 +360,7 @@ only useful for printing debug and internal error messages.
 
 For more information on states, see L<guestfs(3)>.");
 
-  ("set_memsize", (RErr, [Int "memsize"]), -1, [FishAlias "memsize"],
+  ("set_memsize", (RErr, [Int "memsize"], []), -1, [FishAlias "memsize"],
    [InitNone, Always, TestOutputInt (
       [["set_memsize"; "500"];
        ["get_memsize"]], 500)],
@@ -410,7 +377,7 @@ created.
 For more information on the architecture of libguestfs,
 see L<guestfs(3)>.");
 
-  ("get_memsize", (RInt "memsize", []), -1, [],
+  ("get_memsize", (RInt "memsize", [], []), -1, [],
    [InitNone, Always, TestOutputIntOp (
       [["get_memsize"]], ">=", 256)],
    "get memory allocated to the qemu subprocess",
@@ -425,7 +392,7 @@ then this returns the compiled-in default value for memsize.
 For more information on the architecture of libguestfs,
 see L<guestfs(3)>.");
 
-  ("get_pid", (RInt "pid", []), -1, [FishAlias "pid"],
+  ("get_pid", (RInt "pid", [], []), -1, [FishAlias "pid"],
    [InitNone, Always, TestOutputIntOp (
       [["get_pid"]], ">=", 1)],
    "get PID of qemu subprocess",
@@ -435,7 +402,7 @@ qemu subprocess, then this will return an error.
 
 This is an internal call used for debugging and testing.");
 
-  ("version", (RStruct ("version", "version"), []), -1, [],
+  ("version", (RStruct ("version", "version"), [], []), -1, [],
    [InitNone, Always, TestOutputStruct (
       [["version"]], [CompareWithInt ("major", 1)])],
    "get the library version number",
@@ -471,7 +438,7 @@ features from later versions into earlier versions,
 making this an unreliable way to test for features.
 Use C<guestfs_available> instead.");
 
-  ("set_selinux", (RErr, [Bool "selinux"]), -1, [FishAlias "selinux"],
+  ("set_selinux", (RErr, [Bool "selinux"], []), -1, [FishAlias "selinux"],
    [InitNone, Always, TestOutputTrue (
       [["set_selinux"; "true"];
        ["get_selinux"]])],
@@ -486,7 +453,7 @@ Permissive mode (C<enforcing=0>).
 For more information on the architecture of libguestfs,
 see L<guestfs(3)>.");
 
-  ("get_selinux", (RBool "selinux", []), -1, [],
+  ("get_selinux", (RBool "selinux", [], []), -1, [],
    [],
    "get SELinux enabled flag",
    "\
@@ -496,7 +463,7 @@ is passed to the appliance at boot time.  See C<guestfs_set_selinux>.
 For more information on the architecture of libguestfs,
 see L<guestfs(3)>.");
 
-  ("set_trace", (RErr, [Bool "trace"]), -1, [FishAlias "trace"],
+  ("set_trace", (RErr, [Bool "trace"], []), -1, [FishAlias "trace"],
    [InitNone, Always, TestOutputFalse (
       [["set_trace"; "false"];
        ["get_trace"]])],
@@ -516,13 +483,13 @@ the external ltrace(1) command.
 Command traces are disabled unless the environment variable
 C<LIBGUESTFS_TRACE> is defined and set to C<1>.");
 
-  ("get_trace", (RBool "trace", []), -1, [],
+  ("get_trace", (RBool "trace", [], []), -1, [],
    [],
    "get command trace enabled flag",
    "\
 Return the command trace flag.");
 
-  ("set_direct", (RErr, [Bool "direct"]), -1, [FishAlias "direct"],
+  ("set_direct", (RErr, [Bool "direct"], []), -1, [FishAlias "direct"],
    [InitNone, Always, TestOutputFalse (
       [["set_direct"; "false"];
        ["get_direct"]])],
@@ -541,13 +508,13 @@ are doing.
 
 The default is disabled.");
 
-  ("get_direct", (RBool "direct", []), -1, [],
+  ("get_direct", (RBool "direct", [], []), -1, [],
    [],
    "get direct appliance mode flag",
    "\
 Return the direct appliance mode flag.");
 
-  ("set_recovery_proc", (RErr, [Bool "recoveryproc"]), -1, [FishAlias "recovery-proc"],
+  ("set_recovery_proc", (RErr, [Bool "recoveryproc"], []), -1, [FishAlias "recovery-proc"],
    [InitNone, Always, TestOutputTrue (
       [["set_recovery_proc"; "true"];
        ["get_recovery_proc"]])],
@@ -567,27 +534,27 @@ if the main process will fork itself into the background
 thinks that the main program has disappeared and so kills
 qemu, which is not very helpful.");
 
-  ("get_recovery_proc", (RBool "recoveryproc", []), -1, [],
+  ("get_recovery_proc", (RBool "recoveryproc", [], []), -1, [],
    [],
    "get recovery process enabled flag",
    "\
 Return the recovery process enabled flag.");
 
-  ("add_drive_with_if", (RErr, [String "filename"; String "iface"]), -1, [],
+  ("add_drive_with_if", (RErr, [String "filename"; String "iface"], []), -1, [DeprecatedBy "add_drive_opts"],
    [],
    "add a drive specifying the QEMU block emulation to use",
    "\
 This is the same as C<guestfs_add_drive> but it allows you
 to specify the QEMU interface emulation to use at run time.");
 
-  ("add_drive_ro_with_if", (RErr, [String "filename"; String "iface"]), -1, [],
+  ("add_drive_ro_with_if", (RErr, [String "filename"; String "iface"], []), -1, [DeprecatedBy "add_drive_opts"],
    [],
    "add a drive read-only specifying the QEMU block emulation to use",
    "\
 This is the same as C<guestfs_add_drive_ro> but it allows you
 to specify the QEMU interface emulation to use at run time.");
 
-  ("file_architecture", (RString "arch", [Pathname "filename"]), -1, [],
+  ("file_architecture", (RString "arch", [Pathname "filename"], []), -1, [],
    [InitISOFS, Always, TestOutput (
       [["file_architecture"; "/bin-i586-dynamic"]], "i386");
     InitISOFS, Always, TestOutput (
@@ -715,7 +682,7 @@ initrd or kernel module(s) instead.
 
 =back");
 
-  ("inspect_os", (RStringList "roots", []), -1, [],
+  ("inspect_os", (RStringList "roots", [], []), -1, [],
    [],
    "inspect disk and return list of operating systems found",
    "\
@@ -751,7 +718,7 @@ Please read L<guestfs(3)/INSPECTION> for more details.
 
 See also C<guestfs_list_filesystems>.");
 
-  ("inspect_get_type", (RString "name", [Device "root"]), -1, [],
+  ("inspect_get_type", (RString "name", [Device "root"], []), -1, [],
    [],
    "get type of inspected operating system",
    "\
@@ -782,7 +749,7 @@ The caller should be prepared to handle any string.
 
 Please read L<guestfs(3)/INSPECTION> for more details.");
 
-  ("inspect_get_arch", (RString "arch", [Device "root"]), -1, [],
+  ("inspect_get_arch", (RString "arch", [Device "root"], []), -1, [],
    [],
    "get architecture of inspected operating system",
    "\
@@ -798,7 +765,7 @@ string C<unknown> is returned.
 
 Please read L<guestfs(3)/INSPECTION> for more details.");
 
-  ("inspect_get_distro", (RString "distro", [Device "root"]), -1, [],
+  ("inspect_get_distro", (RString "distro", [Device "root"], []), -1, [],
    [],
    "get distro of inspected operating system",
    "\
@@ -844,7 +811,7 @@ The caller should be prepared to handle any string.
 
 Please read L<guestfs(3)/INSPECTION> for more details.");
 
-  ("inspect_get_major_version", (RInt "major", [Device "root"]), -1, [],
+  ("inspect_get_major_version", (RInt "major", [Device "root"], []), -1, [],
    [],
    "get major version of inspected operating system",
    "\
@@ -865,7 +832,7 @@ If the version could not be determined, then C<0> is returned.
 
 Please read L<guestfs(3)/INSPECTION> for more details.");
 
-  ("inspect_get_minor_version", (RInt "minor", [Device "root"]), -1, [],
+  ("inspect_get_minor_version", (RInt "minor", [Device "root"], []), -1, [],
    [],
    "get minor version of inspected operating system",
    "\
@@ -880,7 +847,7 @@ If the version could not be determined, then C<0> is returned.
 Please read L<guestfs(3)/INSPECTION> for more details.
 See also C<guestfs_inspect_get_major_version>.");
 
-  ("inspect_get_product_name", (RString "product", [Device "root"]), -1, [],
+  ("inspect_get_product_name", (RString "product", [Device "root"], []), -1, [],
    [],
    "get product name of inspected operating system",
    "\
@@ -897,7 +864,7 @@ string C<unknown> is returned.
 
 Please read L<guestfs(3)/INSPECTION> for more details.");
 
-  ("inspect_get_mountpoints", (RHashtable "mountpoints", [Device "root"]), -1, [],
+  ("inspect_get_mountpoints", (RHashtable "mountpoints", [Device "root"], []), -1, [],
    [],
    "get mountpoints of inspected operating system",
    "\
@@ -920,7 +887,7 @@ returned in this list.
 Please read L<guestfs(3)/INSPECTION> for more details.
 See also C<guestfs_inspect_get_filesystems>.");
 
-  ("inspect_get_filesystems", (RStringList "filesystems", [Device "root"]), -1, [],
+  ("inspect_get_filesystems", (RStringList "filesystems", [Device "root"], []), -1, [],
    [],
    "get filesystems associated with inspected operating system",
    "\
@@ -938,7 +905,7 @@ for a filesystem to be shared between operating systems.
 Please read L<guestfs(3)/INSPECTION> for more details.
 See also C<guestfs_inspect_get_mountpoints>.");
 
-  ("set_network", (RErr, [Bool "network"]), -1, [FishAlias "network"],
+  ("set_network", (RErr, [Bool "network"], []), -1, [FishAlias "network"],
    [],
    "set enable network flag",
    "\
@@ -951,13 +918,13 @@ This affects whether commands are able to access the network
 You must call this before calling C<guestfs_launch>, otherwise
 it has no effect.");
 
-  ("get_network", (RBool "network", []), -1, [],
+  ("get_network", (RBool "network", [], []), -1, [],
    [],
    "get enable network flag",
    "\
 This returns the enable network flag.");
 
-  ("list_filesystems", (RHashtable "fses", []), -1, [],
+  ("list_filesystems", (RHashtable "fses", [], []), -1, [],
    [],
    "list filesystems",
    "\
@@ -990,6 +957,51 @@ be mountable but require special options.  Filesystems may
 not all belong to a single logical operating system
 (use C<guestfs_inspect_os> to look for OSes).");
 
+  ("add_drive_opts", (RErr, [String "filename"], [Bool "readonly"; String "format"; String "iface"]), -1, [FishAlias "add"],
+   [],
+   "add an image to examine or modify",
+   "\
+This function adds a virtual machine disk image C<filename> to
+libguestfs.  The first time you call this function, the disk
+appears as C</dev/sda>, the second time as C</dev/sdb>, and
+so on.
+
+You don't necessarily need to be root when using libguestfs.  However
+you obviously do need sufficient permissions to access the filename
+for whatever operations you want to perform (ie. read access if you
+just want to read the image or write access if you want to modify the
+image).
+
+This call checks that C<filename> exists.
+
+The optional arguments are:
+
+=over 4
+
+=item C<readonly>
+
+If true then the image is treated as read-only.  Writes are still
+allowed, but they are stored in a temporary snapshot overlay which
+is discarded at the end.  The disk that you add is not modified.
+
+=item C<format>
+
+This forces the image format.  If you omit this (or use C<guestfs_add_drive>
+or C<guestfs_add_drive_ro>) then the format is automatically detected.
+Possible formats include C<raw> and C<qcow2>.
+
+Automatic detection of the format opens you up to a potential
+security hole when dealing with untrusted raw-format images.
+See CVE-2010-3851 and RHBZ#642934.  Specifying the format closes
+this security hole.
+
+=item C<iface>
+
+This rarely-used option lets you emulate the behaviour of the
+deprecated C<guestfs_add_drive_with_if> call (q.v.)
+
+=back");
+
 ]
 
 (* daemon_functions are any functions which cause some action
@@ -997,7 +1009,7 @@ not all belong to a single logical operating system
  *)
 
 let daemon_functions = [
-  ("mount", (RErr, [Device "device"; String "mountpoint"]), 1, [],
+  ("mount", (RErr, [Device "device"; String "mountpoint"], []), 1, [],
    [InitEmpty, Always, TestOutput (
       [["part_disk"; "/dev/sda"; "mbr"];
        ["mkfs"; "ext2"; "/dev/sda1"];
@@ -1030,7 +1042,7 @@ C<guestfs_mount> in any code that needs performance, and instead
 use C<guestfs_mount_options> (use an empty string for the first
 parameter if you don't want any options).");
 
-  ("sync", (RErr, []), 2, [],
+  ("sync", (RErr, [], []), 2, [],
    [ InitEmpty, Always, TestRun [["sync"]]],
    "sync disks, writes are flushed through to the disk image",
    "\
@@ -1040,7 +1052,7 @@ underlying disk image.
 You should always call this if you have modified a disk image, before
 closing the handle.");
 
-  ("touch", (RErr, [Pathname "path"]), 3, [],
+  ("touch", (RErr, [Pathname "path"], []), 3, [],
    [InitBasicFS, Always, TestOutputTrue (
       [["touch"; "/new"];
        ["exists"; "/new"]])],
@@ -1053,7 +1065,7 @@ to create a new zero-length file.
 This command only works on regular files, and will fail on other
 file types such as directories, symbolic links, block special etc.");
 
-  ("cat", (RString "content", [Pathname "path"]), 4, [ProtocolLimitWarning],
+  ("cat", (RString "content", [Pathname "path"], []), 4, [ProtocolLimitWarning],
    [InitISOFS, Always, TestOutput (
       [["cat"; "/known-2"]], "abcdef\n")],
    "list the contents of a file",
@@ -1065,7 +1077,7 @@ Note that this function cannot correctly handle binary files
 as end of string).  For those you need to use the C<guestfs_read_file>
 or C<guestfs_download> functions which have a more complex interface.");
 
-  ("ll", (RString "listing", [Pathname "directory"]), 5, [],
+  ("ll", (RString "listing", [Pathname "directory"], []), 5, [],
    [], (* XXX Tricky to test because it depends on the exact format
         * of the 'ls -l' command, which changes between F10 and F11.
         *)
@@ -1077,7 +1089,7 @@ there is no cwd) in the format of 'ls -la'.
 This command is mostly useful for interactive sessions.  It
 is I<not> intended that you try to parse the output string.");
 
-  ("ls", (RStringList "listing", [Pathname "directory"]), 6, [],
+  ("ls", (RStringList "listing", [Pathname "directory"], []), 6, [],
    [InitBasicFS, Always, TestOutputList (
       [["touch"; "/new"];
        ["touch"; "/newer"];
@@ -1092,7 +1104,7 @@ hidden files are shown.
 This command is mostly useful for interactive sessions.  Programs
 should probably use C<guestfs_readdir> instead.");
 
-  ("list_devices", (RStringList "devices", []), 7, [],
+  ("list_devices", (RStringList "devices", [], []), 7, [],
    [InitEmpty, Always, TestOutputListOfDevices (
       [["list_devices"]], ["/dev/sda"; "/dev/sdb"; "/dev/sdc"; "/dev/sdd"])],
    "list the block devices",
@@ -1103,7 +1115,7 @@ The full block device names are returned, eg. C</dev/sda>.
 
 See also C<guestfs_list_filesystems>.");
 
-  ("list_partitions", (RStringList "partitions", []), 8, [],
+  ("list_partitions", (RStringList "partitions", [], []), 8, [],
    [InitBasicFS, Always, TestOutputListOfDevices (
       [["list_partitions"]], ["/dev/sda1"]);
     InitEmpty, Always, TestOutputListOfDevices (
@@ -1120,7 +1132,7 @@ call C<guestfs_lvs>.
 
 See also C<guestfs_list_filesystems>.");
 
-  ("pvs", (RStringList "physvols", []), 9, [Optional "lvm2"],
+  ("pvs", (RStringList "physvols", [], []), 9, [Optional "lvm2"],
    [InitBasicFSonLVM, Always, TestOutputListOfDevices (
       [["pvs"]], ["/dev/sda1"]);
     InitEmpty, Always, TestOutputListOfDevices (
@@ -1139,7 +1151,7 @@ PVs (eg. C</dev/sda2>).
 
 See also C<guestfs_pvs_full>.");
 
-  ("vgs", (RStringList "volgroups", []), 10, [Optional "lvm2"],
+  ("vgs", (RStringList "volgroups", [], []), 10, [Optional "lvm2"],
    [InitBasicFSonLVM, Always, TestOutputList (
       [["vgs"]], ["VG"]);
     InitEmpty, Always, TestOutputList (
@@ -1160,7 +1172,7 @@ detected (eg. C<VolGroup00>).
 
 See also C<guestfs_vgs_full>.");
 
-  ("lvs", (RStringList "logvols", []), 11, [Optional "lvm2"],
+  ("lvs", (RStringList "logvols", [], []), 11, [Optional "lvm2"],
    [InitBasicFSonLVM, Always, TestOutputList (
       [["lvs"]], ["/dev/VG/LV"]);
     InitEmpty, Always, TestOutputList (
@@ -1184,28 +1196,28 @@ This returns a list of the logical volume device names
 
 See also C<guestfs_lvs_full>, C<guestfs_list_filesystems>.");
 
-  ("pvs_full", (RStructList ("physvols", "lvm_pv"), []), 12, [Optional "lvm2"],
+  ("pvs_full", (RStructList ("physvols", "lvm_pv"), [], []), 12, [Optional "lvm2"],
    [], (* XXX how to test? *)
    "list the LVM physical volumes (PVs)",
    "\
 List all the physical volumes detected.  This is the equivalent
 of the L<pvs(8)> command.  The \"full\" version includes all fields.");
 
-  ("vgs_full", (RStructList ("volgroups", "lvm_vg"), []), 13, [Optional "lvm2"],
+  ("vgs_full", (RStructList ("volgroups", "lvm_vg"), [], []), 13, [Optional "lvm2"],
    [], (* XXX how to test? *)
    "list the LVM volume groups (VGs)",
    "\
 List all the volumes groups detected.  This is the equivalent
 of the L<vgs(8)> command.  The \"full\" version includes all fields.");
 
-  ("lvs_full", (RStructList ("logvols", "lvm_lv"), []), 14, [Optional "lvm2"],
+  ("lvs_full", (RStructList ("logvols", "lvm_lv"), [], []), 14, [Optional "lvm2"],
    [], (* XXX how to test? *)
    "list the LVM logical volumes (LVs)",
    "\
 List all the logical volumes detected.  This is the equivalent
 of the L<lvs(8)> command.  The \"full\" version includes all fields.");
 
-  ("read_lines", (RStringList "lines", [Pathname "path"]), 15, [],
+  ("read_lines", (RStringList "lines", [Pathname "path"], []), 15, [],
    [InitISOFS, Always, TestOutputList (
       [["read_lines"; "/known-4"]], ["abc"; "def"; "ghi"]);
     InitISOFS, Always, TestOutputList (
@@ -1222,7 +1234,7 @@ Note that this function cannot correctly handle binary files
 as end of line).  For those you need to use the C<guestfs_read_file>
 function which has a more complex interface.");
 
-  ("aug_init", (RErr, [Pathname "root"; Int "flags"]), 16, [Optional "augeas"],
+  ("aug_init", (RErr, [Pathname "root"; Int "flags"], []), 16, [Optional "augeas"],
    [], (* XXX Augeas code needs tests. *)
    "create a new Augeas handle",
    "\
@@ -1273,7 +1285,7 @@ To close the handle, you can call C<guestfs_aug_close>.
 
 To find out more about Augeas, see L<http://augeas.net/>.");
 
-  ("aug_close", (RErr, []), 26, [Optional "augeas"],
+  ("aug_close", (RErr, [], []), 26, [Optional "augeas"],
    [], (* XXX Augeas code needs tests. *)
    "close the current Augeas handle",
    "\
@@ -1282,7 +1294,7 @@ used by it.  After calling this, you have to call
 C<guestfs_aug_init> again before you can use any other
 Augeas functions.");
 
-  ("aug_defvar", (RInt "nrnodes", [String "name"; OptString "expr"]), 17, [Optional "augeas"],
+  ("aug_defvar", (RInt "nrnodes", [String "name"; OptString "expr"], []), 17, [Optional "augeas"],
    [], (* XXX Augeas code needs tests. *)
    "define an Augeas variable",
    "\
@@ -1293,7 +1305,7 @@ undefined.
 On success this returns the number of nodes in C<expr>, or
 C<0> if C<expr> evaluates to something which is not a nodeset.");
 
-  ("aug_defnode", (RStruct ("nrnodescreated", "int_bool"), [String "name"; String "expr"; String "val"]), 18, [Optional "augeas"],
+  ("aug_defnode", (RStruct ("nrnodescreated", "int_bool"), [String "name"; String "expr"; String "val"], []), 18, [Optional "augeas"],
    [], (* XXX Augeas code needs tests. *)
    "define an Augeas node",
    "\
@@ -1308,14 +1320,14 @@ On success this returns a pair containing the
 number of nodes in the nodeset, and a boolean flag
 if a node was created.");
 
-  ("aug_get", (RString "val", [String "augpath"]), 19, [Optional "augeas"],
+  ("aug_get", (RString "val", [String "augpath"], []), 19, [Optional "augeas"],
    [], (* XXX Augeas code needs tests. *)
    "look up the value of an Augeas path",
    "\
 Look up the value associated with C<path>.  If C<path>
 matches exactly one node, the C<value> is returned.");
 
-  ("aug_set", (RErr, [String "augpath"; String "val"]), 20, [Optional "augeas"],
+  ("aug_set", (RErr, [String "augpath"; String "val"], []), 20, [Optional "augeas"],
    [], (* XXX Augeas code needs tests. *)
    "set Augeas path to value",
    "\
@@ -1326,7 +1338,7 @@ the value to NULL.  Due to an oversight in the libguestfs API
 you cannot do that with this call.  Instead you must use the
 C<guestfs_aug_clear> call.");
 
-  ("aug_insert", (RErr, [String "augpath"; String "label"; Bool "before"]), 21, [Optional "augeas"],
+  ("aug_insert", (RErr, [String "augpath"; String "label"; Bool "before"], []), 21, [Optional "augeas"],
    [], (* XXX Augeas code needs tests. *)
    "insert a sibling Augeas node",
    "\
@@ -1338,7 +1350,7 @@ C<path> must match exactly one existing node in the tree, and
 C<label> must be a label, ie. not contain C</>, C<*> or end
 with a bracketed index C<[N]>.");
 
-  ("aug_rm", (RInt "nrnodes", [String "augpath"]), 22, [Optional "augeas"],
+  ("aug_rm", (RInt "nrnodes", [String "augpath"], []), 22, [Optional "augeas"],
    [], (* XXX Augeas code needs tests. *)
    "remove an Augeas path",
    "\
@@ -1346,14 +1358,14 @@ Remove C<path> and all of its children.
 
 On success this returns the number of entries which were removed.");
 
-  ("aug_mv", (RErr, [String "src"; String "dest"]), 23, [Optional "augeas"],
+  ("aug_mv", (RErr, [String "src"; String "dest"], []), 23, [Optional "augeas"],
    [], (* XXX Augeas code needs tests. *)
    "move Augeas node",
    "\
 Move the node C<src> to C<dest>.  C<src> must match exactly
 one node.  C<dest> is overwritten if it exists.");
 
-  ("aug_match", (RStringList "matches", [String "augpath"]), 24, [Optional "augeas"],
+  ("aug_match", (RStringList "matches", [String "augpath"], []), 24, [Optional "augeas"],
    [], (* XXX Augeas code needs tests. *)
    "return Augeas nodes which match augpath",
    "\
@@ -1361,7 +1373,7 @@ Returns a list of paths which match the path expression C<path>.
 The returned paths are sufficiently qualified so that they match
 exactly one node in the current tree.");
 
-  ("aug_save", (RErr, []), 25, [Optional "augeas"],
+  ("aug_save", (RErr, [], []), 25, [Optional "augeas"],
    [], (* XXX Augeas code needs tests. *)
    "write all pending Augeas changes to disk",
    "\
@@ -1370,7 +1382,7 @@ This writes all pending changes to disk.
 The flags which were passed to C<guestfs_aug_init> affect exactly
 how files are saved.");
 
-  ("aug_load", (RErr, []), 27, [Optional "augeas"],
+  ("aug_load", (RErr, [], []), 27, [Optional "augeas"],
    [], (* XXX Augeas code needs tests. *)
    "load files into the tree",
    "\
@@ -1379,14 +1391,14 @@ Load files into the tree.
 See C<aug_load> in the Augeas documentation for the full gory
 details.");
 
-  ("aug_ls", (RStringList "matches", [String "augpath"]), 28, [Optional "augeas"],
+  ("aug_ls", (RStringList "matches", [String "augpath"], []), 28, [Optional "augeas"],
    [], (* XXX Augeas code needs tests. *)
    "list Augeas nodes under augpath",
    "\
 This is just a shortcut for listing C<guestfs_aug_match>
 C<path/*> and sorting the resulting nodes into alphabetical order.");
 
-  ("rm", (RErr, [Pathname "path"]), 29, [],
+  ("rm", (RErr, [Pathname "path"], []), 29, [],
    [InitBasicFS, Always, TestRun
       [["touch"; "/new"];
        ["rm"; "/new"]];
@@ -1399,7 +1411,7 @@ C<path/*> and sorting the resulting nodes into alphabetical order.");
    "\
 Remove the single file C<path>.");
 
-  ("rmdir", (RErr, [Pathname "path"]), 30, [],
+  ("rmdir", (RErr, [Pathname "path"], []), 30, [],
    [InitBasicFS, Always, TestRun
       [["mkdir"; "/new"];
        ["rmdir"; "/new"]];
@@ -1412,7 +1424,7 @@ Remove the single file C<path>.");
    "\
 Remove the single directory C<path>.");
 
-  ("rm_rf", (RErr, [Pathname "path"]), 31, [],
+  ("rm_rf", (RErr, [Pathname "path"], []), 31, [],
    [InitBasicFS, Always, TestOutputFalse
       [["mkdir"; "/new"];
        ["mkdir"; "/new/foo"];
@@ -1425,7 +1437,7 @@ Remove the file or directory C<path>, recursively removing the
 contents if its a directory.  This is like the C<rm -rf> shell
 command.");
 
-  ("mkdir", (RErr, [Pathname "path"]), 32, [],
+  ("mkdir", (RErr, [Pathname "path"], []), 32, [],
    [InitBasicFS, Always, TestOutputTrue
       [["mkdir"; "/new"];
        ["is_dir"; "/new"]];
@@ -1435,7 +1447,7 @@ command.");
    "\
 Create a directory named C<path>.");
 
-  ("mkdir_p", (RErr, [Pathname "path"]), 33, [],
+  ("mkdir_p", (RErr, [Pathname "path"], []), 33, [],
    [InitBasicFS, Always, TestOutputTrue
       [["mkdir_p"; "/new/foo/bar"];
        ["is_dir"; "/new/foo/bar"]];
@@ -1457,7 +1469,7 @@ Create a directory named C<path>.");
 Create a directory named C<path>, creating any parent directories
 as necessary.  This is like the C<mkdir -p> shell command.");
 
-  ("chmod", (RErr, [Int "mode"; Pathname "path"]), 34, [],
+  ("chmod", (RErr, [Int "mode"; Pathname "path"], []), 34, [],
    [], (* XXX Need stat command to test *)
    "change file mode",
    "\
@@ -1470,7 +1482,7 @@ C<0> to get octal, ie. use C<0700> not C<700>.
 
 The mode actually set is affected by the umask.");
 
-  ("chown", (RErr, [Int "owner"; Int "group"; Pathname "path"]), 35, [],
+  ("chown", (RErr, [Int "owner"; Int "group"; Pathname "path"], []), 35, [],
    [], (* XXX Need stat command to test *)
    "change file owner and group",
    "\
@@ -1480,7 +1492,7 @@ Only numeric uid and gid are supported.  If you want to use
 names, you will need to locate and parse the password file
 yourself (Augeas support makes this relatively easy).");
 
-  ("exists", (RBool "existsflag", [Pathname "path"]), 36, [],
+  ("exists", (RBool "existsflag", [Pathname "path"], []), 36, [],
    [InitISOFS, Always, TestOutputTrue (
       [["exists"; "/empty"]]);
     InitISOFS, Always, TestOutputTrue (
@@ -1492,7 +1504,7 @@ This returns C<true> if and only if there is a file, directory
 
 See also C<guestfs_is_file>, C<guestfs_is_dir>, C<guestfs_stat>.");
 
-  ("is_file", (RBool "fileflag", [Pathname "path"]), 37, [],
+  ("is_file", (RBool "fileflag", [Pathname "path"], []), 37, [],
    [InitISOFS, Always, TestOutputTrue (
       [["is_file"; "/known-1"]]);
     InitISOFS, Always, TestOutputFalse (
@@ -1505,7 +1517,7 @@ other objects like directories.
 
 See also C<guestfs_stat>.");
 
-  ("is_dir", (RBool "dirflag", [Pathname "path"]), 38, [],
+  ("is_dir", (RBool "dirflag", [Pathname "path"], []), 38, [],
    [InitISOFS, Always, TestOutputFalse (
       [["is_dir"; "/known-3"]]);
     InitISOFS, Always, TestOutputTrue (
@@ -1518,7 +1530,7 @@ other objects like files.
 
 See also C<guestfs_stat>.");
 
-  ("pvcreate", (RErr, [Device "device"]), 39, [Optional "lvm2"],
+  ("pvcreate", (RErr, [Device "device"], []), 39, [Optional "lvm2"],
    [InitEmpty, Always, TestOutputListOfDevices (
       [["sfdiskM"; "/dev/sda"; ",100 ,200 ,"];
        ["pvcreate"; "/dev/sda1"];
@@ -1531,7 +1543,7 @@ This creates an LVM physical volume on the named C<device>,
 where C<device> should usually be a partition name such
 as C</dev/sda1>.");
 
-  ("vgcreate", (RErr, [String "volgroup"; DeviceList "physvols"]), 40, [Optional "lvm2"],
+  ("vgcreate", (RErr, [String "volgroup"; DeviceList "physvols"], []), 40, [Optional "lvm2"],
    [InitEmpty, Always, TestOutputList (
       [["sfdiskM"; "/dev/sda"; ",100 ,200 ,"];
        ["pvcreate"; "/dev/sda1"];
@@ -1545,7 +1557,7 @@ as C</dev/sda1>.");
 This creates an LVM volume group called C<volgroup>
 from the non-empty list of physical volumes C<physvols>.");
 
-  ("lvcreate", (RErr, [String "logvol"; String "volgroup"; Int "mbytes"]), 41, [Optional "lvm2"],
+  ("lvcreate", (RErr, [String "logvol"; String "volgroup"; Int "mbytes"], []), 41, [Optional "lvm2"],
    [InitEmpty, Always, TestOutputList (
       [["sfdiskM"; "/dev/sda"; ",100 ,200 ,"];
        ["pvcreate"; "/dev/sda1"];
@@ -1566,7 +1578,7 @@ from the non-empty list of physical volumes C<physvols>.");
 This creates an LVM logical volume called C<logvol>
 on the volume group C<volgroup>, with C<size> megabytes.");
 
-  ("mkfs", (RErr, [String "fstype"; Device "device"]), 42, [],
+  ("mkfs", (RErr, [String "fstype"; Device "device"], []), 42, [],
    [InitEmpty, Always, TestOutput (
       [["part_disk"; "/dev/sda"; "mbr"];
        ["mkfs"; "ext2"; "/dev/sda1"];
@@ -1581,7 +1593,7 @@ example C<ext3>.");
 
   ("sfdisk", (RErr, [Device "device";
                      Int "cyls"; Int "heads"; Int "sectors";
-                     StringList "lines"]), 43, [DangerWillRobinson],
+                     StringList "lines"], []), 43, [DangerWillRobinson],
    [],
    "create partitions on a block device",
    "\
@@ -1608,7 +1620,7 @@ the string C<,> (comma).
 See also: C<guestfs_sfdisk_l>, C<guestfs_sfdisk_N>,
 C<guestfs_part_init>");
 
-  ("write_file", (RErr, [Pathname "path"; String "content"; Int "size"]), 44, [ProtocolLimitWarning; DeprecatedBy "write"],
+  ("write_file", (RErr, [Pathname "path"; String "content"; Int "size"], []), 44, [ProtocolLimitWarning; DeprecatedBy "write"],
    (* Regression test for RHBZ#597135. *)
    [InitBasicFS, Always, TestLastFail
       [["write_file"; "/new"; "abc"; "10000"]]],
@@ -1625,7 +1637,7 @@ the content cannot contain embedded ASCII NULs).
 I<NB.> Owing to a bug, writing content containing ASCII NUL
 characters does I<not> work, even if the length is specified.");
 
-  ("umount", (RErr, [String "pathordevice"]), 45, [FishAlias "unmount"],
+  ("umount", (RErr, [String "pathordevice"], []), 45, [FishAlias "unmount"],
    [InitEmpty, Always, TestOutputListOfDevices (
       [["part_disk"; "/dev/sda"; "mbr"];
        ["mkfs"; "ext2"; "/dev/sda1"];
@@ -1643,7 +1655,7 @@ This unmounts the given filesystem.  The filesystem may be
 specified either by its mountpoint (path) or the device which
 contains the filesystem.");
 
-  ("mounts", (RStringList "devices", []), 46, [],
+  ("mounts", (RStringList "devices", [], []), 46, [],
    [InitBasicFS, Always, TestOutputListOfDevices (
       [["mounts"]], ["/dev/sda1"])],
    "show mounted filesystems",
@@ -1655,7 +1667,7 @@ Some internal mounts are not shown.
 
 See also: C<guestfs_mountpoints>");
 
-  ("umount_all", (RErr, []), 47, [FishAlias "unmount-all"],
+  ("umount_all", (RErr, [], []), 47, [FishAlias "unmount-all"],
    [InitBasicFS, Always, TestOutputList (
       [["umount_all"];
        ["mounts"]], []);
@@ -1679,14 +1691,14 @@ This unmounts all mounted filesystems.
 
 Some internal mounts are not unmounted by this call.");
 
-  ("lvm_remove_all", (RErr, []), 48, [DangerWillRobinson; Optional "lvm2"],
+  ("lvm_remove_all", (RErr, [], []), 48, [DangerWillRobinson; Optional "lvm2"],
    [],
    "remove all LVM LVs, VGs and PVs",
    "\
 This command removes all LVM logical volumes, volume groups
 and physical volumes.");
 
-  ("file", (RString "description", [Dev_or_Path "path"]), 49, [],
+  ("file", (RString "description", [Dev_or_Path "path"], []), 49, [],
    [InitISOFS, Always, TestOutput (
       [["file"; "/empty"]], "empty");
     InitISOFS, Always, TestOutput (
@@ -1719,7 +1731,7 @@ this command only works for the content of regular files.
 For other file types (directory, symbolic link etc) it
 will just return the string C<directory> etc.");
 
-  ("command", (RString "output", [StringList "arguments"]), 50, [ProtocolLimitWarning],
+  ("command", (RString "output", [StringList "arguments"], []), 50, [ProtocolLimitWarning],
    [InitBasicFS, Always, TestOutput (
       [["upload"; "test-command"; "/test-command"];
        ["chmod"; "0o755"; "/test-command"];
@@ -1800,7 +1812,7 @@ correct places.  It is the caller's responsibility to ensure
 all filesystems that are needed are mounted at the right
 locations.");
 
-  ("command_lines", (RStringList "lines", [StringList "arguments"]), 51, [ProtocolLimitWarning],
+  ("command_lines", (RStringList "lines", [StringList "arguments"], []), 51, [ProtocolLimitWarning],
    [InitBasicFS, Always, TestOutputList (
       [["upload"; "test-command"; "/test-command"];
        ["chmod"; "0o755"; "/test-command"];
@@ -1852,7 +1864,7 @@ result into a list of lines.
 
 See also: C<guestfs_sh_lines>");
 
-  ("stat", (RStruct ("statbuf", "stat"), [Pathname "path"]), 52, [],
+  ("stat", (RStruct ("statbuf", "stat"), [Pathname "path"], []), 52, [],
    [InitISOFS, Always, TestOutputStruct (
       [["stat"; "/empty"]], [CompareWithInt ("size", 0)])],
    "get file information",
@@ -1861,7 +1873,7 @@ Returns file information for the given C<path>.
 
 This is the same as the C<stat(2)> system call.");
 
-  ("lstat", (RStruct ("statbuf", "stat"), [Pathname "path"]), 53, [],
+  ("lstat", (RStruct ("statbuf", "stat"), [Pathname "path"], []), 53, [],
    [InitISOFS, Always, TestOutputStruct (
       [["lstat"; "/empty"]], [CompareWithInt ("size", 0)])],
    "get file information for a symbolic link",
@@ -1874,7 +1886,7 @@ refers to.
 
 This is the same as the C<lstat(2)> system call.");
 
-  ("statvfs", (RStruct ("statbuf", "statvfs"), [Pathname "path"]), 54, [],
+  ("statvfs", (RStruct ("statbuf", "statvfs"), [Pathname "path"], []), 54, [],
    [InitISOFS, Always, TestOutputStruct (
       [["statvfs"; "/"]], [CompareWithInt ("namemax", 255)])],
    "get file system statistics",
@@ -1885,7 +1897,7 @@ C<path> should be a file or directory in the mounted file system
 
 This is the same as the C<statvfs(2)> system call.");
 
-  ("tune2fs_l", (RHashtable "superblock", [Device "device"]), 55, [],
+  ("tune2fs_l", (RHashtable "superblock", [Device "device"], []), 55, [],
    [], (* XXX test *)
    "get ext2/ext3/ext4 superblock details",
    "\
@@ -1897,7 +1909,7 @@ manpage for more details.  The list of fields returned isn't
 clearly defined, and depends on both the version of C<tune2fs>
 that libguestfs was built against, and the filesystem itself.");
 
-  ("blockdev_setro", (RErr, [Device "device"]), 56, [],
+  ("blockdev_setro", (RErr, [Device "device"], []), 56, [],
    [InitEmpty, Always, TestOutputTrue (
       [["blockdev_setro"; "/dev/sda"];
        ["blockdev_getro"; "/dev/sda"]])],
@@ -1907,7 +1919,7 @@ Sets the block device named C<device> to read-only.
 
 This uses the L<blockdev(8)> command.");
 
-  ("blockdev_setrw", (RErr, [Device "device"]), 57, [],
+  ("blockdev_setrw", (RErr, [Device "device"], []), 57, [],
    [InitEmpty, Always, TestOutputFalse (
       [["blockdev_setrw"; "/dev/sda"];
        ["blockdev_getro"; "/dev/sda"]])],
@@ -1917,7 +1929,7 @@ Sets the block device named C<device> to read-write.
 
 This uses the L<blockdev(8)> command.");
 
-  ("blockdev_getro", (RBool "ro", [Device "device"]), 58, [],
+  ("blockdev_getro", (RBool "ro", [Device "device"], []), 58, [],
    [InitEmpty, Always, TestOutputTrue (
       [["blockdev_setro"; "/dev/sda"];
        ["blockdev_getro"; "/dev/sda"]])],
@@ -1928,7 +1940,7 @@ Returns a boolean indicating if the block device is read-only
 
 This uses the L<blockdev(8)> command.");
 
-  ("blockdev_getss", (RInt "sectorsize", [Device "device"]), 59, [],
+  ("blockdev_getss", (RInt "sectorsize", [Device "device"], []), 59, [],
    [InitEmpty, Always, TestOutputInt (
       [["blockdev_getss"; "/dev/sda"]], 512)],
    "get sectorsize of block device",
@@ -1941,7 +1953,7 @@ for that).
 
 This uses the L<blockdev(8)> command.");
 
-  ("blockdev_getbsz", (RInt "blocksize", [Device "device"]), 60, [],
+  ("blockdev_getbsz", (RInt "blocksize", [Device "device"], []), 60, [],
    [InitEmpty, Always, TestOutputInt (
       [["blockdev_getbsz"; "/dev/sda"]], 4096)],
    "get blocksize of block device",
@@ -1953,7 +1965,7 @@ I<filesystem block size>).
 
 This uses the L<blockdev(8)> command.");
 
-  ("blockdev_setbsz", (RErr, [Device "device"; Int "blocksize"]), 61, [],
+  ("blockdev_setbsz", (RErr, [Device "device"; Int "blocksize"], []), 61, [],
    [], (* XXX test *)
    "set blocksize of block device",
    "\
@@ -1964,7 +1976,7 @@ I<filesystem block size>).
 
 This uses the L<blockdev(8)> command.");
 
-  ("blockdev_getsz", (RInt64 "sizeinsectors", [Device "device"]), 62, [],
+  ("blockdev_getsz", (RInt64 "sizeinsectors", [Device "device"], []), 62, [],
    [InitEmpty, Always, TestOutputInt (
       [["blockdev_getsz"; "/dev/sda"]], 1024000)],
    "get total size of device in 512-byte sectors",
@@ -1978,7 +1990,7 @@ useful I<size in bytes>.
 
 This uses the L<blockdev(8)> command.");
 
-  ("blockdev_getsize64", (RInt64 "sizeinbytes", [Device "device"]), 63, [],
+  ("blockdev_getsize64", (RInt64 "sizeinbytes", [Device "device"], []), 63, [],
    [InitEmpty, Always, TestOutputInt (
       [["blockdev_getsize64"; "/dev/sda"]], 524288000)],
    "get total size of device in bytes",
@@ -1989,7 +2001,7 @@ See also C<guestfs_blockdev_getsz>.
 
 This uses the L<blockdev(8)> command.");
 
-  ("blockdev_flushbufs", (RErr, [Device "device"]), 64, [],
+  ("blockdev_flushbufs", (RErr, [Device "device"], []), 64, [],
    [InitEmpty, Always, TestRun
       [["blockdev_flushbufs"; "/dev/sda"]]],
    "flush device buffers",
@@ -1999,7 +2011,7 @@ with C<device>.
 
 This uses the L<blockdev(8)> command.");
 
-  ("blockdev_rereadpt", (RErr, [Device "device"]), 65, [],
+  ("blockdev_rereadpt", (RErr, [Device "device"], []), 65, [],
    [InitEmpty, Always, TestRun
       [["blockdev_rereadpt"; "/dev/sda"]]],
    "reread partition table",
@@ -2008,7 +2020,7 @@ Reread the partition table on C<device>.
 
 This uses the L<blockdev(8)> command.");
 
-  ("upload", (RErr, [FileIn "filename"; Dev_or_Path "remotefilename"]), 66, [],
+  ("upload", (RErr, [FileIn "filename"; Dev_or_Path "remotefilename"], []), 66, [],
    [InitBasicFS, Always, TestOutput (
       (* Pick a file from cwd which isn't likely to change. *)
       [["upload"; "../COPYING.LIB"; "/COPYING.LIB"];
@@ -2023,7 +2035,7 @@ C<filename> can also be a named pipe.
 
 See also C<guestfs_download>.");
 
-  ("download", (RErr, [Dev_or_Path "remotefilename"; FileOut "filename"]), 67, [Progress],
+  ("download", (RErr, [Dev_or_Path "remotefilename"; FileOut "filename"], []), 67, [Progress],
    [InitBasicFS, Always, TestOutput (
       (* Pick a file from cwd which isn't likely to change. *)
       [["upload"; "../COPYING.LIB"; "/COPYING.LIB"];
@@ -2040,7 +2052,7 @@ C<filename> can also be a named pipe.
 
 See also C<guestfs_upload>, C<guestfs_cat>.");
 
-  ("checksum", (RString "checksum", [String "csumtype"; Pathname "path"]), 68, [],
+  ("checksum", (RString "checksum", [String "csumtype"; Pathname "path"], []), 68, [],
    [InitISOFS, Always, TestOutput (
       [["checksum"; "crc"; "/known-3"]], "2891671662");
     InitISOFS, Always, TestLastFail (
@@ -2107,7 +2119,7 @@ To get the checksum for a device, use C<guestfs_checksum_device>.
 
 To get the checksums for many files, use C<guestfs_checksums_out>.");
 
-  ("tar_in", (RErr, [FileIn "tarfile"; Pathname "directory"]), 69, [],
+  ("tar_in", (RErr, [FileIn "tarfile"; Pathname "directory"], []), 69, [],
    [InitBasicFS, Always, TestOutput (
       [["tar_in"; "../images/helloworld.tar"; "/"];
        ["cat"; "/hello"]], "hello\n")],
@@ -2119,7 +2131,7 @@ I<uncompressed> tar file) into C<directory>.
 To upload a compressed tarball, use C<guestfs_tgz_in>
 or C<guestfs_txz_in>.");
 
-  ("tar_out", (RErr, [String "directory"; FileOut "tarfile"]), 70, [],
+  ("tar_out", (RErr, [String "directory"; FileOut "tarfile"], []), 70, [],
    [],
    "pack directory into tarfile",
    "\
@@ -2129,7 +2141,7 @@ it to local file C<tarfile>.
 To download a compressed tarball, use C<guestfs_tgz_out>
 or C<guestfs_txz_out>.");
 
-  ("tgz_in", (RErr, [FileIn "tarball"; Pathname "directory"]), 71, [],
+  ("tgz_in", (RErr, [FileIn "tarball"; Pathname "directory"], []), 71, [],
    [InitBasicFS, Always, TestOutput (
       [["tgz_in"; "../images/helloworld.tar.gz"; "/"];
        ["cat"; "/hello"]], "hello\n")],
@@ -2140,7 +2152,7 @@ I<gzip compressed> tar file) into C<directory>.
 
 To upload an uncompressed tarball, use C<guestfs_tar_in>.");
 
-  ("tgz_out", (RErr, [Pathname "directory"; FileOut "tarball"]), 72, [],
+  ("tgz_out", (RErr, [Pathname "directory"; FileOut "tarball"], []), 72, [],
    [],
    "pack directory into compressed tarball",
    "\
@@ -2149,7 +2161,7 @@ it to local file C<tarball>.
 
 To download an uncompressed tarball, use C<guestfs_tar_out>.");
 
-  ("mount_ro", (RErr, [Device "device"; String "mountpoint"]), 73, [],
+  ("mount_ro", (RErr, [Device "device"; String "mountpoint"], []), 73, [],
    [InitBasicFS, Always, TestLastFail (
       [["umount"; "/"];
        ["mount_ro"; "/dev/sda1"; "/"];
@@ -2164,7 +2176,7 @@ To download an uncompressed tarball, use C<guestfs_tar_out>.");
 This is the same as the C<guestfs_mount> command, but it
 mounts the filesystem with the read-only (I<-o ro>) flag.");
 
-  ("mount_options", (RErr, [String "options"; Device "device"; String "mountpoint"]), 74, [],
+  ("mount_options", (RErr, [String "options"; Device "device"; String "mountpoint"], []), 74, [],
    [],
    "mount a guest disk with mount options",
    "\
@@ -2176,7 +2188,7 @@ If the C<options> parameter is an empty string, then
 no options are passed (all options default to whatever
 the filesystem uses).");
 
-  ("mount_vfs", (RErr, [String "options"; String "vfstype"; Device "device"; String "mountpoint"]), 75, [],
+  ("mount_vfs", (RErr, [String "options"; String "vfstype"; Device "device"; String "mountpoint"], []), 75, [],
    [],
    "mount a guest disk with mount options and vfstype",
    "\
@@ -2184,7 +2196,7 @@ This is the same as the C<guestfs_mount> command, but it
 allows you to set both the mount options and the vfstype
 as for the L<mount(8)> I<-o> and I<-t> flags.");
 
-  ("debug", (RString "result", [String "subcmd"; StringList "extraargs"]), 76, [],
+  ("debug", (RString "result", [String "subcmd"; StringList "extraargs"], []), 76, [],
    [],
    "debugging and internals",
    "\
@@ -2196,7 +2208,7 @@ There is no comprehensive help for this command.  You have
 to look at the file C<daemon/debug.c> in the libguestfs source
 to find out what you can do.");
 
-  ("lvremove", (RErr, [Device "device"]), 77, [Optional "lvm2"],
+  ("lvremove", (RErr, [Device "device"], []), 77, [Optional "lvm2"],
    [InitEmpty, Always, TestOutputList (
       [["part_disk"; "/dev/sda"; "mbr"];
        ["pvcreate"; "/dev/sda1"];
@@ -2229,7 +2241,7 @@ the path to the LV, such as C</dev/VG/LV>.
 You can also remove all LVs in a volume group by specifying
 the VG name, C</dev/VG>.");
 
-  ("vgremove", (RErr, [String "vgname"]), 78, [Optional "lvm2"],
+  ("vgremove", (RErr, [String "vgname"], []), 78, [Optional "lvm2"],
    [InitEmpty, Always, TestOutputList (
       [["part_disk"; "/dev/sda"; "mbr"];
        ["pvcreate"; "/dev/sda1"];
@@ -2253,7 +2265,7 @@ Remove an LVM volume group C<vgname>, (for example C<VG>).
 This also forcibly removes all logical volumes in the volume
 group (if any).");
 
-  ("pvremove", (RErr, [Device "device"]), 79, [Optional "lvm2"],
+  ("pvremove", (RErr, [Device "device"], []), 79, [Optional "lvm2"],
    [InitEmpty, Always, TestOutputListOfDevices (
       [["part_disk"; "/dev/sda"; "mbr"];
        ["pvcreate"; "/dev/sda1"];
@@ -2290,7 +2302,7 @@ The implementation uses the C<pvremove> command which refuses to
 wipe physical volumes that contain any volume groups, so you have
 to remove those first.");
 
-  ("set_e2label", (RErr, [Device "device"; String "label"]), 80, [],
+  ("set_e2label", (RErr, [Device "device"; String "label"], []), 80, [],
    [InitBasicFS, Always, TestOutput (
       [["set_e2label"; "/dev/sda1"; "testlabel"];
        ["get_e2label"; "/dev/sda1"]], "testlabel")],
@@ -2303,14 +2315,14 @@ C<device> to C<label>.  Filesystem labels are limited to
 You can use either C<guestfs_tune2fs_l> or C<guestfs_get_e2label>
 to return the existing label on a filesystem.");
 
-  ("get_e2label", (RString "label", [Device "device"]), 81, [DeprecatedBy "vfs_label"],
+  ("get_e2label", (RString "label", [Device "device"], []), 81, [DeprecatedBy "vfs_label"],
    [],
    "get the ext2/3/4 filesystem label",
    "\
 This returns the ext2/3/4 filesystem label of the filesystem on
 C<device>.");
 
-  ("set_e2uuid", (RErr, [Device "device"; String "uuid"]), 82, [],
+  ("set_e2uuid", (RErr, [Device "device"; String "uuid"], []), 82, [],
    (let uuid = uuidgen () in
     [InitBasicFS, Always, TestOutput (
        [["set_e2uuid"; "/dev/sda1"; uuid];
@@ -2333,7 +2345,7 @@ L<tune2fs(8)> manpage.
 You can use either C<guestfs_tune2fs_l> or C<guestfs_get_e2uuid>
 to return the existing UUID of a filesystem.");
 
-  ("get_e2uuid", (RString "uuid", [Device "device"]), 83, [DeprecatedBy "vfs_uuid"],
+  ("get_e2uuid", (RString "uuid", [Device "device"], []), 83, [DeprecatedBy "vfs_uuid"],
    (* Regression test for RHBZ#597112. *)
    (let uuid = uuidgen () in
     [InitBasicFS, Always, TestOutput (
@@ -2345,7 +2357,7 @@ to return the existing UUID of a filesystem.");
 This returns the ext2/3/4 filesystem UUID of the filesystem on
 C<device>.");
 
-  ("fsck", (RInt "status", [String "fstype"; Device "device"]), 84, [FishOutput FishOutputHexadecimal],
+  ("fsck", (RInt "status", [String "fstype"; Device "device"], []), 84, [FishOutput FishOutputHexadecimal],
    [InitBasicFS, Always, TestOutputInt (
       [["umount"; "/dev/sda1"];
        ["fsck"; "ext2"; "/dev/sda1"]], 0);
@@ -2383,7 +2395,7 @@ Checking or repairing NTFS volumes is not supported
 
 This command is entirely equivalent to running C<fsck -a -t fstype device>.");
 
-  ("zero", (RErr, [Device "device"]), 85, [Progress],
+  ("zero", (RErr, [Device "device"], []), 85, [Progress],
    [InitBasicFS, Always, TestOutput (
       [["umount"; "/dev/sda1"];
        ["zero"; "/dev/sda1"];
@@ -2398,7 +2410,7 @@ any partition tables, filesystem superblocks and so on.
 
 See also: C<guestfs_zero_device>, C<guestfs_scrub_device>.");
 
-  ("grub_install", (RErr, [Pathname "root"; Device "device"]), 86, [],
+  ("grub_install", (RErr, [Pathname "root"; Device "device"], []), 86, [],
    (* See:
     * https://bugzilla.redhat.com/show_bug.cgi?id=484986
     * https://bugzilla.redhat.com/show_bug.cgi?id=479760
@@ -2424,7 +2436,7 @@ a file containing:
 
 replacing C</dev/vda> with the name of the installation device.");
 
-  ("cp", (RErr, [Pathname "src"; Pathname "dest"]), 87, [],
+  ("cp", (RErr, [Pathname "src"; Pathname "dest"], []), 87, [],
    [InitBasicFS, Always, TestOutput (
       [["write"; "/old"; "file content"];
        ["cp"; "/old"; "/new"];
@@ -2443,7 +2455,7 @@ replacing C</dev/vda> with the name of the installation device.");
 This copies a file from C<src> to C<dest> where C<dest> is
 either a destination filename or destination directory.");
 
-  ("cp_a", (RErr, [Pathname "src"; Pathname "dest"]), 88, [],
+  ("cp_a", (RErr, [Pathname "src"; Pathname "dest"], []), 88, [],
    [InitBasicFS, Always, TestOutput (
       [["mkdir"; "/olddir"];
        ["mkdir"; "/newdir"];
@@ -2455,7 +2467,7 @@ either a destination filename or destination directory.");
 This copies a file or directory from C<src> to C<dest>
 recursively using the C<cp -a> command.");
 
-  ("mv", (RErr, [Pathname "src"; Pathname "dest"]), 89, [],
+  ("mv", (RErr, [Pathname "src"; Pathname "dest"], []), 89, [],
    [InitBasicFS, Always, TestOutput (
       [["write"; "/old"; "file content"];
        ["mv"; "/old"; "/new"];
@@ -2469,7 +2481,7 @@ recursively using the C<cp -a> command.");
 This moves a file from C<src> to C<dest> where C<dest> is
 either a destination filename or destination directory.");
 
-  ("drop_caches", (RErr, [Int "whattodrop"]), 90, [],
+  ("drop_caches", (RErr, [Int "whattodrop"], []), 90, [],
    [InitEmpty, Always, TestRun (
       [["drop_caches"; "3"]])],
    "drop kernel page cache, dentries and inodes",
@@ -2484,7 +2496,7 @@ Setting C<whattodrop> to 3 should drop everything.
 This automatically calls L<sync(2)> before the operation,
 so that the maximum guest memory is freed.");
 
-  ("dmesg", (RString "kmsgs", []), 91, [],
+  ("dmesg", (RString "kmsgs", [], []), 91, [],
    [InitEmpty, Always, TestRun (
       [["dmesg"]])],
    "return kernel messages",
@@ -2498,7 +2510,7 @@ verbose messages with C<guestfs_set_verbose> or by setting
 the environment variable C<LIBGUESTFS_DEBUG=1> before
 running the program.");
 
-  ("ping_daemon", (RErr, []), 92, [],
+  ("ping_daemon", (RErr, [], []), 92, [],
    [InitEmpty, Always, TestRun (
       [["ping_daemon"]])],
    "ping the guest daemon",
@@ -2508,7 +2520,7 @@ the qemu subprocess.  Calling this function checks that the
 daemon responds to the ping message, without affecting the daemon
 or attached block device(s) in any other way.");
 
-  ("equal", (RBool "equality", [Pathname "file1"; Pathname "file2"]), 93, [],
+  ("equal", (RBool "equality", [Pathname "file1"; Pathname "file2"], []), 93, [],
    [InitBasicFS, Always, TestOutputTrue (
       [["write"; "/file1"; "contents of a file"];
        ["cp"; "/file1"; "/file2"];
@@ -2526,7 +2538,7 @@ true if their content is exactly equal, or false otherwise.
 
 The external L<cmp(1)> program is used for the comparison.");
 
-  ("strings", (RStringList "stringsout", [Pathname "path"]), 94, [ProtocolLimitWarning],
+  ("strings", (RStringList "stringsout", [Pathname "path"], []), 94, [ProtocolLimitWarning],
    [InitISOFS, Always, TestOutputList (
       [["strings"; "/known-5"]], ["abcdefghi"; "jklmnopqr"]);
     InitISOFS, Always, TestOutputList (
@@ -2539,7 +2551,7 @@ The external L<cmp(1)> program is used for the comparison.");
 This runs the L<strings(1)> command on a file and returns
 the list of printable strings found.");
 
-  ("strings_e", (RStringList "stringsout", [String "encoding"; Pathname "path"]), 95, [ProtocolLimitWarning],
+  ("strings_e", (RStringList "stringsout", [String "encoding"; Pathname "path"], []), 95, [ProtocolLimitWarning],
    [InitISOFS, Always, TestOutputList (
       [["strings_e"; "b"; "/known-5"]], []);
     InitBasicFS, Always, TestOutputList (
@@ -2586,7 +2598,7 @@ This is useful for examining binaries in Windows guests.
 
 The returned strings are transcoded to UTF-8.");
 
-  ("hexdump", (RString "dump", [Pathname "path"]), 96, [ProtocolLimitWarning],
+  ("hexdump", (RString "dump", [Pathname "path"], []), 96, [ProtocolLimitWarning],
    [InitISOFS, Always, TestOutput (
       [["hexdump"; "/known-4"]], "00000000  61 62 63 0a 64 65 66 0a  67 68 69                 |abc.def.ghi|\n0000000b\n");
     (* Test for RHBZ#501888c2 regression which caused large hexdump
@@ -2602,7 +2614,7 @@ The returned strings are transcoded to UTF-8.");
 This runs C<hexdump -C> on the given C<path>.  The result is
 the human-readable, canonical hex dump of the file.");
 
-  ("zerofree", (RErr, [Device "device"]), 97, [Optional "zerofree"],
+  ("zerofree", (RErr, [Device "device"], []), 97, [Optional "zerofree"],
    [InitNone, Always, TestOutput (
       [["part_disk"; "/dev/sda"; "mbr"];
        ["mkfs"; "ext3"; "/dev/sda1"];
@@ -2625,7 +2637,7 @@ mounted.
 It is possible that using this program can damage the filesystem
 or data on the filesystem.");
 
-  ("pvresize", (RErr, [Device "device"]), 98, [Optional "lvm2"],
+  ("pvresize", (RErr, [Device "device"], []), 98, [Optional "lvm2"],
    [],
    "resize an LVM physical volume",
    "\
@@ -2634,7 +2646,7 @@ volume to match the new size of the underlying device.");
 
   ("sfdisk_N", (RErr, [Device "device"; Int "partnum";
                        Int "cyls"; Int "heads"; Int "sectors";
-                       String "line"]), 99, [DangerWillRobinson],
+                       String "line"], []), 99, [DangerWillRobinson],
    [],
    "modify a single partition on a block device",
    "\
@@ -2646,7 +2658,7 @@ pass C<0> for the cyls/heads/sectors parameters.
 
 See also: C<guestfs_part_add>");
 
-  ("sfdisk_l", (RString "partitions", [Device "device"]), 100, [],
+  ("sfdisk_l", (RString "partitions", [Device "device"], []), 100, [],
    [],
    "display the partition table",
    "\
@@ -2656,7 +2668,7 @@ not intended to be parsed.
 
 See also: C<guestfs_part_list>");
 
-  ("sfdisk_kernel_geometry", (RString "partitions", [Device "device"]), 101, [],
+  ("sfdisk_kernel_geometry", (RString "partitions", [Device "device"], []), 101, [],
    [],
    "display the kernel geometry",
    "\
@@ -2665,7 +2677,7 @@ This displays the kernel's idea of the geometry of C<device>.
 The result is in human-readable format, and not designed to
 be parsed.");
 
-  ("sfdisk_disk_geometry", (RString "partitions", [Device "device"]), 102, [],
+  ("sfdisk_disk_geometry", (RString "partitions", [Device "device"], []), 102, [],
    [],
    "display the disk geometry from the partition table",
    "\
@@ -2677,7 +2689,7 @@ kernel's idea of the geometry (see C<guestfs_sfdisk_kernel_geometry>).
 The result is in human-readable format, and not designed to
 be parsed.");
 
-  ("vg_activate_all", (RErr, [Bool "activate"]), 103, [Optional "lvm2"],
+  ("vg_activate_all", (RErr, [Bool "activate"], []), 103, [Optional "lvm2"],
    [],
    "activate or deactivate all volume groups",
    "\
@@ -2689,7 +2701,7 @@ then those devices disappear.
 
 This command is the same as running C<vgchange -a y|n>");
 
-  ("vg_activate", (RErr, [Bool "activate"; StringList "volgroups"]), 104, [Optional "lvm2"],
+  ("vg_activate", (RErr, [Bool "activate"; StringList "volgroups"], []), 104, [Optional "lvm2"],
    [],
    "activate or deactivate some volume groups",
    "\
@@ -2704,7 +2716,7 @@ This command is the same as running C<vgchange -a y|n volgroups...>
 Note that if C<volgroups> is an empty list then B<all> volume groups
 are activated or deactivated.");
 
-  ("lvresize", (RErr, [Device "device"; Int "mbytes"]), 105, [Optional "lvm2"],
+  ("lvresize", (RErr, [Device "device"; Int "mbytes"], []), 105, [Optional "lvm2"],
    [InitNone, Always, TestOutput (
       [["part_disk"; "/dev/sda"; "mbr"];
        ["pvcreate"; "/dev/sda1"];
@@ -2732,7 +2744,7 @@ This resizes (expands or shrinks) an existing LVM logical
 volume to C<mbytes>.  When reducing, data in the reduced part
 is lost.");
 
-  ("resize2fs", (RErr, [Device "device"]), 106, [],
+  ("resize2fs", (RErr, [Device "device"], []), 106, [],
    [], (* lvresize tests this *)
    "resize an ext2, ext3 or ext4 filesystem",
    "\
@@ -2745,7 +2757,7 @@ C<resize2fs> sometimes gives an error about this and sometimes not.
 In any case, it is always safe to call C<guestfs_e2fsck_f> before
 calling this function.");
 
-  ("find", (RStringList "names", [Pathname "directory"]), 107, [ProtocolLimitWarning],
+  ("find", (RStringList "names", [Pathname "directory"], []), 107, [ProtocolLimitWarning],
    [InitBasicFS, Always, TestOutputList (
       [["find"; "/"]], ["lost+found"]);
     InitBasicFS, Always, TestOutputList (
@@ -2786,7 +2798,7 @@ The returned list is sorted.
 
 See also C<guestfs_find0>.");
 
-  ("e2fsck_f", (RErr, [Device "device"]), 108, [],
+  ("e2fsck_f", (RErr, [Device "device"], []), 108, [],
    [], (* lvresize tests this *)
    "check an ext2/ext3 filesystem",
    "\
@@ -2797,14 +2809,14 @@ even if the filesystem appears to be clean (C<-f>).
 This command is only needed because of C<guestfs_resize2fs>
 (q.v.).  Normally you should use C<guestfs_fsck>.");
 
-  ("sleep", (RErr, [Int "secs"]), 109, [],
+  ("sleep", (RErr, [Int "secs"], []), 109, [],
    [InitNone, Always, TestRun (
       [["sleep"; "1"]])],
    "sleep for some seconds",
    "\
 Sleep for C<secs> seconds.");
 
-  ("ntfs_3g_probe", (RInt "status", [Bool "rw"; Device "device"]), 110, [Optional "ntfs3g"],
+  ("ntfs_3g_probe", (RInt "status", [Bool "rw"; Device "device"], []), 110, [Optional "ntfs3g"],
    [InitNone, Always, TestOutputInt (
       [["part_disk"; "/dev/sda"; "mbr"];
        ["mkfs"; "ntfs"; "/dev/sda1"];
@@ -2827,7 +2839,7 @@ The return value is an integer which C<0> if the operation
 would succeed, or some non-zero value documented in the
 L<ntfs-3g.probe(8)> manual page.");
 
-  ("sh", (RString "output", [String "command"]), 111, [],
+  ("sh", (RString "output", [String "command"], []), 111, [],
    [], (* XXX needs tests *)
    "run a command via the shell",
    "\
@@ -2844,7 +2856,7 @@ and so on.
 
 All the provisos about C<guestfs_command> apply to this call.");
 
-  ("sh_lines", (RStringList "lines", [String "command"]), 112, [],
+  ("sh_lines", (RStringList "lines", [String "command"], []), 112, [],
    [], (* XXX needs tests *)
    "run a command via the shell returning lines",
    "\
@@ -2853,7 +2865,7 @@ into a list of lines.
 
 See also: C<guestfs_command_lines>");
 
-  ("glob_expand", (RStringList "paths", [Pathname "pattern"]), 113, [],
+  ("glob_expand", (RStringList "paths", [Pathname "pattern"], []), 113, [],
    (* Use Pathname here, and hence ABS_PATH (pattern,... in generated
     * code in stubs.c, since all valid glob patterns must start with "/".
     * There is no concept of "cwd" in libguestfs, hence no "."-relative names.
@@ -2886,7 +2898,7 @@ It is just a wrapper around the C L<glob(3)> function
 with flags C<GLOB_MARK|GLOB_BRACE>.
 See that manual page for more details.");
 
-  ("scrub_device", (RErr, [Device "device"]), 114, [DangerWillRobinson; Optional "scrub"],
+  ("scrub_device", (RErr, [Device "device"], []), 114, [DangerWillRobinson; Optional "scrub"],
    [InitNone, Always, TestRun (	(* use /dev/sdc because it's smaller *)
       [["scrub_device"; "/dev/sdc"]])],
    "scrub (securely wipe) a device",
@@ -2897,7 +2909,7 @@ more difficult.
 It is an interface to the L<scrub(1)> program.  See that
 manual page for more details.");
 
-  ("scrub_file", (RErr, [Pathname "file"]), 115, [Optional "scrub"],
+  ("scrub_file", (RErr, [Pathname "file"], []), 115, [Optional "scrub"],
    [InitBasicFS, Always, TestRun (
       [["write"; "/file"; "content"];
        ["scrub_file"; "/file"]])],
@@ -2911,7 +2923,7 @@ The file is I<removed> after scrubbing.
 It is an interface to the L<scrub(1)> program.  See that
 manual page for more details.");
 
-  ("scrub_freespace", (RErr, [Pathname "dir"]), 116, [Optional "scrub"],
+  ("scrub_freespace", (RErr, [Pathname "dir"], []), 116, [Optional "scrub"],
    [], (* XXX needs testing *)
    "scrub (securely wipe) free space",
    "\
@@ -2924,7 +2936,7 @@ containing C<dir>.
 It is an interface to the L<scrub(1)> program.  See that
 manual page for more details.");
 
-  ("mkdtemp", (RString "dir", [Pathname "template"]), 117, [],
+  ("mkdtemp", (RString "dir", [Pathname "template"], []), 117, [],
    [InitBasicFS, Always, TestRun (
       [["mkdir"; "/tmp"];
        ["mkdtemp"; "/tmp/tmpXXXXXX"]])],
@@ -2949,7 +2961,7 @@ directory and its contents after use.
 
 See also: L<mkdtemp(3)>");
 
-  ("wc_l", (RInt "lines", [Pathname "path"]), 118, [],
+  ("wc_l", (RInt "lines", [Pathname "path"], []), 118, [],
    [InitISOFS, Always, TestOutputInt (
       [["wc_l"; "/10klines"]], 10000);
     (* Test for RHBZ#579608, absolute symbolic links. *)
@@ -2960,7 +2972,7 @@ See also: L<mkdtemp(3)>");
 This command counts the lines in a file, using the
 C<wc -l> external command.");
 
-  ("wc_w", (RInt "words", [Pathname "path"]), 119, [],
+  ("wc_w", (RInt "words", [Pathname "path"], []), 119, [],
    [InitISOFS, Always, TestOutputInt (
       [["wc_w"; "/10klines"]], 10000)],
    "count words in a file",
@@ -2968,7 +2980,7 @@ C<wc -l> external command.");
 This command counts the words in a file, using the
 C<wc -w> external command.");
 
-  ("wc_c", (RInt "chars", [Pathname "path"]), 120, [],
+  ("wc_c", (RInt "chars", [Pathname "path"], []), 120, [],
    [InitISOFS, Always, TestOutputInt (
       [["wc_c"; "/100kallspaces"]], 102400)],
    "count characters in a file",
@@ -2976,7 +2988,7 @@ C<wc -w> external command.");
 This command counts the characters in a file, using the
 C<wc -c> external command.");
 
-  ("head", (RStringList "lines", [Pathname "path"]), 121, [ProtocolLimitWarning],
+  ("head", (RStringList "lines", [Pathname "path"], []), 121, [ProtocolLimitWarning],
    [InitISOFS, Always, TestOutputList (
       [["head"; "/10klines"]], ["0abcdefghijklmnopqrstuvwxyz";"1abcdefghijklmnopqrstuvwxyz";"2abcdefghijklmnopqrstuvwxyz";"3abcdefghijklmnopqrstuvwxyz";"4abcdefghijklmnopqrstuvwxyz";"5abcdefghijklmnopqrstuvwxyz";"6abcdefghijklmnopqrstuvwxyz";"7abcdefghijklmnopqrstuvwxyz";"8abcdefghijklmnopqrstuvwxyz";"9abcdefghijklmnopqrstuvwxyz"]);
     (* Test for RHBZ#579608, absolute symbolic links. *)
@@ -2987,7 +2999,7 @@ C<wc -c> external command.");
 This command returns up to the first 10 lines of a file as
 a list of strings.");
 
-  ("head_n", (RStringList "lines", [Int "nrlines"; Pathname "path"]), 122, [ProtocolLimitWarning],
+  ("head_n", (RStringList "lines", [Int "nrlines"; Pathname "path"], []), 122, [ProtocolLimitWarning],
    [InitISOFS, Always, TestOutputList (
       [["head_n"; "3"; "/10klines"]], ["0abcdefghijklmnopqrstuvwxyz";"1abcdefghijklmnopqrstuvwxyz";"2abcdefghijklmnopqrstuvwxyz"]);
     InitISOFS, Always, TestOutputList (
@@ -3004,7 +3016,7 @@ from the file C<path>, excluding the last C<nrlines> lines.
 
 If the parameter C<nrlines> is zero, this returns an empty list.");
 
-  ("tail", (RStringList "lines", [Pathname "path"]), 123, [ProtocolLimitWarning],
+  ("tail", (RStringList "lines", [Pathname "path"], []), 123, [ProtocolLimitWarning],
    [InitISOFS, Always, TestOutputList (
       [["tail"; "/10klines"]], ["9990abcdefghijklmnopqrstuvwxyz";"9991abcdefghijklmnopqrstuvwxyz";"9992abcdefghijklmnopqrstuvwxyz";"9993abcdefghijklmnopqrstuvwxyz";"9994abcdefghijklmnopqrstuvwxyz";"9995abcdefghijklmnopqrstuvwxyz";"9996abcdefghijklmnopqrstuvwxyz";"9997abcdefghijklmnopqrstuvwxyz";"9998abcdefghijklmnopqrstuvwxyz";"9999abcdefghijklmnopqrstuvwxyz"])],
    "return last 10 lines of a file",
@@ -3012,7 +3024,7 @@ If the parameter C<nrlines> is zero, this returns an empty list.");
 This command returns up to the last 10 lines of a file as
 a list of strings.");
 
-  ("tail_n", (RStringList "lines", [Int "nrlines"; Pathname "path"]), 124, [ProtocolLimitWarning],
+  ("tail_n", (RStringList "lines", [Int "nrlines"; Pathname "path"], []), 124, [ProtocolLimitWarning],
    [InitISOFS, Always, TestOutputList (
       [["tail_n"; "3"; "/10klines"]], ["9997abcdefghijklmnopqrstuvwxyz";"9998abcdefghijklmnopqrstuvwxyz";"9999abcdefghijklmnopqrstuvwxyz"]);
     InitISOFS, Always, TestOutputList (
@@ -3029,7 +3041,7 @@ from the file C<path>, starting with the C<-nrlines>th line.
 
 If the parameter C<nrlines> is zero, this returns an empty list.");
 
-  ("df", (RString "output", []), 125, [],
+  ("df", (RString "output", [], []), 125, [],
    [], (* XXX Tricky to test because it depends on the exact format
         * of the 'df' command and other imponderables.
         *)
@@ -3041,7 +3053,7 @@ This command is mostly useful for interactive sessions.  It
 is I<not> intended that you try to parse the output string.
 Use C<statvfs> from programs.");
 
-  ("df_h", (RString "output", []), 126, [],
+  ("df_h", (RString "output", [], []), 126, [],
    [], (* XXX Tricky to test because it depends on the exact format
         * of the 'df' command and other imponderables.
         *)
@@ -3054,7 +3066,7 @@ This command is mostly useful for interactive sessions.  It
 is I<not> intended that you try to parse the output string.
 Use C<statvfs> from programs.");
 
-  ("du", (RInt64 "sizekb", [Pathname "path"]), 127, [],
+  ("du", (RInt64 "sizekb", [Pathname "path"], []), 127, [],
    [InitISOFS, Always, TestOutputInt (
       [["du"; "/directory"]], 2 (* ISO fs blocksize is 2K *))],
    "estimate file space usage",
@@ -3069,7 +3081,7 @@ subdirectories (recursively).
 The result is the estimated size in I<kilobytes>
 (ie. units of 1024 bytes).");
 
-  ("initrd_list", (RStringList "filenames", [Pathname "path"]), 128, [],
+  ("initrd_list", (RStringList "filenames", [Pathname "path"], []), 128, [],
    [InitISOFS, Always, TestOutputList (
       [["initrd_list"; "/initrd"]], ["empty";"known-1";"known-2";"known-3";"known-4"; "known-5"])],
    "list files in an initrd",
@@ -3084,7 +3096,7 @@ Old Linux kernels (2.4 and earlier) used a compressed ext2
 filesystem as initrd.  We I<only> support the newer initramfs
 format (compressed cpio files).");
 
-  ("mount_loop", (RErr, [Pathname "file"; Pathname "mountpoint"]), 129, [],
+  ("mount_loop", (RErr, [Pathname "file"; Pathname "mountpoint"], []), 129, [],
    [],
    "mount a file using the loop device",
    "\
@@ -3092,7 +3104,7 @@ This command lets you mount C<file> (a filesystem image
 in a file) on a mount point.  It is entirely equivalent to
 the command C<mount -o loop file mountpoint>.");
 
-  ("mkswap", (RErr, [Device "device"]), 130, [],
+  ("mkswap", (RErr, [Device "device"], []), 130, [],
    [InitEmpty, Always, TestRun (
       [["part_disk"; "/dev/sda"; "mbr"];
        ["mkswap"; "/dev/sda1"]])],
@@ -3100,7 +3112,7 @@ the command C<mount -o loop file mountpoint>.");
    "\
 Create a swap partition on C<device>.");
 
-  ("mkswap_L", (RErr, [String "label"; Device "device"]), 131, [],
+  ("mkswap_L", (RErr, [String "label"; Device "device"], []), 131, [],
    [InitEmpty, Always, TestRun (
       [["part_disk"; "/dev/sda"; "mbr"];
        ["mkswap_L"; "hello"; "/dev/sda1"]])],
@@ -3112,7 +3124,7 @@ Note that you cannot attach a swap label to a block device
 (eg. C</dev/sda>), just to a partition.  This appears to be
 a limitation of the kernel or swap tools.");
 
-  ("mkswap_U", (RErr, [String "uuid"; Device "device"]), 132, [Optional "linuxfsuuid"],
+  ("mkswap_U", (RErr, [String "uuid"; Device "device"], []), 132, [Optional "linuxfsuuid"],
    (let uuid = uuidgen () in
     [InitEmpty, Always, TestRun (
        [["part_disk"; "/dev/sda"; "mbr"];
@@ -3121,7 +3133,7 @@ a limitation of the kernel or swap tools.");
    "\
 Create a swap partition on C<device> with UUID C<uuid>.");
 
-  ("mknod", (RErr, [Int "mode"; Int "devmajor"; Int "devminor"; Pathname "path"]), 133, [Optional "mknod"],
+  ("mknod", (RErr, [Int "mode"; Int "devmajor"; Int "devminor"; Pathname "path"], []), 133, [Optional "mknod"],
    [InitBasicFS, Always, TestOutputStruct (
       [["mknod"; "0o10777"; "0"; "0"; "/node"];
        (* NB: default umask 022 means 0777 -> 0755 in these tests *)
@@ -3149,7 +3161,7 @@ in the appropriate constant for you.
 
 The mode actually set is affected by the umask.");
 
-  ("mkfifo", (RErr, [Int "mode"; Pathname "path"]), 134, [Optional "mknod"],
+  ("mkfifo", (RErr, [Int "mode"; Pathname "path"], []), 134, [Optional "mknod"],
    [InitBasicFS, Always, TestOutputStruct (
       [["mkfifo"; "0o777"; "/node"];
        ["stat"; "/node"]], [CompareWithInt ("mode", 0o10755)])],
@@ -3161,7 +3173,7 @@ C<guestfs_mknod>.
 
 The mode actually set is affected by the umask.");
 
-  ("mknod_b", (RErr, [Int "mode"; Int "devmajor"; Int "devminor"; Pathname "path"]), 135, [Optional "mknod"],
+  ("mknod_b", (RErr, [Int "mode"; Int "devmajor"; Int "devminor"; Pathname "path"], []), 135, [Optional "mknod"],
    [InitBasicFS, Always, TestOutputStruct (
       [["mknod_b"; "0o777"; "99"; "66"; "/node"];
        ["stat"; "/node"]], [CompareWithInt ("mode", 0o60755)])],
@@ -3173,7 +3185,7 @@ It is just a convenient wrapper around C<guestfs_mknod>.
 
 The mode actually set is affected by the umask.");
 
-  ("mknod_c", (RErr, [Int "mode"; Int "devmajor"; Int "devminor"; Pathname "path"]), 136, [Optional "mknod"],
+  ("mknod_c", (RErr, [Int "mode"; Int "devmajor"; Int "devminor"; Pathname "path"], []), 136, [Optional "mknod"],
    [InitBasicFS, Always, TestOutputStruct (
       [["mknod_c"; "0o777"; "99"; "66"; "/node"];
        ["stat"; "/node"]], [CompareWithInt ("mode", 0o20755)])],
@@ -3185,7 +3197,7 @@ It is just a convenient wrapper around C<guestfs_mknod>.
 
 The mode actually set is affected by the umask.");
 
-  ("umask", (RInt "oldmask", [Int "mask"]), 137, [FishOutput FishOutputOctal],
+  ("umask", (RInt "oldmask", [Int "mask"], []), 137, [FishOutput FishOutputOctal],
    [InitEmpty, Always, TestOutputInt (
       [["umask"; "0o22"]], 0o22)],
    "set file mode creation mask (umask)",
@@ -3207,7 +3219,7 @@ L<umask(2)>, C<guestfs_mknod>, C<guestfs_mkdir>.
 
 This call returns the previous umask.");
 
-  ("readdir", (RStructList ("entries", "dirent"), [Pathname "dir"]), 138, [],
+  ("readdir", (RStructList ("entries", "dirent"), [Pathname "dir"], []), 138, [],
    [],
    "read directories entries",
    "\
@@ -3265,7 +3277,7 @@ This function is primarily intended for use by programs.  To
 get a simple list of names, use C<guestfs_ls>.  To get a printable
 directory for human consumption, use C<guestfs_ll>.");
 
-  ("sfdiskM", (RErr, [Device "device"; StringList "lines"]), 139, [DangerWillRobinson],
+  ("sfdiskM", (RErr, [Device "device"; StringList "lines"], []), 139, [DangerWillRobinson],
    [],
    "create partitions on a block device",
    "\
@@ -3278,7 +3290,7 @@ were rarely if ever used anyway.
 See also: C<guestfs_sfdisk>, the L<sfdisk(8)> manpage
 and C<guestfs_part_disk>");
 
-  ("zfile", (RString "description", [String "meth"; Pathname "path"]), 140, [DeprecatedBy "file"],
+  ("zfile", (RString "description", [String "meth"; Pathname "path"], []), 140, [DeprecatedBy "file"],
    [],
    "determine file type inside a compressed file",
    "\
@@ -3290,7 +3302,7 @@ C<method> must be one of C<gzip>, C<compress> or C<bzip2>.
 Since 1.0.63, use C<guestfs_file> instead which can now
 process compressed files.");
 
-  ("getxattrs", (RStructList ("xattrs", "xattr"), [Pathname "path"]), 141, [Optional "linuxxattrs"],
+  ("getxattrs", (RStructList ("xattrs", "xattr"), [Pathname "path"], []), 141, [Optional "linuxxattrs"],
    [],
    "list extended attributes of a file or directory",
    "\
@@ -3302,7 +3314,7 @@ L<listxattr(2)> and L<getxattr(2)> calls.
 
 See also: C<guestfs_lgetxattrs>, L<attr(5)>.");
 
-  ("lgetxattrs", (RStructList ("xattrs", "xattr"), [Pathname "path"]), 142, [Optional "linuxxattrs"],
+  ("lgetxattrs", (RStructList ("xattrs", "xattr"), [Pathname "path"], []), 142, [Optional "linuxxattrs"],
    [],
    "list extended attributes of a file or directory",
    "\
@@ -3312,7 +3324,7 @@ of the link itself.");
 
   ("setxattr", (RErr, [String "xattr";
                        String "val"; Int "vallen"; (* will be BufferIn *)
-                       Pathname "path"]), 143, [Optional "linuxxattrs"],
+                       Pathname "path"], []), 143, [Optional "linuxxattrs"],
    [],
    "set extended attribute of a file or directory",
    "\
@@ -3324,7 +3336,7 @@ See also: C<guestfs_lsetxattr>, L<attr(5)>.");
 
   ("lsetxattr", (RErr, [String "xattr";
                         String "val"; Int "vallen"; (* will be BufferIn *)
-                        Pathname "path"]), 144, [Optional "linuxxattrs"],
+                        Pathname "path"], []), 144, [Optional "linuxxattrs"],
    [],
    "set extended attribute of a file or directory",
    "\
@@ -3332,7 +3344,7 @@ This is the same as C<guestfs_setxattr>, but if C<path>
 is a symbolic link, then it sets an extended attribute
 of the link itself.");
 
-  ("removexattr", (RErr, [String "xattr"; Pathname "path"]), 145, [Optional "linuxxattrs"],
+  ("removexattr", (RErr, [String "xattr"; Pathname "path"], []), 145, [Optional "linuxxattrs"],
    [],
    "remove extended attribute of a file or directory",
    "\
@@ -3341,7 +3353,7 @@ of the file C<path>.
 
 See also: C<guestfs_lremovexattr>, L<attr(5)>.");
 
-  ("lremovexattr", (RErr, [String "xattr"; Pathname "path"]), 146, [Optional "linuxxattrs"],
+  ("lremovexattr", (RErr, [String "xattr"; Pathname "path"], []), 146, [Optional "linuxxattrs"],
    [],
    "remove extended attribute of a file or directory",
    "\
@@ -3349,7 +3361,7 @@ This is the same as C<guestfs_removexattr>, but if C<path>
 is a symbolic link, then it removes an extended attribute
 of the link itself.");
 
-  ("mountpoints", (RHashtable "mps", []), 147, [],
+  ("mountpoints", (RHashtable "mps", [], []), 147, [],
    [],
    "show mountpoints",
    "\
@@ -3357,7 +3369,7 @@ This call is similar to C<guestfs_mounts>.  That call returns
 a list of devices.  This one returns a hash table (map) of
 device name to directory where the device is mounted.");
 
-  ("mkmountpoint", (RErr, [String "exemptpath"]), 148, [],
+  ("mkmountpoint", (RErr, [String "exemptpath"], []), 148, [],
    (* This is a special case: while you would expect a parameter
     * of type "Pathname", that doesn't work, because it implies
     * NEED_ROOT in the generated calling code in stubs.c, and
@@ -3390,7 +3402,7 @@ in guestfish:
 
 The inner filesystem is now unpacked under the /ext3 mountpoint.");
 
-  ("rmmountpoint", (RErr, [String "exemptpath"]), 149, [],
+  ("rmmountpoint", (RErr, [String "exemptpath"], []), 149, [],
    [],
    "remove a mountpoint",
    "\
@@ -3398,7 +3410,7 @@ This calls removes a mountpoint that was previously created
 with C<guestfs_mkmountpoint>.  See C<guestfs_mkmountpoint>
 for full details.");
 
-  ("read_file", (RBufferOut "content", [Pathname "path"]), 150, [ProtocolLimitWarning],
+  ("read_file", (RBufferOut "content", [Pathname "path"], []), 150, [ProtocolLimitWarning],
    [InitISOFS, Always, TestOutputBuffer (
       [["read_file"; "/known-4"]], "abc\ndef\nghi");
     (* Test various near large, large and too large files (RHBZ#589039). *)
@@ -3424,7 +3436,7 @@ handle files that contain embedded ASCII NUL characters.
 However unlike C<guestfs_download>, this function is limited
 in the total size of file that can be handled.");
 
-  ("grep", (RStringList "lines", [String "regex"; Pathname "path"]), 151, [ProtocolLimitWarning],
+  ("grep", (RStringList "lines", [String "regex"; Pathname "path"], []), 151, [ProtocolLimitWarning],
    [InitISOFS, Always, TestOutputList (
       [["grep"; "abc"; "/test-grep.txt"]], ["abc"; "abc123"]);
     InitISOFS, Always, TestOutputList (
@@ -3437,7 +3449,7 @@ in the total size of file that can be handled.");
 This calls the external C<grep> program and returns the
 matching lines.");
 
-  ("egrep", (RStringList "lines", [String "regex"; Pathname "path"]), 152, [ProtocolLimitWarning],
+  ("egrep", (RStringList "lines", [String "regex"; Pathname "path"], []), 152, [ProtocolLimitWarning],
    [InitISOFS, Always, TestOutputList (
       [["egrep"; "abc"; "/test-grep.txt"]], ["abc"; "abc123"])],
    "return lines matching a pattern",
@@ -3445,7 +3457,7 @@ matching lines.");
 This calls the external C<egrep> program and returns the
 matching lines.");
 
-  ("fgrep", (RStringList "lines", [String "pattern"; Pathname "path"]), 153, [ProtocolLimitWarning],
+  ("fgrep", (RStringList "lines", [String "pattern"; Pathname "path"], []), 153, [ProtocolLimitWarning],
    [InitISOFS, Always, TestOutputList (
       [["fgrep"; "abc"; "/test-grep.txt"]], ["abc"; "abc123"])],
    "return lines matching a pattern",
@@ -3453,7 +3465,7 @@ matching lines.");
 This calls the external C<fgrep> program and returns the
 matching lines.");
 
-  ("grepi", (RStringList "lines", [String "regex"; Pathname "path"]), 154, [ProtocolLimitWarning],
+  ("grepi", (RStringList "lines", [String "regex"; Pathname "path"], []), 154, [ProtocolLimitWarning],
    [InitISOFS, Always, TestOutputList (
       [["grepi"; "abc"; "/test-grep.txt"]], ["abc"; "abc123"; "ABC"])],
    "return lines matching a pattern",
@@ -3461,7 +3473,7 @@ matching lines.");
 This calls the external C<grep -i> program and returns the
 matching lines.");
 
-  ("egrepi", (RStringList "lines", [String "regex"; Pathname "path"]), 155, [ProtocolLimitWarning],
+  ("egrepi", (RStringList "lines", [String "regex"; Pathname "path"], []), 155, [ProtocolLimitWarning],
    [InitISOFS, Always, TestOutputList (
       [["egrepi"; "abc"; "/test-grep.txt"]], ["abc"; "abc123"; "ABC"])],
    "return lines matching a pattern",
@@ -3469,7 +3481,7 @@ matching lines.");
 This calls the external C<egrep -i> program and returns the
 matching lines.");
 
-  ("fgrepi", (RStringList "lines", [String "pattern"; Pathname "path"]), 156, [ProtocolLimitWarning],
+  ("fgrepi", (RStringList "lines", [String "pattern"; Pathname "path"], []), 156, [ProtocolLimitWarning],
    [InitISOFS, Always, TestOutputList (
       [["fgrepi"; "abc"; "/test-grep.txt"]], ["abc"; "abc123"; "ABC"])],
    "return lines matching a pattern",
@@ -3477,7 +3489,7 @@ matching lines.");
 This calls the external C<fgrep -i> program and returns the
 matching lines.");
 
-  ("zgrep", (RStringList "lines", [String "regex"; Pathname "path"]), 157, [ProtocolLimitWarning],
+  ("zgrep", (RStringList "lines", [String "regex"; Pathname "path"], []), 157, [ProtocolLimitWarning],
    [InitISOFS, Always, TestOutputList (
       [["zgrep"; "abc"; "/test-grep.txt.gz"]], ["abc"; "abc123"])],
    "return lines matching a pattern",
@@ -3485,7 +3497,7 @@ matching lines.");
 This calls the external C<zgrep> program and returns the
 matching lines.");
 
-  ("zegrep", (RStringList "lines", [String "regex"; Pathname "path"]), 158, [ProtocolLimitWarning],
+  ("zegrep", (RStringList "lines", [String "regex"; Pathname "path"], []), 158, [ProtocolLimitWarning],
    [InitISOFS, Always, TestOutputList (
       [["zegrep"; "abc"; "/test-grep.txt.gz"]], ["abc"; "abc123"])],
    "return lines matching a pattern",
@@ -3493,7 +3505,7 @@ matching lines.");
 This calls the external C<zegrep> program and returns the
 matching lines.");
 
-  ("zfgrep", (RStringList "lines", [String "pattern"; Pathname "path"]), 159, [ProtocolLimitWarning],
+  ("zfgrep", (RStringList "lines", [String "pattern"; Pathname "path"], []), 159, [ProtocolLimitWarning],
    [InitISOFS, Always, TestOutputList (
       [["zfgrep"; "abc"; "/test-grep.txt.gz"]], ["abc"; "abc123"])],
    "return lines matching a pattern",
@@ -3501,7 +3513,7 @@ matching lines.");
 This calls the external C<zfgrep> program and returns the
 matching lines.");
 
-  ("zgrepi", (RStringList "lines", [String "regex"; Pathname "path"]), 160, [ProtocolLimitWarning],
+  ("zgrepi", (RStringList "lines", [String "regex"; Pathname "path"], []), 160, [ProtocolLimitWarning],
    [InitISOFS, Always, TestOutputList (
       [["zgrepi"; "abc"; "/test-grep.txt.gz"]], ["abc"; "abc123"; "ABC"])],
    "return lines matching a pattern",
@@ -3509,7 +3521,7 @@ matching lines.");
 This calls the external C<zgrep -i> program and returns the
 matching lines.");
 
-  ("zegrepi", (RStringList "lines", [String "regex"; Pathname "path"]), 161, [ProtocolLimitWarning],
+  ("zegrepi", (RStringList "lines", [String "regex"; Pathname "path"], []), 161, [ProtocolLimitWarning],
    [InitISOFS, Always, TestOutputList (
       [["zegrepi"; "abc"; "/test-grep.txt.gz"]], ["abc"; "abc123"; "ABC"])],
    "return lines matching a pattern",
@@ -3517,7 +3529,7 @@ matching lines.");
 This calls the external C<zegrep -i> program and returns the
 matching lines.");
 
-  ("zfgrepi", (RStringList "lines", [String "pattern"; Pathname "path"]), 162, [ProtocolLimitWarning],
+  ("zfgrepi", (RStringList "lines", [String "pattern"; Pathname "path"], []), 162, [ProtocolLimitWarning],
    [InitISOFS, Always, TestOutputList (
       [["zfgrepi"; "abc"; "/test-grep.txt.gz"]], ["abc"; "abc123"; "ABC"])],
    "return lines matching a pattern",
@@ -3525,7 +3537,7 @@ matching lines.");
 This calls the external C<zfgrep -i> program and returns the
 matching lines.");
 
-  ("realpath", (RString "rpath", [Pathname "path"]), 163, [Optional "realpath"],
+  ("realpath", (RString "rpath", [Pathname "path"], []), 163, [Optional "realpath"],
    [InitISOFS, Always, TestOutput (
       [["realpath"; "/../directory"]], "/directory")],
    "canonicalized absolute pathname",
@@ -3533,7 +3545,7 @@ matching lines.");
 Return the canonicalized absolute pathname of C<path>.  The
 returned path has no C<.>, C<..> or symbolic link path elements.");
 
-  ("ln", (RErr, [String "target"; Pathname "linkname"]), 164, [],
+  ("ln", (RErr, [String "target"; Pathname "linkname"], []), 164, [],
    [InitBasicFS, Always, TestOutputStruct (
       [["touch"; "/a"];
        ["ln"; "/a"; "/b"];
@@ -3542,7 +3554,7 @@ returned path has no C<.>, C<..> or symbolic link path elements.");
    "\
 This command creates a hard link using the C<ln> command.");
 
-  ("ln_f", (RErr, [String "target"; Pathname "linkname"]), 165, [],
+  ("ln_f", (RErr, [String "target"; Pathname "linkname"], []), 165, [],
    [InitBasicFS, Always, TestOutputStruct (
       [["touch"; "/a"];
        ["touch"; "/b"];
@@ -3553,7 +3565,7 @@ This command creates a hard link using the C<ln> command.");
 This command creates a hard link using the C<ln -f> command.
 The C<-f> option removes the link (C<linkname>) if it exists already.");
 
-  ("ln_s", (RErr, [String "target"; Pathname "linkname"]), 166, [],
+  ("ln_s", (RErr, [String "target"; Pathname "linkname"], []), 166, [],
    [InitBasicFS, Always, TestOutputStruct (
       [["touch"; "/a"];
        ["ln_s"; "a"; "/b"];
@@ -3562,7 +3574,7 @@ The C<-f> option removes the link (C<linkname>) if it exists already.");
    "\
 This command creates a symbolic link using the C<ln -s> command.");
 
-  ("ln_sf", (RErr, [String "target"; Pathname "linkname"]), 167, [],
+  ("ln_sf", (RErr, [String "target"; Pathname "linkname"], []), 167, [],
    [InitBasicFS, Always, TestOutput (
       [["mkdir_p"; "/a/b"];
        ["touch"; "/a/b/c"];
@@ -3573,13 +3585,13 @@ This command creates a symbolic link using the C<ln -s> command.");
 This command creates a symbolic link using the C<ln -sf> command,
 The C<-f> option removes the link (C<linkname>) if it exists already.");
 
-  ("readlink", (RString "link", [Pathname "path"]), 168, [],
+  ("readlink", (RString "link", [Pathname "path"], []), 168, [],
    [] (* XXX tested above *),
    "read the target of a symbolic link",
    "\
 This command reads the target of a symbolic link.");
 
-  ("fallocate", (RErr, [Pathname "path"; Int "len"]), 169, [DeprecatedBy "fallocate64"],
+  ("fallocate", (RErr, [Pathname "path"; Int "len"], []), 169, [DeprecatedBy "fallocate64"],
    [InitBasicFS, Always, TestOutputStruct (
       [["fallocate"; "/a"; "1000000"];
        ["stat"; "/a"]], [CompareWithInt ("size", 1_000_000)])],
@@ -3593,7 +3605,7 @@ Do not confuse this with the guestfish-specific
 C<alloc> command which allocates a file in the host and
 attaches it as a device.");
 
-  ("swapon_device", (RErr, [Device "device"]), 170, [],
+  ("swapon_device", (RErr, [Device "device"], []), 170, [],
    [InitPartition, Always, TestRun (
       [["mkswap"; "/dev/sda1"];
        ["swapon_device"; "/dev/sda1"];
@@ -3612,7 +3624,7 @@ the guest doesn't want you to trash.  You also risk leaking
 information about the host to the guest this way.  Instead,
 attach a new host device to the guest and swap on that.");
 
-  ("swapoff_device", (RErr, [Device "device"]), 171, [],
+  ("swapoff_device", (RErr, [Device "device"], []), 171, [],
    [], (* XXX tested by swapon_device *)
    "disable swap on device",
    "\
@@ -3620,7 +3632,7 @@ This command disables the libguestfs appliance swap
 device or partition named C<device>.
 See C<guestfs_swapon_device>.");
 
-  ("swapon_file", (RErr, [Pathname "file"]), 172, [],
+  ("swapon_file", (RErr, [Pathname "file"], []), 172, [],
    [InitBasicFS, Always, TestRun (
       [["fallocate"; "/swap"; "8388608"];
        ["mkswap_file"; "/swap"];
@@ -3631,13 +3643,13 @@ See C<guestfs_swapon_device>.");
 This command enables swap to a file.
 See C<guestfs_swapon_device> for other notes.");
 
-  ("swapoff_file", (RErr, [Pathname "file"]), 173, [],
+  ("swapoff_file", (RErr, [Pathname "file"], []), 173, [],
    [], (* XXX tested by swapon_file *)
    "disable swap on file",
    "\
 This command disables the libguestfs appliance swap on file.");
 
-  ("swapon_label", (RErr, [String "label"]), 174, [],
+  ("swapon_label", (RErr, [String "label"], []), 174, [],
    [InitEmpty, Always, TestRun (
       [["part_disk"; "/dev/sdb"; "mbr"];
        ["mkswap_L"; "swapit"; "/dev/sdb1"];
@@ -3650,14 +3662,14 @@ This command disables the libguestfs appliance swap on file.");
 This command enables swap to a labeled swap partition.
 See C<guestfs_swapon_device> for other notes.");
 
-  ("swapoff_label", (RErr, [String "label"]), 175, [],
+  ("swapoff_label", (RErr, [String "label"], []), 175, [],
    [], (* XXX tested by swapon_label *)
    "disable swap on labeled swap partition",
    "\
 This command disables the libguestfs appliance swap on
 labeled swap partition.");
 
-  ("swapon_uuid", (RErr, [String "uuid"]), 176, [Optional "linuxfsuuid"],
+  ("swapon_uuid", (RErr, [String "uuid"], []), 176, [Optional "linuxfsuuid"],
    (let uuid = uuidgen () in
     [InitEmpty, Always, TestRun (
        [["mkswap_U"; uuid; "/dev/sdb"];
@@ -3668,14 +3680,14 @@ labeled swap partition.");
 This command enables swap to a swap partition with the given UUID.
 See C<guestfs_swapon_device> for other notes.");
 
-  ("swapoff_uuid", (RErr, [String "uuid"]), 177, [Optional "linuxfsuuid"],
+  ("swapoff_uuid", (RErr, [String "uuid"], []), 177, [Optional "linuxfsuuid"],
    [], (* XXX tested by swapon_uuid *)
    "disable swap on swap partition by UUID",
    "\
 This command disables the libguestfs appliance swap partition
 with the given UUID.");
 
-  ("mkswap_file", (RErr, [Pathname "path"]), 178, [],
+  ("mkswap_file", (RErr, [Pathname "path"], []), 178, [],
    [InitBasicFS, Always, TestRun (
       [["fallocate"; "/swap"; "8388608"];
        ["mkswap_file"; "/swap"]])],
@@ -3686,7 +3698,7 @@ Create a swap file.
 This command just writes a swap file signature to an existing
 file.  To create the file itself, use something like C<guestfs_fallocate>.");
 
-  ("inotify_init", (RErr, [Int "maxevents"]), 179, [Optional "inotify"],
+  ("inotify_init", (RErr, [Int "maxevents"], []), 179, [Optional "inotify"],
    [InitISOFS, Always, TestRun (
       [["inotify_init"; "0"]])],
    "create an inotify handle",
@@ -3727,7 +3739,7 @@ as exposed by the Linux kernel, which is roughly what we expose
 via libguestfs.  Note that there is one global inotify handle
 per libguestfs instance.");
 
-  ("inotify_add_watch", (RInt64 "wd", [Pathname "path"; Int "mask"]), 180, [Optional "inotify"],
+  ("inotify_add_watch", (RInt64 "wd", [Pathname "path"; Int "mask"], []), 180, [Optional "inotify"],
    [InitBasicFS, Always, TestOutputList (
       [["inotify_init"; "0"];
        ["inotify_add_watch"; "/"; "1073741823"];
@@ -3746,14 +3758,14 @@ Note for non-C or non-Linux callers: the inotify events are
 defined by the Linux kernel ABI and are listed in
 C</usr/include/sys/inotify.h>.");
 
-  ("inotify_rm_watch", (RErr, [Int(*XXX64*) "wd"]), 181, [Optional "inotify"],
+  ("inotify_rm_watch", (RErr, [Int(*XXX64*) "wd"], []), 181, [Optional "inotify"],
    [],
    "remove an inotify watch",
    "\
 Remove a previously defined inotify watch.
 See C<guestfs_inotify_add_watch>.");
 
-  ("inotify_read", (RStructList ("events", "inotify_event"), []), 182, [Optional "inotify"],
+  ("inotify_read", (RStructList ("events", "inotify_event"), [], []), 182, [Optional "inotify"],
    [],
    "return list of inotify events",
    "\
@@ -3768,7 +3780,7 @@ returns an empty list.  The reason is that the call will
 read events up to the maximum appliance-to-host message
 size and leave remaining events in the queue.");
 
-  ("inotify_files", (RStringList "paths", []), 183, [Optional "inotify"],
+  ("inotify_files", (RStringList "paths", [], []), 183, [Optional "inotify"],
    [],
    "return list of watched files that had events",
    "\
@@ -3776,7 +3788,7 @@ This function is a helpful wrapper around C<guestfs_inotify_read>
 which just returns a list of pathnames of objects that were
 touched.  The returned pathnames are sorted and deduplicated.");
 
-  ("inotify_close", (RErr, []), 184, [Optional "inotify"],
+  ("inotify_close", (RErr, [], []), 184, [Optional "inotify"],
    [],
    "close the inotify handle",
    "\
@@ -3784,7 +3796,7 @@ This closes the inotify handle which was previously
 opened by inotify_init.  It removes all watches, throws
 away any pending events, and deallocates all resources.");
 
-  ("setcon", (RErr, [String "context"]), 185, [Optional "selinux"],
+  ("setcon", (RErr, [String "context"], []), 185, [Optional "selinux"],
    [],
    "set SELinux security context",
    "\
@@ -3793,7 +3805,7 @@ to the string C<context>.
 
 See the documentation about SELINUX in L<guestfs(3)>.");
 
-  ("getcon", (RString "context", []), 186, [Optional "selinux"],
+  ("getcon", (RString "context", [], []), 186, [Optional "selinux"],
    [],
    "get SELinux security context",
    "\
@@ -3802,7 +3814,7 @@ This gets the SELinux security context of the daemon.
 See the documentation about SELINUX in L<guestfs(3)>,
 and C<guestfs_setcon>");
 
-  ("mkfs_b", (RErr, [String "fstype"; Int "blocksize"; Device "device"]), 187, [],
+  ("mkfs_b", (RErr, [String "fstype"; Int "blocksize"; Device "device"], []), 187, [],
    [InitEmpty, Always, TestOutput (
       [["part_disk"; "/dev/sda"; "mbr"];
        ["mkfs_b"; "ext2"; "4096"; "/dev/sda1"];
@@ -3831,7 +3843,7 @@ are C<1024>, C<2048> or C<4096> only.
 For VFAT and NTFS the C<blocksize> parameter is treated as
 the requested cluster size.");
 
-  ("mke2journal", (RErr, [Int "blocksize"; Device "device"]), 188, [],
+  ("mke2journal", (RErr, [Int "blocksize"; Device "device"], []), 188, [],
    [InitEmpty, Always, TestOutput (
       [["sfdiskM"; "/dev/sda"; ",100 ,"];
        ["mke2journal"; "4096"; "/dev/sda1"];
@@ -3846,7 +3858,7 @@ to the command:
 
  mke2fs -O journal_dev -b blocksize device");
 
-  ("mke2journal_L", (RErr, [Int "blocksize"; String "label"; Device "device"]), 189, [],
+  ("mke2journal_L", (RErr, [Int "blocksize"; String "label"; Device "device"], []), 189, [],
    [InitEmpty, Always, TestOutput (
       [["sfdiskM"; "/dev/sda"; ",100 ,"];
        ["mke2journal_L"; "4096"; "JOURNAL"; "/dev/sda1"];
@@ -3858,7 +3870,7 @@ to the command:
    "\
 This creates an ext2 external journal on C<device> with label C<label>.");
 
-  ("mke2journal_U", (RErr, [Int "blocksize"; String "uuid"; Device "device"]), 190, [Optional "linuxfsuuid"],
+  ("mke2journal_U", (RErr, [Int "blocksize"; String "uuid"; Device "device"], []), 190, [Optional "linuxfsuuid"],
    (let uuid = uuidgen () in
     [InitEmpty, Always, TestOutput (
        [["sfdiskM"; "/dev/sda"; ",100 ,"];
@@ -3871,7 +3883,7 @@ This creates an ext2 external journal on C<device> with label C<label>.");
    "\
 This creates an ext2 external journal on C<device> with UUID C<uuid>.");
 
-  ("mke2fs_J", (RErr, [String "fstype"; Int "blocksize"; Device "device"; Device "journal"]), 191, [],
+  ("mke2fs_J", (RErr, [String "fstype"; Int "blocksize"; Device "device"; Device "journal"], []), 191, [],
    [],
    "make ext2/3/4 filesystem with external journal",
    "\
@@ -3883,7 +3895,7 @@ to the command:
 
 See also C<guestfs_mke2journal>.");
 
-  ("mke2fs_JL", (RErr, [String "fstype"; Int "blocksize"; Device "device"; String "label"]), 192, [],
+  ("mke2fs_JL", (RErr, [String "fstype"; Int "blocksize"; Device "device"; String "label"], []), 192, [],
    [],
    "make ext2/3/4 filesystem with external journal",
    "\
@@ -3892,7 +3904,7 @@ an external journal on the journal labeled C<label>.
 
 See also C<guestfs_mke2journal_L>.");
 
-  ("mke2fs_JU", (RErr, [String "fstype"; Int "blocksize"; Device "device"; String "uuid"]), 193, [Optional "linuxfsuuid"],
+  ("mke2fs_JU", (RErr, [String "fstype"; Int "blocksize"; Device "device"; String "uuid"], []), 193, [Optional "linuxfsuuid"],
    [],
    "make ext2/3/4 filesystem with external journal",
    "\
@@ -3901,7 +3913,7 @@ an external journal on the journal with UUID C<uuid>.
 
 See also C<guestfs_mke2journal_U>.");
 
-  ("modprobe", (RErr, [String "modulename"]), 194, [Optional "linuxmodules"],
+  ("modprobe", (RErr, [String "modulename"], []), 194, [Optional "linuxmodules"],
    [InitNone, Always, TestRun [["modprobe"; "fat"]]],
    "load a kernel module",
    "\
@@ -3910,7 +3922,7 @@ This loads a kernel module in the appliance.
 The kernel module must have been whitelisted when libguestfs
 was built (see C<appliance/kmod.whitelist.in> in the source).");
 
-  ("echo_daemon", (RString "output", [StringList "words"]), 195, [],
+  ("echo_daemon", (RString "output", [StringList "words"], []), 195, [],
    [InitNone, Always, TestOutput (
       [["echo_daemon"; "This is a test"]], "This is a test"
     )],
@@ -3923,7 +3935,7 @@ You can use this command to test the connection through to the daemon.
 
 See also C<guestfs_ping_daemon>.");
 
-  ("find0", (RErr, [Pathname "directory"; FileOut "files"]), 196, [],
+  ("find0", (RErr, [Pathname "directory"; FileOut "files"], []), 196, [],
    [], (* There is a regression test for this. *)
    "find all files and directories, returning NUL-separated list",
    "\
@@ -3956,7 +3968,7 @@ The result list is not sorted.
 
 =back");
 
-  ("case_sensitive_path", (RString "rpath", [Pathname "path"]), 197, [],
+  ("case_sensitive_path", (RString "rpath", [Pathname "path"], []), 197, [],
    [InitISOFS, Always, TestOutput (
       [["case_sensitive_path"; "/DIRECTORY"]], "/directory");
     InitISOFS, Always, TestOutput (
@@ -4014,7 +4026,7 @@ This function does not handle drive names, backslashes etc.
 
 See also C<guestfs_realpath>.");
 
-  ("vfs_type", (RString "fstype", [Device "device"]), 198, [],
+  ("vfs_type", (RString "fstype", [Device "device"], []), 198, [],
    [InitBasicFS, Always, TestOutput (
       [["vfs_type"; "/dev/sda1"]], "ext2")],
    "get the Linux VFS type corresponding to a mounted device",
@@ -4027,7 +4039,7 @@ VFS module which would be used to mount this filesystem
 if you mounted it without specifying the filesystem type.
 For example a string such as C<ext3> or C<ntfs>.");
 
-  ("truncate", (RErr, [Pathname "path"]), 199, [],
+  ("truncate", (RErr, [Pathname "path"], []), 199, [],
    [InitBasicFS, Always, TestOutputStruct (
       [["write"; "/test"; "some stuff so size is not zero"];
        ["truncate"; "/test"];
@@ -4037,7 +4049,7 @@ For example a string such as C<ext3> or C<ntfs>.");
 This command truncates C<path> to a zero-length file.  The
 file must exist already.");
 
-  ("truncate_size", (RErr, [Pathname "path"; Int64 "size"]), 200, [],
+  ("truncate_size", (RErr, [Pathname "path"; Int64 "size"], []), 200, [],
    [InitBasicFS, Always, TestOutputStruct (
       [["touch"; "/test"];
        ["truncate_size"; "/test"; "1000"];
@@ -4053,7 +4065,7 @@ This creates a sparse file (ie. disk blocks are not allocated
 for the file until you write to it).  To create a non-sparse
 file of zeroes, use C<guestfs_fallocate64> instead.");
 
-  ("utimens", (RErr, [Pathname "path"; Int64 "atsecs"; Int64 "atnsecs"; Int64 "mtsecs"; Int64 "mtnsecs"]), 201, [],
+  ("utimens", (RErr, [Pathname "path"; Int64 "atsecs"; Int64 "atnsecs"; Int64 "mtsecs"; Int64 "mtnsecs"], []), 201, [],
    [InitBasicFS, Always, TestOutputStruct (
       [["touch"; "/test"];
        ["utimens"; "/test"; "12345"; "67890"; "9876"; "5432"];
@@ -4077,7 +4089,7 @@ If the C<*nsecs> field contains the special value C<-2> then
 the corresponding timestamp is left unchanged.  (The
 C<*secs> field is ignored in this case).");
 
-  ("mkdir_mode", (RErr, [Pathname "path"; Int "mode"]), 202, [],
+  ("mkdir_mode", (RErr, [Pathname "path"; Int "mode"], []), 202, [],
    [InitBasicFS, Always, TestOutputStruct (
       [["mkdir_mode"; "/test"; "0o111"];
        ["stat"; "/test"]], [CompareWithInt ("mode", 0o40111)])],
@@ -4092,7 +4104,7 @@ interpret the mode in other ways.
 
 See also C<guestfs_mkdir>, C<guestfs_umask>");
 
-  ("lchown", (RErr, [Int "owner"; Int "group"; Pathname "path"]), 203, [],
+  ("lchown", (RErr, [Int "owner"; Int "group"; Pathname "path"], []), 203, [],
    [], (* XXX *)
    "change file owner and group",
    "\
@@ -4104,7 +4116,7 @@ Only numeric uid and gid are supported.  If you want to use
 names, you will need to locate and parse the password file
 yourself (Augeas support makes this relatively easy).");
 
-  ("lstatlist", (RStructList ("statbufs", "stat"), [Pathname "path"; StringList "names"]), 204, [],
+  ("lstatlist", (RStructList ("statbufs", "stat"), [Pathname "path"; StringList "names"], []), 204, [],
    [], (* XXX *)
    "lstat on multiple files",
    "\
@@ -4125,7 +4137,7 @@ might cause the protocol message size to be exceeded, causing
 this call to fail.  The caller must split up such requests
 into smaller groups of names.");
 
-  ("lxattrlist", (RStructList ("xattrs", "xattr"), [Pathname "path"; StringList "names"]), 205, [Optional "linuxxattrs"],
+  ("lxattrlist", (RStructList ("xattrs", "xattr"), [Pathname "path"; StringList "names"], []), 205, [Optional "linuxxattrs"],
    [], (* XXX *)
    "lgetxattr on multiple files",
    "\
@@ -4151,7 +4163,7 @@ might cause the protocol message size to be exceeded, causing
 this call to fail.  The caller must split up such requests
 into smaller groups of names.");
 
-  ("readlinklist", (RStringList "links", [Pathname "path"; StringList "names"]), 206, [],
+  ("readlinklist", (RStringList "links", [Pathname "path"; StringList "names"], []), 206, [],
    [], (* XXX *)
    "readlink on multiple files",
    "\
@@ -4177,7 +4189,7 @@ message size to be exceeded, causing
 this call to fail.  The caller must split up such requests
 into smaller groups of names.");
 
-  ("pread", (RBufferOut "content", [Pathname "path"; Int "count"; Int64 "offset"]), 207, [ProtocolLimitWarning],
+  ("pread", (RBufferOut "content", [Pathname "path"; Int "count"; Int64 "offset"], []), 207, [ProtocolLimitWarning],
    [InitISOFS, Always, TestOutputBuffer (
       [["pread"; "/known-4"; "1"; "3"]], "\n");
     InitISOFS, Always, TestOutputBuffer (
@@ -4192,7 +4204,7 @@ see the L<pread(2)> system call.
 
 See also C<guestfs_pwrite>, C<guestfs_pread_device>.");
 
-  ("part_init", (RErr, [Device "device"; String "parttype"]), 208, [],
+  ("part_init", (RErr, [Device "device"; String "parttype"], []), 208, [],
    [InitEmpty, Always, TestRun (
       [["part_init"; "/dev/sda"; "gpt"]])],
    "create an empty partition table",
@@ -4264,7 +4276,7 @@ Sun disk labels.
 
 =back");
 
-  ("part_add", (RErr, [Device "device"; String "prlogex"; Int64 "startsect"; Int64 "endsect"]), 209, [],
+  ("part_add", (RErr, [Device "device"; String "prlogex"; Int64 "startsect"; Int64 "endsect"], []), 209, [],
    [InitEmpty, Always, TestRun (
       [["part_init"; "/dev/sda"; "mbr"];
        ["part_add"; "/dev/sda"; "primary"; "1"; "-1"]]);
@@ -4295,7 +4307,7 @@ backwards from the end of the disk (C<-1> is the last sector).
 Creating a partition which covers the whole disk is not so easy.
 Use C<guestfs_part_disk> to do that.");
 
-  ("part_disk", (RErr, [Device "device"; String "parttype"]), 210, [DangerWillRobinson],
+  ("part_disk", (RErr, [Device "device"; String "parttype"], []), 210, [DangerWillRobinson],
    [InitEmpty, Always, TestRun (
       [["part_disk"; "/dev/sda"; "mbr"]]);
     InitEmpty, Always, TestRun (
@@ -4309,7 +4321,7 @@ covering the whole disk.
 C<parttype> is the partition table type, usually C<mbr> or C<gpt>,
 but other possible values are described in C<guestfs_part_init>.");
 
-  ("part_set_bootable", (RErr, [Device "device"; Int "partnum"; Bool "bootable"]), 211, [],
+  ("part_set_bootable", (RErr, [Device "device"; Int "partnum"; Bool "bootable"], []), 211, [],
    [InitEmpty, Always, TestRun (
       [["part_disk"; "/dev/sda"; "mbr"];
        ["part_set_bootable"; "/dev/sda"; "1"; "true"]])],
@@ -4322,7 +4334,7 @@ The bootable flag is used by some operating systems (notably
 Windows) to determine which partition to boot from.  It is by
 no means universally recognized.");
 
-  ("part_set_name", (RErr, [Device "device"; Int "partnum"; String "name"]), 212, [],
+  ("part_set_name", (RErr, [Device "device"; Int "partnum"; String "name"], []), 212, [],
    [InitEmpty, Always, TestRun (
       [["part_disk"; "/dev/sda"; "gpt"];
        ["part_set_name"; "/dev/sda"; "1"; "thepartname"]])],
@@ -4334,7 +4346,7 @@ device C<device>.  Note that partitions are numbered from 1.
 The partition name can only be set on certain types of partition
 table.  This works on C<gpt> but not on C<mbr> partitions.");
 
-  ("part_list", (RStructList ("partitions", "partition"), [Device "device"]), 213, [],
+  ("part_list", (RStructList ("partitions", "partition"), [Device "device"], []), 213, [],
    [], (* XXX Add a regression test for this. *)
    "list partitions on a device",
    "\
@@ -4364,7 +4376,7 @@ Size of the partition in bytes.
 
 =back");
 
-  ("part_get_parttype", (RString "parttype", [Device "device"]), 214, [],
+  ("part_get_parttype", (RString "parttype", [Device "device"], []), 214, [],
    [InitEmpty, Always, TestOutput (
       [["part_disk"; "/dev/sda"; "gpt"];
        ["part_get_parttype"; "/dev/sda"]], "gpt")],
@@ -4378,7 +4390,7 @@ partition table), C<gpt> (a GPT/EFI-style partition table).  Other
 values are possible, although unusual.  See C<guestfs_part_init>
 for a full list.");
 
-  ("fill", (RErr, [Int "c"; Int "len"; Pathname "path"]), 215, [Progress],
+  ("fill", (RErr, [Int "c"; Int "len"; Pathname "path"], []), 215, [Progress],
    [InitBasicFS, Always, TestOutputBuffer (
       [["fill"; "0x63"; "10"; "/test"];
        ["read_file"; "/test"]], "cccccccccc")],
@@ -4393,7 +4405,7 @@ much more efficient to use C<guestfs_truncate_size>.
 To create a file with a pattern of repeating bytes
 use C<guestfs_fill_pattern>.");
 
-  ("available", (RErr, [StringList "groups"]), 216, [],
+  ("available", (RErr, [StringList "groups"], []), 216, [],
    [InitNone, Always, TestRun [["available"; ""]]],
    "test availability of some parts of the API",
    "\
@@ -4454,7 +4466,7 @@ See also C<guestfs_version>.
 
 =back");
 
-  ("dd", (RErr, [Dev_or_Path "src"; Dev_or_Path "dest"]), 217, [],
+  ("dd", (RErr, [Dev_or_Path "src"; Dev_or_Path "dest"], []), 217, [],
    [InitBasicFS, Always, TestOutputBuffer (
       [["write"; "/src"; "hello, world"];
        ["dd"; "/src"; "/dest"];
@@ -4470,7 +4482,7 @@ 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 (see C<guestfs_copy_size>).");
 
-  ("filesize", (RInt64 "size", [Pathname "file"]), 218, [],
+  ("filesize", (RInt64 "size", [Pathname "file"], []), 218, [],
    [InitBasicFS, Always, TestOutputInt (
       [["write"; "/file"; "hello, world"];
        ["filesize"; "/file"]], 12)],
@@ -4482,7 +4494,7 @@ 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, [],
+  ("lvrename", (RErr, [String "logvol"; String "newlogvol"], []), 219, [],
    [InitBasicFSonLVM, Always, TestOutputList (
       [["lvrename"; "/dev/VG/LV"; "/dev/VG/LV2"];
        ["lvs"]], ["/dev/VG/LV2"])],
@@ -4490,7 +4502,7 @@ To get the size of block devices, use C<guestfs_blockdev_getsize64>.");
    "\
 Rename a logical volume C<logvol> with the new name C<newlogvol>.");
 
-  ("vgrename", (RErr, [String "volgroup"; String "newvolgroup"]), 220, [],
+  ("vgrename", (RErr, [String "volgroup"; String "newvolgroup"], []), 220, [],
    [InitBasicFSonLVM, Always, TestOutputList (
       [["umount"; "/"];
        ["vg_activate"; "false"; "VG"];
@@ -4502,7 +4514,7 @@ Rename a logical volume C<logvol> with the new name C<newlogvol>.");
    "\
 Rename a volume group C<volgroup> with the new name C<newvolgroup>.");
 
-  ("initrd_cat", (RBufferOut "content", [Pathname "initrdpath"; String "filename"]), 221, [ProtocolLimitWarning],
+  ("initrd_cat", (RBufferOut "content", [Pathname "initrdpath"; String "filename"], []), 221, [ProtocolLimitWarning],
    [InitISOFS, Always, TestOutputBuffer (
       [["initrd_cat"; "/initrd"; "known-4"]], "abc\ndef\nghi")],
    "list the contents of a single file in an initrd",
@@ -4519,25 +4531,25 @@ contained in a Linux initrd or initramfs image:
 
 See also C<guestfs_initrd_list>.");
 
-  ("pvuuid", (RString "uuid", [Device "device"]), 222, [],
+  ("pvuuid", (RString "uuid", [Device "device"], []), 222, [],
    [],
    "get the UUID of a physical volume",
    "\
 This command returns the UUID of the LVM PV C<device>.");
 
-  ("vguuid", (RString "uuid", [String "vgname"]), 223, [],
+  ("vguuid", (RString "uuid", [String "vgname"], []), 223, [],
    [],
    "get the UUID of a volume group",
    "\
 This command returns the UUID of the LVM VG named C<vgname>.");
 
-  ("lvuuid", (RString "uuid", [Device "device"]), 224, [],
+  ("lvuuid", (RString "uuid", [Device "device"], []), 224, [],
    [],
    "get the UUID of a logical volume",
    "\
 This command returns the UUID of the LVM LV C<device>.");
 
-  ("vgpvuuids", (RStringList "uuids", [String "vgname"]), 225, [],
+  ("vgpvuuids", (RStringList "uuids", [String "vgname"], []), 225, [],
    [],
    "get the PV UUIDs containing the volume group",
    "\
@@ -4549,7 +4561,7 @@ calls to associate physical volumes and volume groups.
 
 See also C<guestfs_vglvuuids>.");
 
-  ("vglvuuids", (RStringList "uuids", [String "vgname"]), 226, [],
+  ("vglvuuids", (RStringList "uuids", [String "vgname"], []), 226, [],
    [],
    "get the LV UUIDs of all LVs in the volume group",
    "\
@@ -4561,7 +4573,7 @@ calls to associate logical volumes and volume groups.
 
 See also C<guestfs_vgpvuuids>.");
 
-  ("copy_size", (RErr, [Dev_or_Path "src"; Dev_or_Path "dest"; Int64 "size"]), 227, [Progress],
+  ("copy_size", (RErr, [Dev_or_Path "src"; Dev_or_Path "dest"; Int64 "size"], []), 227, [Progress],
    [InitBasicFS, Always, TestOutputBuffer (
       [["write"; "/src"; "hello, world"];
        ["copy_size"; "/src"; "/dest"; "5"];
@@ -4574,7 +4586,7 @@ or file C<src> to another destination device or file C<dest>.
 Note this will fail if the source is too short or if the destination
 is not large enough.");
 
-  ("zero_device", (RErr, [Device "device"]), 228, [DangerWillRobinson; Progress],
+  ("zero_device", (RErr, [Device "device"], []), 228, [DangerWillRobinson; Progress],
    [InitBasicFSonLVM, Always, TestRun (
       [["zero_device"; "/dev/VG/LV"]])],
    "write zeroes to an entire device",
@@ -4583,7 +4595,7 @@ This command writes zeroes over the entire C<device>.  Compare
 with C<guestfs_zero> which just zeroes the first few blocks of
 a device.");
 
-  ("txz_in", (RErr, [FileIn "tarball"; Pathname "directory"]), 229, [Optional "xz"],
+  ("txz_in", (RErr, [FileIn "tarball"; Pathname "directory"], []), 229, [Optional "xz"],
    [InitBasicFS, Always, TestOutput (
       [["txz_in"; "../images/helloworld.tar.xz"; "/"];
        ["cat"; "/hello"]], "hello\n")],
@@ -4592,14 +4604,14 @@ a device.");
 This command uploads and unpacks local file C<tarball> (an
 I<xz compressed> tar file) into C<directory>.");
 
-  ("txz_out", (RErr, [Pathname "directory"; FileOut "tarball"]), 230, [Optional "xz"],
+  ("txz_out", (RErr, [Pathname "directory"; FileOut "tarball"], []), 230, [Optional "xz"],
    [],
    "pack directory into compressed tarball",
    "\
 This command packs the contents of C<directory> and downloads
 it to local file C<tarball> (as an xz compressed tar archive).");
 
-  ("ntfsresize", (RErr, [Device "device"]), 231, [Optional "ntfsprogs"],
+  ("ntfsresize", (RErr, [Device "device"], []), 231, [Optional "ntfsprogs"],
    [],
    "resize an NTFS filesystem",
    "\
@@ -4607,7 +4619,7 @@ This command resizes an NTFS filesystem, expanding or
 shrinking it to the size of the underlying device.
 See also L<ntfsresize(8)>.");
 
-  ("vgscan", (RErr, []), 232, [],
+  ("vgscan", (RErr, [], []), 232, [],
    [InitEmpty, Always, TestRun (
       [["vgscan"]])],
    "rescan for LVM physical volumes, volume groups and logical volumes",
@@ -4615,7 +4627,7 @@ See also L<ntfsresize(8)>.");
 This rescans all block devices and rebuilds the list of LVM
 physical volumes, volume groups and logical volumes.");
 
-  ("part_del", (RErr, [Device "device"; Int "partnum"]), 233, [],
+  ("part_del", (RErr, [Device "device"; Int "partnum"], []), 233, [],
    [InitEmpty, Always, TestRun (
       [["part_init"; "/dev/sda"; "mbr"];
        ["part_add"; "/dev/sda"; "primary"; "1"; "-1"];
@@ -4628,7 +4640,7 @@ Note that in the case of MBR partitioning, deleting an
 extended partition also deletes any logical partitions
 it contains.");
 
-  ("part_get_bootable", (RBool "bootable", [Device "device"; Int "partnum"]), 234, [],
+  ("part_get_bootable", (RBool "bootable", [Device "device"; Int "partnum"], []), 234, [],
    [InitEmpty, Always, TestOutputTrue (
       [["part_init"; "/dev/sda"; "mbr"];
        ["part_add"; "/dev/sda"; "primary"; "1"; "-1"];
@@ -4641,7 +4653,7 @@ C<device> has the bootable flag set.
 
 See also C<guestfs_part_set_bootable>.");
 
-  ("part_get_mbr_id", (RInt "idbyte", [Device "device"; Int "partnum"]), 235, [FishOutput FishOutputHexadecimal],
+  ("part_get_mbr_id", (RInt "idbyte", [Device "device"; Int "partnum"], []), 235, [FishOutput FishOutputHexadecimal],
    [InitEmpty, Always, TestOutputInt (
       [["part_init"; "/dev/sda"; "mbr"];
        ["part_add"; "/dev/sda"; "primary"; "1"; "-1"];
@@ -4656,7 +4668,7 @@ Note that only MBR (old DOS-style) partitions have type bytes.
 You will get undefined results for other partition table
 types (see C<guestfs_part_get_parttype>).");
 
-  ("part_set_mbr_id", (RErr, [Device "device"; Int "partnum"; Int "idbyte"]), 236, [],
+  ("part_set_mbr_id", (RErr, [Device "device"; Int "partnum"; Int "idbyte"], []), 236, [],
    [], (* tested by part_get_mbr_id *)
    "set the MBR type byte (ID byte) of a partition",
    "\
@@ -4670,7 +4682,7 @@ Note that only MBR (old DOS-style) partitions have type bytes.
 You will get undefined results for other partition table
 types (see C<guestfs_part_get_parttype>).");
 
-  ("checksum_device", (RString "checksum", [String "csumtype"; Device "device"]), 237, [],
+  ("checksum_device", (RString "checksum", [String "csumtype"; Device "device"], []), 237, [],
    [InitISOFS, Always, TestOutputFileMD5 (
       [["checksum_device"; "md5"; "/dev/sdd"]],
       "../images/test.iso")],
@@ -4680,7 +4692,7 @@ This call computes the MD5, SHAx or CRC checksum of the
 contents of the device named C<device>.  For the types of
 checksums supported see the C<guestfs_checksum> command.");
 
-  ("lvresize_free", (RErr, [Device "lv"; Int "percent"]), 238, [Optional "lvm2"],
+  ("lvresize_free", (RErr, [Device "lv"; Int "percent"], []), 238, [Optional "lvm2"],
    [InitNone, Always, TestRun (
       [["part_disk"; "/dev/sda"; "mbr"];
        ["pvcreate"; "/dev/sda1"];
@@ -4695,14 +4707,14 @@ you would call this with pc = 100 which expands the logical volume
 as much as possible, using all remaining free space in the volume
 group.");
 
-  ("aug_clear", (RErr, [String "augpath"]), 239, [Optional "augeas"],
+  ("aug_clear", (RErr, [String "augpath"], []), 239, [Optional "augeas"],
    [], (* XXX Augeas code needs tests. *)
    "clear Augeas path",
    "\
 Set the value associated with C<path> to C<NULL>.  This
 is the same as the L<augtool(1)> C<clear> command.");
 
-  ("get_umask", (RInt "mask", []), 240, [FishOutput FishOutputOctal],
+  ("get_umask", (RInt "mask", [], []), 240, [FishOutput FishOutputOctal],
    [InitEmpty, Always, TestOutputInt (
       [["get_umask"]], 0o22)],
    "get the current umask",
@@ -4710,7 +4722,7 @@ is the same as the L<augtool(1)> C<clear> command.");
 Return the current umask.  By default the umask is C<022>
 unless it has been set by calling C<guestfs_umask>.");
 
-  ("debug_upload", (RErr, [FileIn "filename"; String "tmpname"; Int "mode"]), 241, [],
+  ("debug_upload", (RErr, [FileIn "filename"; String "tmpname"; Int "mode"], []), 241, [],
    [],
    "upload a file to the appliance (internal use only)",
    "\
@@ -4721,7 +4733,7 @@ There is no comprehensive help for this command.  You have
 to look at the file C<daemon/debug.c> in the libguestfs source
 to find out what it is for.");
 
-  ("base64_in", (RErr, [FileIn "base64file"; Pathname "filename"]), 242, [],
+  ("base64_in", (RErr, [FileIn "base64file"; Pathname "filename"], []), 242, [],
    [InitBasicFS, Always, TestOutput (
       [["base64_in"; "../images/hello.b64"; "/hello"];
        ["cat"; "/hello"]], "hello\n")],
@@ -4730,14 +4742,14 @@ to find out what it is for.");
 This command uploads base64-encoded data from C<base64file>
 to C<filename>.");
 
-  ("base64_out", (RErr, [Pathname "filename"; FileOut "base64file"]), 243, [],
+  ("base64_out", (RErr, [Pathname "filename"; FileOut "base64file"], []), 243, [],
    [],
    "download file and encode as base64",
    "\
 This command downloads the contents of C<filename>, writing
 it out to local file C<base64file> encoded as base64.");
 
-  ("checksums_out", (RErr, [String "csumtype"; Pathname "directory"; FileOut "sumsfile"]), 244, [],
+  ("checksums_out", (RErr, [String "csumtype"; Pathname "directory"; FileOut "sumsfile"], []), 244, [],
    [],
    "compute MD5, SHAx or CRC checksum of files in a directory",
    "\
@@ -4753,7 +4765,7 @@ filename is not printable, coreutils uses a special
 backslash syntax.  For more information, see the GNU
 coreutils info file.");
 
-  ("fill_pattern", (RErr, [String "pattern"; Int "len"; Pathname "path"]), 245, [Progress],
+  ("fill_pattern", (RErr, [String "pattern"; Int "len"; Pathname "path"], []), 245, [Progress],
    [InitBasicFS, Always, TestOutputBuffer (
       [["fill_pattern"; "abcdefghijklmnopqrstuvwxyz"; "28"; "/test"];
        ["read_file"; "/test"]], "abcdefghijklmnopqrstuvwxyzab")],
@@ -4764,7 +4776,7 @@ a new file of length C<len> containing the repeating pattern
 of bytes in C<pattern>.  The pattern is truncated if necessary
 to ensure the length of the file is exactly C<len> bytes.");
 
-  ("write", (RErr, [Pathname "path"; BufferIn "content"]), 246, [ProtocolLimitWarning],
+  ("write", (RErr, [Pathname "path"; BufferIn "content"], []), 246, [ProtocolLimitWarning],
    [InitBasicFS, Always, TestOutput (
       [["write"; "/new"; "new file contents"];
        ["cat"; "/new"]], "new file contents");
@@ -4788,7 +4800,7 @@ to ensure the length of the file is exactly C<len> bytes.");
 This call creates a file called C<path>.  The content of the
 file is the string C<content> (which can contain any 8 bit data).");
 
-  ("pwrite", (RInt "nbytes", [Pathname "path"; BufferIn "content"; Int64 "offset"]), 247, [ProtocolLimitWarning],
+  ("pwrite", (RInt "nbytes", [Pathname "path"; BufferIn "content"; Int64 "offset"], []), 247, [ProtocolLimitWarning],
    [InitBasicFS, Always, TestOutput (
       [["write"; "/new"; "new file contents"];
        ["pwrite"; "/new"; "data"; "4"];
@@ -4814,28 +4826,28 @@ unlikely for regular files in ordinary circumstances.
 
 See also C<guestfs_pread>, C<guestfs_pwrite_device>.");
 
-  ("resize2fs_size", (RErr, [Device "device"; Int64 "size"]), 248, [],
+  ("resize2fs_size", (RErr, [Device "device"; Int64 "size"], []), 248, [],
    [],
    "resize an ext2, ext3 or ext4 filesystem (with size)",
    "\
 This command is the same as C<guestfs_resize2fs> except that it
 allows you to specify the new size (in bytes) explicitly.");
 
-  ("pvresize_size", (RErr, [Device "device"; Int64 "size"]), 249, [Optional "lvm2"],
+  ("pvresize_size", (RErr, [Device "device"; Int64 "size"], []), 249, [Optional "lvm2"],
    [],
    "resize an LVM physical volume (with size)",
    "\
 This command is the same as C<guestfs_pvresize> except that it
 allows you to specify the new size (in bytes) explicitly.");
 
-  ("ntfsresize_size", (RErr, [Device "device"; Int64 "size"]), 250, [Optional "ntfsprogs"],
+  ("ntfsresize_size", (RErr, [Device "device"; Int64 "size"], []), 250, [Optional "ntfsprogs"],
    [],
    "resize an NTFS filesystem (with size)",
    "\
 This command is the same as C<guestfs_ntfsresize> except that it
 allows you to specify the new size (in bytes) explicitly.");
 
-  ("available_all_groups", (RStringList "groups", []), 251, [],
+  ("available_all_groups", (RStringList "groups", [], []), 251, [],
    [InitNone, Always, TestRun [["available_all_groups"]]],
    "return a list of all optional groups",
    "\
@@ -4847,7 +4859,7 @@ returned list.
 
 See also C<guestfs_available> and L<guestfs(3)/AVAILABILITY>.");
 
-  ("fallocate64", (RErr, [Pathname "path"; Int64 "len"]), 252, [],
+  ("fallocate64", (RErr, [Pathname "path"; Int64 "len"], []), 252, [],
    [InitBasicFS, Always, TestOutputStruct (
       [["fallocate64"; "/a"; "1000000"];
        ["stat"; "/a"]], [CompareWithInt ("size", 1_000_000)])],
@@ -4869,7 +4881,7 @@ Do not confuse this with the guestfish-specific
 C<alloc> and C<sparse> commands which create
 a file in the host and attach it as a device.");
 
-  ("vfs_label", (RString "label", [Device "device"]), 253, [],
+  ("vfs_label", (RString "label", [Device "device"], []), 253, [],
    [InitBasicFS, Always, TestOutput (
        [["set_e2label"; "/dev/sda1"; "LTEST"];
         ["vfs_label"; "/dev/sda1"]], "LTEST")],
@@ -4882,7 +4894,7 @@ If the filesystem is unlabeled, this returns the empty string.
 
 To find a filesystem from the label, use C<guestfs_findfs_label>.");
 
-  ("vfs_uuid", (RString "uuid", [Device "device"]), 254, [],
+  ("vfs_uuid", (RString "uuid", [Device "device"], []), 254, [],
    (let uuid = uuidgen () in
     [InitBasicFS, Always, TestOutput (
        [["set_e2uuid"; "/dev/sda1"; uuid];
@@ -4896,7 +4908,7 @@ If the filesystem does not have a UUID, this returns the empty string.
 
 To find a filesystem from the UUID, use C<guestfs_findfs_uuid>.");
 
-  ("lvm_set_filter", (RErr, [DeviceList "devices"]), 255, [Optional "lvm2"],
+  ("lvm_set_filter", (RErr, [DeviceList "devices"], []), 255, [Optional "lvm2"],
    (* Can't be tested with the current framework because
     * the VG is being used by the mounted filesystem, so
     * the vgchange -an command we do first will fail.
@@ -4927,7 +4939,7 @@ You cannot use this if any VG is currently in use (eg.
 contains a mounted filesystem), even if you are not
 filtering out that VG.");
 
-  ("lvm_clear_filter", (RErr, []), 256, [],
+  ("lvm_clear_filter", (RErr, [], []), 256, [],
    [], (* see note on lvm_set_filter *)
    "clear LVM device filter",
    "\
@@ -4937,7 +4949,7 @@ will be able to see every block device.
 This command also clears the LVM cache and performs a volume
 group scan.");
 
-  ("luks_open", (RErr, [Device "device"; Key "key"; String "mapname"]), 257, [Optional "luks"],
+  ("luks_open", (RErr, [Device "device"; Key "key"; String "mapname"], []), 257, [Optional "luks"],
    [],
    "open a LUKS-encrypted block device",
    "\
@@ -4957,14 +4969,14 @@ If this block device contains LVM volume groups, then
 calling C<guestfs_vgscan> followed by C<guestfs_vg_activate_all>
 will make them visible.");
 
-  ("luks_open_ro", (RErr, [Device "device"; Key "key"; String "mapname"]), 258, [Optional "luks"],
+  ("luks_open_ro", (RErr, [Device "device"; Key "key"; String "mapname"], []), 258, [Optional "luks"],
    [],
    "open a LUKS-encrypted block device read-only",
    "\
 This is the same as C<guestfs_luks_open> except that a read-only
 mapping is created.");
 
-  ("luks_close", (RErr, [Device "device"]), 259, [Optional "luks"],
+  ("luks_close", (RErr, [Device "device"], []), 259, [Optional "luks"],
    [],
    "close a LUKS device",
    "\
@@ -4974,7 +4986,7 @@ C<device> parameter must be the name of the LUKS mapping
 device (ie. C</dev/mapper/mapname>) and I<not> the name
 of the underlying block device.");
 
-  ("luks_format", (RErr, [Device "device"; Key "key"; Int "keyslot"]), 260, [Optional "luks"; DangerWillRobinson],
+  ("luks_format", (RErr, [Device "device"; Key "key"; Int "keyslot"], []), 260, [Optional "luks"; DangerWillRobinson],
    [],
    "format a block device as a LUKS encrypted device",
    "\
@@ -4983,14 +4995,14 @@ the device as a LUKS encrypted device.  C<key> is the
 initial key, which is added to key slot C<slot>.  (LUKS
 supports 8 key slots, numbered 0-7).");
 
-  ("luks_format_cipher", (RErr, [Device "device"; Key "key"; Int "keyslot"; String "cipher"]), 261, [Optional "luks"; DangerWillRobinson],
+  ("luks_format_cipher", (RErr, [Device "device"; Key "key"; Int "keyslot"; String "cipher"], []), 261, [Optional "luks"; DangerWillRobinson],
    [],
    "format a block device as a LUKS encrypted device",
    "\
 This command is the same as C<guestfs_luks_format> but
 it also allows you to set the C<cipher> used.");
 
-  ("luks_add_key", (RErr, [Device "device"; Key "key"; Key "newkey"; Int "keyslot"]), 262, [Optional "luks"],
+  ("luks_add_key", (RErr, [Device "device"; Key "key"; Key "newkey"; Int "keyslot"], []), 262, [Optional "luks"],
    [],
    "add a key on a LUKS encrypted device",
    "\
@@ -5003,7 +5015,7 @@ Note that if C<keyslot> already contains a key, then this
 command will fail.  You have to use C<guestfs_luks_kill_slot>
 first to remove that key.");
 
-  ("luks_kill_slot", (RErr, [Device "device"; Key "key"; Int "keyslot"]), 263, [Optional "luks"],
+  ("luks_kill_slot", (RErr, [Device "device"; Key "key"; Int "keyslot"], []), 263, [Optional "luks"],
    [],
    "remove a key from a LUKS encrypted device",
    "\
@@ -5011,7 +5023,7 @@ This command deletes the key in key slot C<keyslot> from the
 encrypted LUKS device C<device>.  C<key> must be one of the
 I<other> keys.");
 
-  ("is_lv", (RBool "lvflag", [Device "device"]), 264, [Optional "lvm2"],
+  ("is_lv", (RBool "lvflag", [Device "device"], []), 264, [Optional "lvm2"],
    [InitBasicFSonLVM, IfAvailable "lvm2", TestOutputTrue (
       [["is_lv"; "/dev/VG/LV"]]);
     InitBasicFSonLVM, IfAvailable "lvm2", TestOutputFalse (
@@ -5021,7 +5033,7 @@ I<other> keys.");
 This command tests whether C<device> is a logical volume, and
 returns true iff this is the case.");
 
-  ("findfs_uuid", (RString "device", [String "uuid"]), 265, [],
+  ("findfs_uuid", (RString "device", [String "uuid"], []), 265, [],
    [],
    "find a filesystem by UUID",
    "\
@@ -5031,7 +5043,7 @@ filesystem can be found.
 
 To find the UUID of a filesystem, use C<guestfs_vfs_uuid>.");
 
-  ("findfs_label", (RString "device", [String "label"]), 266, [],
+  ("findfs_label", (RString "device", [String "label"], []), 266, [],
    [],
    "find a filesystem by label",
    "\
@@ -5041,7 +5053,7 @@ filesystem can be found.
 
 To find the label of a filesystem, use C<guestfs_vfs_label>.");
 
-  ("is_chardev", (RBool "flag", [Pathname "path"]), 267, [],
+  ("is_chardev", (RBool "flag", [Pathname "path"], []), 267, [],
    [InitISOFS, Always, TestOutputFalse (
       [["is_chardev"; "/directory"]]);
     InitBasicFS, Always, TestOutputTrue (
@@ -5054,7 +5066,7 @@ with the given C<path> name.
 
 See also C<guestfs_stat>.");
 
-  ("is_blockdev", (RBool "flag", [Pathname "path"]), 268, [],
+  ("is_blockdev", (RBool "flag", [Pathname "path"], []), 268, [],
    [InitISOFS, Always, TestOutputFalse (
       [["is_blockdev"; "/directory"]]);
     InitBasicFS, Always, TestOutputTrue (
@@ -5067,7 +5079,7 @@ with the given C<path> name.
 
 See also C<guestfs_stat>.");
 
-  ("is_fifo", (RBool "flag", [Pathname "path"]), 269, [],
+  ("is_fifo", (RBool "flag", [Pathname "path"], []), 269, [],
    [InitISOFS, Always, TestOutputFalse (
       [["is_fifo"; "/directory"]]);
     InitBasicFS, Always, TestOutputTrue (
@@ -5080,7 +5092,7 @@ with the given C<path> name.
 
 See also C<guestfs_stat>.");
 
-  ("is_symlink", (RBool "flag", [Pathname "path"]), 270, [],
+  ("is_symlink", (RBool "flag", [Pathname "path"], []), 270, [],
    [InitISOFS, Always, TestOutputFalse (
       [["is_symlink"; "/directory"]]);
     InitISOFS, Always, TestOutputTrue (
@@ -5092,7 +5104,7 @@ with the given C<path> name.
 
 See also C<guestfs_stat>.");
 
-  ("is_socket", (RBool "flag", [Pathname "path"]), 271, [],
+  ("is_socket", (RBool "flag", [Pathname "path"], []), 271, [],
    (* XXX Need a positive test for sockets. *)
    [InitISOFS, Always, TestOutputFalse (
       [["is_socket"; "/directory"]])],
@@ -5103,7 +5115,7 @@ with the given C<path> name.
 
 See also C<guestfs_stat>.");
 
-  ("part_to_dev", (RString "device", [Device "partition"]), 272, [],
+  ("part_to_dev", (RString "device", [Device "partition"], []), 272, [],
    [InitPartition, Always, TestOutputDevice (
       [["part_to_dev"; "/dev/sda1"]], "/dev/sda");
     InitEmpty, Always, TestLastFail (
@@ -5117,7 +5129,7 @@ removes the partition number, returning the device name
 The named partition must exist, for example as a string returned
 from C<guestfs_list_partitions>.");
 
-  ("upload_offset", (RErr, [FileIn "filename"; Dev_or_Path "remotefilename"; Int64 "offset"]), 273, [],
+  ("upload_offset", (RErr, [FileIn "filename"; Dev_or_Path "remotefilename"; Int64 "offset"], []), 273, [],
    (let md5 = Digest.to_hex (Digest.file "COPYING.LIB") in
     [InitBasicFS, Always, TestOutput (
        [["upload_offset"; "../COPYING.LIB"; "/COPYING.LIB"; "0"];
@@ -5141,7 +5153,7 @@ error occurs.
 
 See also C<guestfs_upload>, C<guestfs_pwrite>.");
 
-  ("download_offset", (RErr, [Dev_or_Path "remotefilename"; FileOut "filename"; Int64 "offset"; Int64 "size"]), 274, [Progress],
+  ("download_offset", (RErr, [Dev_or_Path "remotefilename"; FileOut "filename"; Int64 "offset"; Int64 "size"], []), 274, [Progress],
    (let md5 = Digest.to_hex (Digest.file "COPYING.LIB") in
     let offset = string_of_int 100 in
     let size = string_of_int ((Unix.stat "COPYING.LIB").Unix.st_size - 100) in
@@ -5166,7 +5178,7 @@ error occurs.
 
 See also C<guestfs_download>, C<guestfs_pread>.");
 
-  ("pwrite_device", (RInt "nbytes", [Device "device"; BufferIn "content"; Int64 "offset"]), 275, [ProtocolLimitWarning],
+  ("pwrite_device", (RInt "nbytes", [Device "device"; BufferIn "content"; Int64 "offset"], []), 275, [ProtocolLimitWarning],
    [InitPartition, Always, TestOutputList (
       [["pwrite_device"; "/dev/sda"; "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000"; "446"];
        ["blockdev_rereadpt"; "/dev/sda"];
@@ -5183,7 +5195,7 @@ probably impossible with standard Linux kernels).
 
 See also C<guestfs_pwrite>.");
 
-  ("pread_device", (RBufferOut "content", [Device "device"; Int "count"; Int64 "offset"]), 276, [ProtocolLimitWarning],
+  ("pread_device", (RBufferOut "content", [Device "device"; Int "count"; Int64 "offset"], []), 276, [ProtocolLimitWarning],
    [InitEmpty, Always, TestOutputBuffer (
       [["pread_device"; "/dev/sdd"; "8"; "32768"]], "\001CD001\001\000")],
    "read part of a device",
@@ -5217,16 +5229,16 @@ let max_proc_nr =
 
 (* Non-API meta-commands available only in guestfish.
  *
- * Note (1): args/return value, proc_nr and tests fields are all
- * meaningless.  The only fields which are actually used are the
- * shortname, FishAlias flags, shortdesc and longdesc.
+ * Note (1): style, proc_nr and tests fields are all meaningless.
+ * The only fields which are actually used are the shortname,
+ * FishAlias flags, shortdesc and longdesc.
  *
  * Note (2): to refer to other commands, use L</shortname>.
  *
  * Note (3): keep this list sorted by shortname.
  *)
 let fish_commands = [
-  ("alloc", (RErr,[]), -1, [FishAlias "allocate"], [],
+  ("alloc", (RErr,[], []), -1, [FishAlias "allocate"], [],
    "allocate and add a disk file",
    " alloc filename size
 
@@ -5240,7 +5252,7 @@ Size can be specified using standard suffixes, eg. C<1M>.
 To create a sparse file, use L</sparse> instead.  To create a
 prepared disk image, see L</PREPARED DISK IMAGES>.");
 
-  ("copy_in", (RErr,[]), -1, [], [],
+  ("copy_in", (RErr,[], []), -1, [], [],
    "copy local files or directories into an image",
    " copy-in local [local ...] /remotedir
 
@@ -5253,7 +5265,7 @@ Multiple local files and directories can be specified, but the last
 parameter must always be a remote directory.  Wildcards cannot be
 used.");
 
-  ("copy_out", (RErr,[]), -1, [], [],
+  ("copy_out", (RErr,[], []), -1, [], [],
    "copy remote files or directories out of an image",
    " copy-out remote [remote ...] localdir
 
@@ -5274,13 +5286,13 @@ them with the help of L</glob> like this:
 
  glob copy-out /home/* .");
 
-  ("echo", (RErr,[]), -1, [], [],
+  ("echo", (RErr,[], []), -1, [], [],
    "display a line of text",
    " echo [params ...]
 
 This echos the parameters to the terminal.");
 
-  ("edit", (RErr,[]), -1, [FishAlias "vi"; FishAlias "emacs"], [],
+  ("edit", (RErr,[], []), -1, [FishAlias "vi"; FishAlias "emacs"], [],
    "edit a file",
    " edit filename
 
@@ -5291,7 +5303,7 @@ The editor is C<$EDITOR>.  However if you use the alternate
 commands C<vi> or C<emacs> you will get those corresponding
 editors.");
 
-  ("glob", (RErr,[]), -1, [], [],
+  ("glob", (RErr,[], []), -1, [], [],
    "expand wildcards in command",
    " glob command args...
 
@@ -5300,7 +5312,7 @@ repeatedly on each matching path.
 
 See L</WILDCARDS AND GLOBBING>.");
 
-  ("hexedit", (RErr,[]), -1, [], [],
+  ("hexedit", (RErr,[], []), -1, [], [],
    "edit with a hex editor",
    " hexedit <filename|device>
  hexedit <filename|device> <max>
@@ -5336,7 +5348,7 @@ environment variable.
 
 See also L</hexdump>.");
 
-  ("lcd", (RErr,[]), -1, [], [],
+  ("lcd", (RErr,[], []), -1, [], [],
    "change working directory",
    " lcd directory
 
@@ -5345,13 +5357,13 @@ itself.
 
 Note that C<!cd> won't do what you might expect.");
 
-  ("man", (RErr,[]), -1, [FishAlias "manual"], [],
+  ("man", (RErr,[], []), -1, [FishAlias "manual"], [],
    "open the manual",
    "  man
 
 Opens the manual page for guestfish.");
 
-  ("more", (RErr,[]), -1, [FishAlias "less"], [],
+  ("more", (RErr,[], []), -1, [FishAlias "less"], [],
    "view a file",
    " more filename
 
@@ -5362,7 +5374,7 @@ This is used to view a file.
 The default viewer is C<$PAGER>.  However if you use the alternate
 command C<less> you will get the C<less> command specifically.");
 
-  ("reopen", (RErr,[]), -1, [], [],
+  ("reopen", (RErr,[], []), -1, [], [],
    "close and reopen libguestfs handle",
    "  reopen
 
@@ -5370,7 +5382,7 @@ Close and reopen the libguestfs handle.  It is not necessary to use
 this normally, because the handle is closed properly when guestfish
 exits.  However this is occasionally useful for testing.");
 
-  ("sparse", (RErr,[]), -1, [], [],
+  ("sparse", (RErr,[], []), -1, [], [],
    "create a sparse disk image and add",
    " sparse filename size
 
@@ -5387,7 +5399,7 @@ For more advanced image creation, see L<qemu-img(1)> utility.
 
 Size can be specified using standard suffixes, eg. C<1M>.");
 
-  ("supported", (RErr,[]), -1, [], [],
+  ("supported", (RErr,[], []), -1, [], [],
    "list supported groups of commands",
    " supported
 
@@ -5397,7 +5409,7 @@ supported by this build of the libguestfs appliance.
 
 See also L<guestfs(3)/AVAILABILITY>.");
 
-  ("time", (RErr,[]), -1, [], [],
+  ("time", (RErr,[], []), -1, [], [],
    "print elapsed time taken to run a command",
    " time command args...
 
diff --git a/generator/generator_bindtests.ml b/generator/generator_bindtests.ml
index 66a5d55..adc10dc 100644
--- a/generator/generator_bindtests.ml
+++ b/generator/generator_bindtests.ml
@@ -69,7 +69,7 @@ print_strings (char *const *argv)
     | test0 :: tests -> test0, tests in
 
   let () =
-    let (name, style, _, _, _, _, _) = test0 in
+    let (name, (ret, args, _ as style), _, _, _, _, _) = test0 in
     generate_prototype ~extern:false ~semicolon:false ~newline:true
       ~handle:"g" ~prefix:"guestfs__" name style;
     pr "{\n";
@@ -93,7 +93,7 @@ print_strings (char *const *argv)
       | Bool n -> pr "  printf (\"%%s\\n\", %s ? \"true\" : \"false\");\n" n
       | Int n -> pr "  printf (\"%%d\\n\", %s);\n" n
       | Int64 n -> pr "  printf (\"%%\" PRIi64 \"\\n\", %s);\n" n
-    ) (snd style);
+    ) args;
     pr "  /* Java changes stdout line buffering so we need this: */\n";
     pr "  fflush (stdout);\n";
     pr "  return 0;\n";
@@ -101,13 +101,13 @@ print_strings (char *const *argv)
     pr "\n" in
 
   List.iter (
-    fun (name, style, _, _, _, _, _) ->
+    fun (name, (ret, args, _ as style), _, _, _, _, _) ->
       if String.sub name (String.length name - 3) 3 <> "err" then (
         pr "/* Test normal return. */\n";
         generate_prototype ~extern:false ~semicolon:false ~newline:true
           ~handle:"g" ~prefix:"guestfs__" name style;
         pr "{\n";
-        (match fst style with
+        (match ret with
          | RErr ->
              pr "  return 0;\n"
          | RInt _ ->
@@ -174,7 +174,7 @@ print_strings (char *const *argv)
           ~handle:"g" ~prefix:"guestfs__" name style;
         pr "{\n";
         pr "  error (g, \"error\");\n";
-        (match fst style with
+        (match ret with
          | RErr | RInt _ | RInt64 _ | RBool _ ->
              pr "  return -1;\n"
          | RConstString _ | RConstOptString _
diff --git a/generator/generator_c.ml b/generator/generator_c.ml
index ca0907d..8acbed2 100644
--- a/generator/generator_c.ml
+++ b/generator/generator_c.ml
@@ -30,15 +30,19 @@ open Generator_structs
 
 (* Generate C API. *)
 
+type optarg_proto = Dots | VA | Argv
+
 (* Generate a C function prototype. *)
 let rec generate_prototype ?(extern = true) ?(static = false)
     ?(semicolon = true)
     ?(single_line = false) ?(newline = false) ?(in_daemon = false)
-    ?(prefix = "")
-    ?handle name style =
+    ?(prefix = "") ?(suffix = "")
+    ?handle
+    ?(optarg_proto = Dots)
+    name (ret, args, optargs) =
   if extern then pr "extern ";
   if static then pr "static ";
-  (match fst style with
+  (match ret with
    | RErr -> pr "int "
    | RInt _ -> pr "int "
    | RInt64 _ -> pr "int64_t "
@@ -53,10 +57,10 @@ let rec generate_prototype ?(extern = true) ?(static = false)
        if not in_daemon then pr "struct guestfs_%s_list *" typ
        else pr "guestfs_int_%s_list *" typ
   );
-  let is_RBufferOut = match fst style with RBufferOut _ -> true | _ -> false in
-  pr "%s%s (" prefix name;
-  if handle = None && List.length (snd style) = 0 && not is_RBufferOut then
-    pr "void"
+  let is_RBufferOut = match ret with RBufferOut _ -> true | _ -> false in
+  pr "%s%s%s (" prefix name suffix;
+  if handle = None && args = [] && optargs = [] && not is_RBufferOut then
+      pr "void"
   else (
     let comma = ref false in
     (match handle with
@@ -92,15 +96,22 @@ let rec generate_prototype ?(extern = true) ?(static = false)
           pr "const char *%s" n;
           next ();
           pr "size_t %s_size" n
-    ) (snd style);
+    ) args;
     if is_RBufferOut then (next (); pr "size_t *size_r");
+    if optargs <> [] then (
+      next ();
+      match optarg_proto with
+      | Dots -> pr "..."
+      | VA -> pr "va_list args"
+      | Argv -> pr "const struct guestfs_%s_argv *optargs" name
+    );
   );
   pr ")";
   if semicolon then pr ";";
   if newline then pr "\n"
 
 (* Generate C call arguments, eg "(handle, foo, bar)" *)
-and generate_c_call_args ?handle style =
+and generate_c_call_args ?handle (ret, args, optargs) =
   pr "(";
   let comma = ref false in
   let next () =
@@ -119,28 +130,56 @@ and generate_c_call_args ?handle style =
     | arg ->
         next ();
         pr "%s" (name_of_argt arg)
-  ) (snd style);
+  ) args;
   (* For RBufferOut calls, add implicit &size parameter. *)
-  (match fst style with
+  (match ret with
    | RBufferOut _ ->
        next ();
        pr "&size"
    | _ -> ()
   );
+  (* For calls with optional arguments, add implicit optargs parameter. *)
+  if optargs <> [] then (
+    next ();
+    pr "optargs"
+  );
   pr ")"
 
 (* Generate the pod documentation for the C API. *)
 and generate_actions_pod () =
   List.iter (
-    fun (shortname, style, _, flags, _, _, longdesc) ->
+    fun (shortname, (ret, args, optargs as style), _, flags, _, _, longdesc) ->
       if not (List.mem NotInDocs flags) then (
         let name = "guestfs_" ^ shortname in
         pr "=head2 %s\n\n" name;
         pr " ";
         generate_prototype ~extern:false ~handle:"g" name style;
         pr "\n\n";
+
+        let uc_shortname = String.uppercase shortname in
+        if optargs <> [] then (
+          pr "You may supply a list of optional arguments to this call.\n";
+          pr "Use zero or more of the following pairs of parameters,\n";
+          pr "and terminate the list with C<-1> on its own.\n";
+          pr "See L</CALLS WITH OPTIONAL ARGUMENTS>.\n\n";
+          List.iter (
+            fun argt ->
+              let n = name_of_argt argt in
+              let uc_n = String.uppercase n in
+              pr " GUESTFS_%s_%s, " uc_shortname uc_n;
+              match argt with
+              | Bool n -> pr "int %s,\n" n
+              | Int n -> pr "int %s,\n" n
+              | Int64 n -> pr "int64_t %s,\n" n
+              | String n -> pr "const char *%s,\n" n
+              | _ -> assert false
+          ) optargs;
+          pr "\n";
+        );
+
         pr "%s\n\n" longdesc;
-        (match fst style with
+        let ret, args, optargs = style in
+        (match ret with
          | RErr ->
              pr "This function returns 0 on success or -1 on error.\n\n"
          | RInt _ ->
@@ -189,13 +228,34 @@ I<The caller must free the returned buffer after use>.\n\n"
           pr "%s\n\n" protocol_limit_warning;
         if List.mem DangerWillRobinson flags then
           pr "%s\n\n" danger_will_robinson;
-        if List.exists (function Key _ -> true | _ -> false) (snd style) then
+        if List.exists (function Key _ -> true | _ -> false) (args at optargs) then
           pr "This function takes a key or passphrase parameter which
 could contain sensitive material.  Read the section
 L</KEYS AND PASSPHRASES> for more information.\n\n";
-        match deprecation_notice flags with
-        | None -> ()
-        | Some txt -> pr "%s\n\n" txt
+        (match deprecation_notice flags with
+         | None -> ()
+         | Some txt -> pr "%s\n\n" txt
+        );
+
+        (* Handling of optional argument variants. *)
+        if optargs <> [] then (
+          pr "=head2 %s_va\n\n" name;
+          pr " ";
+          generate_prototype ~extern:false ~handle:"g"
+            ~prefix:"guestfs_" ~suffix:"_va" ~optarg_proto:VA
+            shortname style;
+          pr "\n\n";
+          pr "This is the \"va_list variant\" of L</%s>.\n\n" name;
+          pr "See L</CALLS WITH OPTIONAL ARGUMENTS>.\n\n";
+          pr "=head2 %s_argv\n\n" name;
+          pr " ";
+          generate_prototype ~extern:false ~handle:"g"
+            ~prefix:"guestfs_" ~suffix:"_argv" ~optarg_proto:Argv
+            shortname style;
+          pr "\n\n";
+          pr "This is the \"argv variant\" of L</%s>.\n\n" name;
+          pr "See L</CALLS WITH OPTIONAL ARGUMENTS>.\n\n";
+        );
       )
   ) all_functions_sorted
 
@@ -305,9 +365,7 @@ and generate_structs_h () =
 and generate_actions_h () =
   generate_header CStyle LGPLv2plus;
   List.iter (
-    fun (shortname, style, _, flags, _, _, _) ->
-      let name = "guestfs_" ^ shortname in
-
+    fun (shortname, (ret, args, optargs as style), _, flags, _, _, _) ->
       let deprecated =
         List.exists (function DeprecatedBy _ -> true | _ -> false) flags in
       let test0 =
@@ -318,7 +376,41 @@ and generate_actions_h () =
         pr "#define LIBGUESTFS_HAVE_%s 1\n" (String.uppercase shortname);
 
       generate_prototype ~single_line:true ~newline:true ~handle:"g"
-        name style
+        ~prefix:"guestfs_" shortname style;
+
+      if optargs <> [] then (
+        generate_prototype ~single_line:true ~newline:true ~handle:"g"
+          ~prefix:"guestfs_" ~suffix:"_va" ~optarg_proto:VA
+          shortname style;
+
+        pr "struct guestfs_%s_argv {\n" shortname;
+        pr "  uint64_t bitmask;\n";
+        iteri (
+          fun i argt ->
+            let c_type =
+              match argt with
+              | Bool n -> "int "
+              | Int n -> "int64_t "
+              | Int64 n -> "int "
+              | String n -> "const char *"
+              | _ -> assert false (* checked in generator_checks *) in
+            let uc_shortname = String.uppercase shortname in
+            let n = name_of_argt argt in
+            let uc_n = String.uppercase n in
+            pr "#define GUESTFS_%s_%s %d\n" uc_shortname uc_n i;
+            pr "#define GUESTFS_%s_%s_BITMASK (1ULL<<%d)\n" uc_shortname uc_n i;
+            pr "/* The field below is only valid in this struct if the\n";
+            pr " * GUESTFS_%s_%s_BITMASK bit is set\n" uc_shortname uc_n;
+            pr " * in the bitmask above, otherwise the contents are ignored.\n";
+            pr " */\n";
+            pr "  %s%s;\n" c_type n
+        ) optargs;
+        pr "};\n";
+
+        generate_prototype ~single_line:true ~newline:true ~handle:"g"
+          ~prefix:"guestfs_" ~suffix:"_argv" ~optarg_proto:Argv
+          shortname style;
+      );
   ) all_functions_sorted
 
 (* Generate the guestfs-internal-actions.h file. *)
@@ -326,9 +418,9 @@ and generate_internal_actions_h () =
   generate_header CStyle LGPLv2plus;
   List.iter (
     fun (shortname, style, _, _, _, _, _) ->
-      let name = "guestfs__" ^ shortname in
       generate_prototype ~single_line:true ~newline:true ~handle:"g"
-        name style
+        ~prefix:"guestfs__" ~optarg_proto:Argv
+        shortname style
   ) non_daemon_functions
 
 (* Generate the client-side dispatch stubs. *)
@@ -408,7 +500,7 @@ check_state (guestfs_h *g, const char *caller)
   (* Generate code to check String-like parameters are not passed in
    * as NULL (returning an error if they are).
    *)
-  let check_null_strings shortname style =
+  let check_null_strings shortname (ret, args, optargs) =
     let pr_newline = ref false in
     List.iter (
       function
@@ -426,7 +518,7 @@ check_state (guestfs_h *g, const char *caller)
           pr "  if (%s == NULL) {\n" n;
           pr "    error (g, \"%%s: %%s: parameter cannot be NULL\",\n";
           pr "           \"%s\", \"%s\");\n" shortname n;
-          pr "    return %s;\n" (error_code_of (fst style));
+          pr "    return %s;\n" (error_code_of ret);
           pr "  }\n";
           pr_newline := true
 
@@ -437,25 +529,60 @@ check_state (guestfs_h *g, const char *caller)
       | Bool _
       | Int _
       | Int64 _ -> ()
-    ) (snd style);
+    ) args;
+
+    (* For optional arguments. *)
+    List.iter (
+      function
+      | String n ->
+          pr "  if ((optargs->bitmask & GUESTFS_%s_%s_BITMASK) &&\n"
+            (String.uppercase shortname) (String.uppercase n);
+          pr "      optargs->%s == NULL) {\n" n;
+          pr "    error (g, \"%%s: %%s: optional parameter cannot be NULL\",\n";
+          pr "           \"%s\", \"%s\");\n" shortname n;
+          pr "    return %s;\n" (error_code_of ret);
+          pr "  }\n";
+          pr_newline := true
+
+      (* not applicable *)
+      | Bool _ | Int _ | Int64 _ -> ()
+
+      | _ -> assert false
+    ) optargs;
 
     if !pr_newline then pr "\n";
   in
 
+  (* Generate code to reject optargs we don't know about. *)
+  let reject_unknown_optargs shortname = function
+    | _, _, [] -> ()
+    | ret, _, optargs ->
+        let len = List.length optargs in
+        let mask = Int64.lognot (Int64.pred (Int64.shift_left 1L len)) in
+        pr "  if (optargs->bitmask & 0x%LxULL) {\n" mask;
+        pr "    error (g, \"%%s: unknown option in guestfs_%%s_argv->bitmask (this can happen if a program is compiled against a newer version of libguestfs, then dynamically linked to an older version)\",\n";
+        pr "           \"%s\", \"%s\");\n" shortname shortname;
+        pr "    return %s;\n" (error_code_of ret);
+        pr "  }\n";
+        pr "\n";
+  in
+
   (* Generate code to generate guestfish call traces. *)
-  let trace_call shortname style =
+  let trace_call shortname (ret, args, optargs) =
     pr "  if (guestfs__get_trace (g)) {\n";
 
     let needs_i =
       List.exists (function
                    | StringList _ | DeviceList _ -> true
-                   | _ -> false) (snd style) in
+                   | _ -> false) args in
     if needs_i then (
       pr "    size_t i;\n";
       pr "\n"
     );
 
     pr "    fprintf (stderr, \"%s\");\n" shortname;
+
+    (* Required arguments. *)
     List.iter (
       function
       | String n			(* strings *)
@@ -486,7 +613,29 @@ check_state (guestfs_h *g, const char *caller)
           pr "    fprintf (stderr, \" %%d\", %s);\n" n
       | Int64 n ->
           pr "    fprintf (stderr, \" %%\" PRIi64, %s);\n" n
-    ) (snd style);
+    ) args;
+
+    (* Optional arguments. *)
+    List.iter (
+      fun argt ->
+        let n = name_of_argt argt in
+        let uc_shortname = String.uppercase shortname in
+        let uc_n = String.uppercase n in
+        pr "    if (optargs->bitmask & GUESTFS_%s_%s_BITMASK)\n"
+          uc_shortname uc_n;
+        (match argt with
+         | String n ->
+             pr "      fprintf (stderr, \" \\\"%%s:%%s\\\"\", \"%s\", optargs->%s);\n" n n
+         | Bool n ->
+             pr "      fprintf (stderr, \" \\\"%%s:%%s\\\"\", \"%s\", optargs->%s ? \"true\" : \"false\");\n" n n
+         | Int n ->
+             pr "      fprintf (stderr, \" \\\"%%s:%%d\\\"\", \"%s\", optargs->%s);\n" n n
+         | Int64 n ->
+             pr "      fprintf (stderr, \" \\\"%%s:%%\" PRIi64 \"\\\"\", \"%s\", optargs->%s);\n" n n
+         | _ -> assert false
+        );
+    ) optargs;
+
     pr "    fputc ('\\n', stderr);\n";
     pr "  }\n";
     pr "\n";
@@ -494,13 +643,18 @@ check_state (guestfs_h *g, const char *caller)
 
   (* For non-daemon functions, generate a wrapper around each function. *)
   List.iter (
-    fun (shortname, style, _, _, _, _, _) ->
-      let name = "guestfs_" ^ shortname in
-
-      generate_prototype ~extern:false ~semicolon:false ~newline:true
-        ~handle:"g" name style;
+    fun (shortname, (_, _, optargs as style), _, _, _, _, _) ->
+      if optargs = [] then
+        generate_prototype ~extern:false ~semicolon:false ~newline:true
+          ~handle:"g" ~prefix:"guestfs_"
+          shortname style
+      else
+        generate_prototype ~extern:false ~semicolon:false ~newline:true
+          ~handle:"g" ~prefix:"guestfs_" ~suffix:"_argv" ~optarg_proto:Argv
+          shortname style;
       pr "{\n";
       check_null_strings shortname style;
+      reject_unknown_optargs shortname style;
       trace_call shortname style;
       pr "  return guestfs__%s " shortname;
       generate_c_call_args ~handle:"g" style;
@@ -511,17 +665,24 @@ check_state (guestfs_h *g, const char *caller)
 
   (* Client-side stubs for each function. *)
   List.iter (
-    fun (shortname, style, _, _, _, _, _) ->
+    fun (shortname, (ret, args, optargs as style), _, _, _, _, _) ->
+      if optargs <> [] then
+        failwithf "optargs not yet implemented for daemon functions";
+
       let name = "guestfs_" ^ shortname in
-      let error_code = error_code_of (fst style) in
+      let error_code = error_code_of ret in
 
       (* Generate the action stub. *)
-      generate_prototype ~extern:false ~semicolon:false ~newline:true
-        ~handle:"g" name style;
+      if optargs = [] then
+        generate_prototype ~extern:false ~semicolon:false ~newline:true
+          ~handle:"g" name style
+      else
+        generate_prototype ~extern:false ~semicolon:false ~newline:true
+          ~handle:"g" ~suffix:"_argv" ~optarg_proto:Argv name style;
 
       pr "{\n";
 
-      (match snd style with
+      (match args with
        | [] -> ()
        | _ -> pr "  struct %s_args args;\n" name
       );
@@ -529,7 +690,7 @@ check_state (guestfs_h *g, const char *caller)
       pr "  guestfs_message_header hdr;\n";
       pr "  guestfs_message_error err;\n";
       let has_ret =
-        match fst style with
+        match ret with
         | RErr -> false
         | RConstString _ | RConstOptString _ ->
             failwithf "RConstString|RConstOptString cannot be used by daemon functions"
@@ -544,6 +705,7 @@ check_state (guestfs_h *g, const char *caller)
       pr "  int r;\n";
       pr "\n";
       check_null_strings shortname style;
+      reject_unknown_optargs shortname style;
       trace_call shortname style;
       pr "  if (check_state (g, \"%s\") == -1) return %s;\n"
         shortname error_code;
@@ -551,7 +713,7 @@ check_state (guestfs_h *g, const char *caller)
       pr "\n";
 
       (* Send the main header and arguments. *)
-      (match snd style with
+      (match args with
        | [] ->
            pr "  serial = guestfs___send (g, GUESTFS_PROC_%s, NULL, NULL);\n"
              (String.uppercase shortname)
@@ -609,7 +771,7 @@ check_state (guestfs_h *g, const char *caller)
             need_read_reply_label := true;
             pr "\n";
         | _ -> ()
-      ) (snd style);
+      ) args;
 
       (* Wait for the reply from the remote end. *)
       if !need_read_reply_label then pr " read_reply:\n";
@@ -655,11 +817,11 @@ check_state (guestfs_h *g, const char *caller)
             pr "  }\n";
             pr "\n";
         | _ -> ()
-      ) (snd style);
+      ) args;
 
       pr "  guestfs___end_busy (g);\n";
 
-      (match fst style with
+      (match ret with
        | RErr -> pr "  return 0;\n"
        | RInt n | RInt64 n | RBool n ->
            pr "  return ret.%s;\n" n
@@ -728,6 +890,95 @@ check_state (guestfs_h *g, const char *caller)
 
   ) structs;
 
+  (* Functions which have optional arguments have two generated variants. *)
+  List.iter (
+    function
+    | shortname, (ret, args, (_::_ as optargs) as style), _, _, _, _, _ ->
+        let uc_shortname = String.uppercase shortname in
+
+        (* Get the name of the last regular argument. *)
+        let last_arg =
+          match args with
+          | [] -> "g"
+          | args -> name_of_argt (List.hd (List.rev args)) in
+
+        let rerrcode, rtype =
+          match ret with
+          | RErr | RInt _ | RBool _ -> "-1", "int "
+          | RInt64 _ -> "-1", "int64_t "
+          | RConstString _ | RConstOptString _ -> "NULL", "const char *"
+          | RString _ | RBufferOut _ -> "NULL", "char *"
+          | RStringList _ | RHashtable _ -> "NULL", "char **"
+          | RStruct (_, typ) -> "NULL", sprintf "struct guestfs_%s *" typ
+          | RStructList (_, typ) ->
+              "NULL", sprintf "struct guestfs_%s_list *" typ in
+
+        (* The regular variable args function, just calls the _va variant. *)
+        generate_prototype ~extern:false ~semicolon:false ~newline:true
+          ~handle:"g" ~prefix:"guestfs_" shortname style;
+        pr "{\n";
+        pr "  va_list optargs;\n";
+        pr "\n";
+        pr "  va_start (optargs, %s);\n" last_arg;
+        pr "  %sr = guestfs_%s_va " rtype shortname;
+        generate_c_call_args ~handle:"g" style;
+        pr ";\n";
+        pr "  va_end (optargs);\n";
+        pr "\n";
+        pr "  return r;\n";
+        pr "}\n\n";
+
+        generate_prototype ~extern:false ~semicolon:false ~newline:true
+          ~handle:"g" ~prefix:"guestfs_" ~suffix:"_va" ~optarg_proto:VA
+          shortname style;
+        pr "{\n";
+        pr "  struct guestfs_%s_argv optargs_s;\n" shortname;
+        pr "  struct guestfs_%s_argv *optargs = &optargs_s;\n" shortname;
+        pr "  int i;\n";
+        pr "\n";
+        pr "  optargs_s.bitmask = 0;\n";
+        pr "\n";
+        pr "  while ((i = va_arg (args, int)) >= 0) {\n";
+        pr "    switch (i) {\n";
+
+        List.iter (
+          fun argt ->
+            let n = name_of_argt argt in
+            let uc_n = String.uppercase n in
+            pr "    case GUESTFS_%s_%s:\n" uc_shortname uc_n;
+            pr "      optargs_s.%s = va_arg (args, " n;
+            (match argt with
+             | Bool _ | Int _ -> pr "int"
+             | Int64 _ -> pr "int64_t"
+             | String _ -> pr "const char *"
+             | _ -> assert false
+            );
+            pr ");\n";
+            pr "      break;\n";
+        ) optargs;
+
+        pr "    default:\n";
+        pr "      error (g, \"%%s: unknown option %%d (this can happen if a program is compiled against a newer version of libguestfs, then dynamically linked to an older version)\",\n";
+        pr "             \"%s\", i);\n" shortname;
+        pr "      return %s;\n" rerrcode;
+        pr "    }\n";
+        pr "\n";
+        pr "    uint64_t i_mask = 1ULL << i;\n";
+        pr "    if (optargs_s.bitmask & i_mask) {\n";
+        pr "      error (g, \"%%s: same optional argument specified more than once\",\n";
+        pr "             \"%s\");\n" shortname;
+        pr "      return %s;\n" rerrcode;
+        pr "    }\n";
+        pr "    optargs_s.bitmask |= i_mask;\n";
+        pr "  }\n";
+        pr "\n";
+        pr "  return guestfs_%s_argv " shortname;
+        generate_c_call_args ~handle:"g" style;
+        pr ";\n";
+        pr "}\n\n"
+    | _ -> ()
+  ) all_functions_sorted
+
 (* Generate the linker script which controls the visibility of
  * symbols in the public ABI and ensures no other symbols get
  * exported accidentally.
@@ -761,8 +1012,16 @@ and generate_linker_script () =
     "guestfs_tmpdir";
   ] in
   let functions =
-    List.map (fun (name, _, _, _, _, _, _) -> "guestfs_" ^ name)
-      all_functions in
+    List.flatten (
+      List.map (
+        function
+        | name, (_, _, []), _, _, _, _, _ -> ["guestfs_" ^ name]
+        | name, (_, _, _), _, _, _, _, _ ->
+            ["guestfs_" ^ name;
+             "guestfs_" ^ name ^ "_va";
+             "guestfs_" ^ name ^ "_argv"]
+      ) all_functions
+    ) in
   let structs =
     List.concat (
       List.map (fun (typ, _) ->
diff --git a/generator/generator_capitests.ml b/generator/generator_capitests.ml
index 594c867..b41e288 100644
--- a/generator/generator_capitests.ml
+++ b/generator/generator_capitests.ml
@@ -720,6 +720,13 @@ and generate_test_command_call ?(expect_error = false) ?test test_name cmd =
         with Not_found ->
           failwithf "%s: in test, command %s was not found" test_name name in
 
+      (* If the call has optional args, fold them all together.  We cannot
+       * test partial optional args yet.
+       *)
+      let style =
+        let ret, args, optargs = style in
+        ret, args at optargs in
+
       if List.length (snd style) <> List.length args then
         failwithf "%s: in test, wrong number of args given to %s"
           test_name name;
diff --git a/generator/generator_checks.ml b/generator/generator_checks.ml
index 98649e2..3474047 100644
--- a/generator/generator_checks.ml
+++ b/generator/generator_checks.ml
@@ -101,7 +101,9 @@ let () =
           failwithf "%s has param/ret using reserved word %s" name n;
       in
 
-      (match fst style with
+      let ret, args, optargs = style in
+
+      (match ret with
        | RErr -> ()
        | RInt n | RInt64 n | RBool n
        | RConstString n | RConstOptString n | RString n
@@ -109,7 +111,22 @@ let () =
        | RHashtable n | RBufferOut n ->
            check_arg_ret_name n
       );
-      List.iter (fun arg -> check_arg_ret_name (name_of_argt arg)) (snd style)
+      List.iter (fun arg -> check_arg_ret_name (name_of_argt arg)) args;
+      List.iter (fun arg -> check_arg_ret_name (name_of_argt arg)) optargs;
+  ) all_functions;
+
+  (* Check only certain types allowed in optargs. *)
+  List.iter (
+    fun (name, (_, _, optargs), _, _, _, _, _) ->
+      if List.length optargs > 64 then
+        failwithf "maximum of 64 optional args allowed for %s" name;
+
+      List.iter (
+        function
+        | Bool _ | Int _ | Int64 _ | String _ -> ()
+        | _ ->
+            failwithf "optional args of %s can only have type Bool|Int|Int64|String" name
+      ) optargs
   ) all_functions;
 
   (* Check short descriptions. *)
diff --git a/generator/generator_csharp.ml b/generator/generator_csharp.ml
index 1b2672b..e178945 100644
--- a/generator/generator_csharp.ml
+++ b/generator/generator_csharp.ml
@@ -44,7 +44,8 @@ let rec generate_csharp () =
 // 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#.
+// we haven't worked out the correct way to do this from C#.  Also
+// we don't handle functions that take optional arguments at all.
 //
 // The third issue is that when compiling you get a lot of warnings.
 // We are not sure whether the warnings are important or not.
@@ -134,9 +135,9 @@ namespace Guestfs
 
   (* Generate C# function bindings. *)
   List.iter (
-    fun (name, style, _, _, _, shortdesc, _) ->
+    fun (name, (ret, args, optargs), _, _, _, shortdesc, _) ->
       let rec csharp_return_type () =
-        match fst style with
+        match ret with
         | RErr -> "void"
         | RBool n -> "bool"
         | RInt n -> "int"
@@ -151,7 +152,7 @@ namespace Guestfs
         | RStructList (_,n) -> sprintf "_%s[]" n
 
       and c_return_type () =
-        match fst style with
+        match ret with
         | RErr
         | RBool _
         | RInt _ -> "int"
@@ -166,7 +167,7 @@ namespace Guestfs
         | RStructList (_,n) -> sprintf "_%s[]" n
 
       and c_error_comparison () =
-        match fst style with
+        match ret with
         | RErr
         | RBool _
         | RInt _
@@ -198,7 +199,7 @@ namespace Guestfs
               pr ", int %s" n
           | Int64 n ->
               pr ", long %s" n
-        ) (snd style);
+        ) args;
         pr ");\n"
 
       and generate_public_prototype () =
@@ -223,12 +224,12 @@ namespace Guestfs
               next (); pr "int %s" n
           | Int64 n ->
               next (); pr "long %s" n
-        ) (snd style);
+        ) args;
         pr ")\n"
 
       and generate_call () =
         pr "guestfs_%s (_handle" name;
-        List.iter (fun arg -> pr ", %s" (name_of_argt arg)) (snd style);
+        List.iter (fun arg -> pr ", %s" (name_of_argt arg)) args;
         pr ");\n";
       in
 
@@ -245,7 +246,7 @@ namespace Guestfs
       generate_call ();
       pr "      if (r %s)\n" (c_error_comparison ());
       pr "        throw new Error (guestfs_last_error (_handle));\n";
-      (match fst style with
+      (match ret with
        | RErr -> ()
        | RBool _ ->
            pr "      return r != 0 ? true : false;\n"
diff --git a/generator/generator_daemon.ml b/generator/generator_daemon.ml
index 4ac2a6e..9d9fb0c 100644
--- a/generator/generator_daemon.ml
+++ b/generator/generator_daemon.ml
@@ -63,12 +63,15 @@ and generate_daemon_actions () =
   pr "\n";
 
   List.iter (
-    fun (name, style, _, _, _, _, _) ->
+    fun (name, (ret, args, optargs), _, _, _, _, _) ->
+      if optargs <> [] then
+        failwithf "optional arguments not supported in the daemon yet";
+
       (* Generate server-side stubs. *)
       pr "static void %s_stub (XDR *xdr_in)\n" name;
       pr "{\n";
       let error_code =
-        match fst style with
+        match ret with
         | RErr | RInt _ -> pr "  int r;\n"; "-1"
         | RInt64 _ -> pr "  int64_t r;\n"; "-1"
         | RBool _ -> pr "  int r;\n"; "-1"
@@ -83,7 +86,7 @@ and generate_daemon_actions () =
             pr "  char *r;\n";
             "NULL" in
 
-      (match snd style with
+      (match args with
        | [] -> ()
        | args ->
            pr "  struct guestfs_%s_args args;\n" name;
@@ -107,9 +110,9 @@ and generate_daemon_actions () =
       pr "\n";
 
       let is_filein =
-        List.exists (function FileIn _ -> true | _ -> false) (snd style) in
+        List.exists (function FileIn _ -> true | _ -> false) args in
 
-      (match snd style with
+      (match args with
        | [] -> ()
        | args ->
            pr "  memset (&args, 0, sizeof args);\n";
@@ -176,7 +179,7 @@ and generate_daemon_actions () =
       );
 
       (* this is used at least for do_equal *)
-      if List.exists (function Pathname _ -> true | _ -> false) (snd style) then (
+      if List.exists (function Pathname _ -> true | _ -> false) args then (
         (* Emit NEED_ROOT just once, even when there are two or
            more Pathname args *)
         pr "  NEED_ROOT (%s, goto done);\n"
@@ -187,13 +190,12 @@ and generate_daemon_actions () =
        * parameters, since these go "outside" the RPC protocol.
        *)
       let args' =
-        List.filter (function FileIn _ | FileOut _ -> false | _ -> true)
-          (snd style) in
+        List.filter (function FileIn _ | FileOut _ -> false | _ -> true) args in
       pr "  r = do_%s " name;
-      generate_c_call_args (fst style, args');
+      generate_c_call_args (ret, args', optargs);
       pr ";\n";
 
-      (match fst style with
+      (match ret with
        | RErr | RInt _ | RInt64 _ | RBool _
        | RConstString _ | RConstOptString _
        | RString _ | RStringList _ | RHashtable _
@@ -216,11 +218,11 @@ and generate_daemon_actions () =
        * send its own reply.
        *)
       let no_reply =
-        List.exists (function FileOut _ -> true | _ -> false) (snd style) in
+        List.exists (function FileOut _ -> true | _ -> false) args in
       if no_reply then
         pr "  /* do_%s has already sent a reply */\n" name
       else (
-        match fst style with
+        match ret with
         | RErr -> pr "  reply (NULL, NULL);\n"
         | RInt n | RInt64 n | RBool n ->
             pr "  struct guestfs_%s_ret ret;\n" name;
@@ -267,7 +269,7 @@ and generate_daemon_actions () =
 
       (* Free the args. *)
       pr "done:\n";
-      (match snd style with
+      (match args with
        | [] -> ()
        | _ ->
            pr "  xdr_free ((xdrproc_t) xdr_guestfs_%s_args, (char *) &args);\n"
@@ -283,7 +285,7 @@ and generate_daemon_actions () =
   pr "  switch (proc_nr) {\n";
 
   List.iter (
-    fun (name, style, _, _, _, _, _) ->
+    fun (name, _, _, _, _, _, _) ->
       pr "    case GUESTFS_PROC_%s:\n" (String.uppercase name);
       pr "      %s_stub (xdr_in);\n" name;
       pr "      break;\n"
diff --git a/generator/generator_fish.ml b/generator/generator_fish.ml
index 749c661..21819f2 100644
--- a/generator/generator_fish.ml
+++ b/generator/generator_fish.ml
@@ -84,7 +84,7 @@ let generate_fish_cmds () =
   pr "{\n";
 
   List.iter (
-    fun (name, style, _, flags, _, shortdesc, longdesc) ->
+    fun (name, _, _, flags, _, shortdesc, longdesc) ->
       let name2 = replace_char name '_' '-' in
       let aliases =
         filter_map (function FishAlias n -> Some n | _ -> None) flags in
@@ -113,21 +113,25 @@ let generate_fish_cmds () =
   ) fish_commands;
 
   List.iter (
-    fun (name, style, _, flags, _, shortdesc, longdesc) ->
+    fun (name, (_, args, optargs), _, flags, _, shortdesc, longdesc) ->
       let name2 = replace_char name '_' '-' in
       let aliases =
         filter_map (function FishAlias n -> Some n | _ -> None) flags in
       let longdesc = replace_str longdesc "C<guestfs_" "C<" in
       let synopsis =
-        match snd style with
+        match args with
         | [] -> name2
         | args ->
             let args = List.filter (function Key _ -> false | _ -> true) args in
-            sprintf "%s %s"
-              name2 (String.concat " " (List.map name_of_argt args)) in
+            sprintf "%s%s%s"
+              name2
+              (String.concat ""
+                 (List.map (fun arg -> " " ^ name_of_argt arg) args))
+              (String.concat ""
+                 (List.map (fun arg -> sprintf " [%s:..]" (name_of_argt arg)) args)) in
 
       let warnings =
-        if List.exists (function Key _ -> true | _ -> false) (snd style) then
+        if List.exists (function Key _ -> true | _ -> false) args then
           "\n\nThis command has one or more key or passphrase parameters.
 Guestfish will prompt for these separately."
         else "" in
@@ -274,10 +278,10 @@ Guestfish will prompt for these separately."
 
   (* run_<action> actions *)
   List.iter (
-    fun (name, style, _, flags, _, _, _) ->
+    fun (name, (ret, args, optargs as style), _, flags, _, _, _) ->
       pr "static int run_%s (const char *cmd, size_t argc, char *argv[])\n" name;
       pr "{\n";
-      (match fst style with
+      (match ret with
        | RErr
        | RInt _
        | RBool _ -> pr "  int r;\n"
@@ -308,26 +312,45 @@ Guestfish will prompt for these separately."
         | Bool n -> pr "  int %s;\n" n
         | Int n -> pr "  int %s;\n" n
         | Int64 n -> pr "  int64_t %s;\n" n
-      ) (snd style);
+      ) args;
+
+      if optargs <> [] then (
+        pr "  struct guestfs_%s_argv optargs_s = { .bitmask = 0 };\n" name;
+        pr "  struct guestfs_%s_argv *optargs = &optargs_s;\n" name
+      );
+
+      if args <> [] || optargs <> [] then
+        pr "  size_t i = 0;\n";
+
+      pr "\n";
 
       (* Check and convert parameters. *)
-      let argc_expected =
+      let argc_minimum, argc_maximum =
         let args_no_keys =
-          List.filter (function Key _ -> false | _ -> true) (snd style) in
-        List.length args_no_keys in
-      pr "  if (argc != %d) {\n" argc_expected;
-      pr "    fprintf (stderr, _(\"%%s should have %%d parameter(s)\\n\"), cmd, %d);\n"
-        argc_expected;
+          List.filter (function Key _ -> false | _ -> true) args in
+        let argc_minimum = List.length args_no_keys in
+        let argc_maximum = argc_minimum + List.length optargs in
+        argc_minimum, argc_maximum in
+
+      if argc_minimum = argc_maximum then (
+        pr "  if (argc != %d) {\n" argc_minimum;
+        pr "    fprintf (stderr, _(\"%%s should have %%d parameter(s)\\n\"), cmd, %d);\n"
+          argc_minimum;
+      ) else (
+        pr "  if (argc < %d || argc > %d) {\n" argc_minimum argc_maximum;
+        pr "    fprintf (stderr, _(\"%%s should have %%d-%%d parameter(s)\\n\"), cmd, %d, %d);\n"
+          argc_minimum argc_maximum;
+      );
       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 =
+      let parse_integer expr fn fntyp rtyp range name =
         pr "  {\n";
         pr "    strtol_error xerr;\n";
         pr "    %s r;\n" fntyp;
         pr "\n";
-        pr "    xerr = %s (argv[i++], NULL, 0, &r, xstrtol_suffixes);\n" fn;
+        pr "    xerr = %s (%s, NULL, 0, &r, xstrtol_suffixes);\n" fn expr;
         pr "    if (xerr != LONGINT_OK) {\n";
         pr "      fprintf (stderr,\n";
         pr "               _(\"%%s: %%s: invalid integer parameter (%%s returned %%d)\\n\"),\n";
@@ -349,9 +372,6 @@ Guestfish will prompt for these separately."
         pr "  }\n";
       in
 
-      if snd style <> [] then
-        pr "  size_t i = 0;\n";
-
       List.iter (
         function
         | Device name
@@ -389,13 +409,67 @@ Guestfish will prompt for these separately."
               and comment =
                 "The Int type in the generator is a signed 31 bit int." in
               Some (min, max, comment) in
-            parse_integer "xstrtoll" "long long" "int" range name
+            parse_integer "argv[i++]" "xstrtoll" "long long" "int" range name
         | Int64 name ->
-            parse_integer "xstrtoll" "long long" "int64_t" None name
-      ) (snd style);
+            parse_integer "argv[i++]" "xstrtoll" "long long" "int64_t" None name
+      ) args;
+
+      (* Optional arguments are prefixed with <argname>:<value> and
+       * may be missing, so we need to parse those until the end of
+       * the argument list.
+       *)
+      if optargs <> [] then (
+        let uc_name = String.uppercase name in
+        pr "\n";
+        pr "  for (; i < argc; ++i) {\n";
+        pr "    uint64_t this_mask;\n";
+        pr "    const char *this_arg;\n";
+        List.iter (
+          fun argt ->
+            let n = name_of_argt argt in
+            let uc_n = String.uppercase n in
+            let len = String.length n in
+            pr "    if (STRPREFIX (argv[i], \"%s:\")) {\n" n;
+            (match argt with
+             | Bool n ->
+                 pr "      optargs_s.%s = is_true (&argv[i][%d]) ? 1 : 0;\n"
+                   n (len+1);
+             | Int n ->
+                 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
+                 let expr = sprintf "&argv[i][%d]" (len+1) in
+                 parse_integer expr "xstrtoll" "long long" "int" range name
+             | Int64 n ->
+                 let expr = sprintf "&argv[i][%d]" (len+1) in
+                 parse_integer expr "xstrtoll" "long long" "int64_t" None name
+             | String n ->
+                 pr "      optargs_s.%s = &argv[i][%d];\n" n (len+1);
+             | _ -> assert false
+            );
+            pr "      this_mask = GUESTFS_%s_%s_BITMASK;\n" uc_name uc_n;
+            pr "      this_arg = \"%s\";\n" n;
+            pr "    }\n";
+        ) optargs;
+
+        pr "    if (optargs_s.bitmask & this_mask) {\n";
+        pr "      fprintf (stderr, _(\"%%s: optional argument %%s given twice\\n\"),\n";
+        pr "               cmd, this_arg);\n";
+        pr "      return -1;\n";
+        pr "    }\n";
+        pr "    optargs_s.bitmask |= this_mask;\n";
+        pr "  }\n";
+        pr "\n";
+      );
 
       (* Call C API function. *)
-      pr "  r = guestfs_%s " name;
+      if optargs = [] then
+        pr "  r = guestfs_%s " name
+      else
+        pr "  r = guestfs_%s_argv " name;
       generate_c_call_args ~handle:"g" style;
       pr ";\n";
 
@@ -412,7 +486,7 @@ Guestfish will prompt for these separately."
             pr "  free_file_in (%s);\n" name
         | StringList name | DeviceList name ->
             pr "  free_strings (%s);\n" name
-      ) (snd style);
+      ) args;
 
       (* Any output flags? *)
       let fish_output =
@@ -426,7 +500,7 @@ Guestfish will prompt for these separately."
             failwithf "%s: more than one FishOutput flag is not allowed" name in
 
       (* Check return value for errors and display command results. *)
-      (match fst style with
+      (match ret with
        | RErr -> pr "  return r;\n"
        | RInt _ ->
            pr "  if (r == -1) return -1;\n";
@@ -637,7 +711,7 @@ and generate_fish_actions_pod () =
   let rex = Str.regexp "C<guestfs_\\([^>]+\\)>" in
 
   List.iter (
-    fun (name, style, _, flags, _, _, longdesc) ->
+    fun (name, (_, args, optargs), _, flags, _, _, longdesc) ->
       let longdesc =
         Str.global_substitute rex (
           fun s ->
@@ -668,19 +742,27 @@ and generate_fish_actions_pod () =
         | FileIn n | FileOut n -> pr " (%s|-)" n
         | BufferIn n -> pr " %s" n
         | Key _ -> () (* keys are entered at a prompt *)
-      ) (snd style);
+      ) args;
+      List.iter (
+        function
+        | Bool n | Int n | Int64 n | String n -> pr " [%s:..]" n
+        | _ -> assert false
+      ) optargs;
       pr "\n";
       pr "\n";
       pr "%s\n\n" longdesc;
 
       if List.exists (function FileIn _ | FileOut _ -> true
-                      | _ -> false) (snd style) then
+                      | _ -> false) args then
         pr "Use C<-> instead of a filename to read/write from stdin/stdout.\n\n";
 
-      if List.exists (function Key _ -> true | _ -> false) (snd style) then
+      if List.exists (function Key _ -> true | _ -> false) args then
         pr "This command has one or more key or passphrase parameters.
 Guestfish will prompt for these separately.\n\n";
 
+      if optargs <> [] then
+        pr "This command has one or more optional arguments.  See L</OPTIONAL ARGUMENTS>.\n\n";
+
       if List.mem ProtocolLimitWarning flags then
         pr "%s\n\n" protocol_limit_warning;
 
@@ -695,7 +777,7 @@ Guestfish will prompt for these separately.\n\n";
 (* Generate documentation for guestfish-only commands. *)
 and generate_fish_commands_pod () =
   List.iter (
-    fun (name, style, _, flags, _, _, longdesc) ->
+    fun (name, _, _, flags, _, _, longdesc) ->
       let name = replace_char name '_' '-' in
       let aliases =
         filter_map (function FishAlias n -> Some n | _ -> None) flags in
diff --git a/generator/generator_haskell.ml b/generator/generator_haskell.ml
index a125cbd..b49e385 100644
--- a/generator/generator_haskell.ml
+++ b/generator/generator_haskell.ml
@@ -36,18 +36,19 @@ let rec generate_haskell_hs () =
    *)
   let can_generate style =
     match style with
-    | RErr, _
-    | RInt _, _
-    | RInt64 _, _ -> true
-    | RBool _, _
-    | RConstString _, _
-    | RConstOptString _, _
-    | RString _, _
-    | RStringList _, _
-    | RStruct _, _
-    | RStructList _, _
-    | RHashtable _, _
-    | RBufferOut _, _ -> false in
+    | _, _, (_::_) -> false (* no optional args yet *)
+    | RErr, _, []
+    | RInt _, _, []
+    | RInt64 _, _, [] -> true
+    | RBool _, _, []
+    | RConstString _, _, []
+    | RConstOptString _, _, []
+    | RString _, _, []
+    | RStringList _, _, []
+    | RStruct _, _, []
+    | RStructList _, _, []
+    | RHashtable _, _, []
+    | RBufferOut _, _, [] -> false in
 
   pr "\
 {-# INCLUDE <guestfs.h> #-}
@@ -123,7 +124,7 @@ last_error h = do
 
   (* Generate wrappers for each foreign function. *)
   List.iter (
-    fun (name, style, _, _, _, _, _) ->
+    fun (name, (ret, args, optargs as style), _, _, _, _, _) ->
       if can_generate style then (
         pr "foreign import ccall unsafe \"guestfs_%s\" c_%s\n" name name;
         pr "  :: ";
@@ -134,7 +135,7 @@ last_error h = do
         generate_haskell_prototype ~handle:"GuestfsH" ~hs:true style;
         pr "\n";
         pr "%s %s = do\n" name
-          (String.concat " " ("h" :: List.map name_of_argt (snd style)));
+          (String.concat " " ("h" :: List.map name_of_argt args));
         pr "  r <- ";
         (* Convert pointer arguments using with* functions. *)
         List.iter (
@@ -148,7 +149,7 @@ last_error h = do
           | OptString n -> pr "maybeWith withCString %s $ \\%s -> " n n
           | StringList n | DeviceList n -> pr "withMany withCString %s $ \\%s -> withArray0 nullPtr %s $ \\%s -> " n n n n
           | Bool _ | Int _ | Int64 _ -> ()
-        ) (snd style);
+        ) args;
         (* Convert integer arguments. *)
         let args =
           List.map (
@@ -162,10 +163,10 @@ last_error h = do
             | StringList n | DeviceList n
             | Key n -> n
             | BufferIn n -> sprintf "%s (fromIntegral %s_size)" n n
-          ) (snd style) in
+          ) args in
         pr "withForeignPtr h (\\p -> c_%s %s)\n" name
           (String.concat " " ("p" :: args));
-        (match fst style with
+        (match ret with
          | RErr | RInt _ | RInt64 _ | RBool _ ->
              pr "  if (r == -1)\n";
              pr "    then do\n";
@@ -179,7 +180,7 @@ last_error h = do
              pr "      err <- last_error h\n";
              pr "      fail err\n";
         );
-        (match fst style with
+        (match ret with
          | RErr ->
              pr "    else return ()\n"
          | RInt _ ->
@@ -202,7 +203,7 @@ last_error h = do
       )
   ) all_functions
 
-and generate_haskell_prototype ~handle ?(hs = false) style =
+and generate_haskell_prototype ~handle ?(hs = false) (ret, args, optargs) =
   pr "%s -> " handle;
   let string = if hs then "String" else "CString" in
   let int = if hs then "Int" else "CInt" in
@@ -225,9 +226,9 @@ and generate_haskell_prototype ~handle ?(hs = false) style =
        | FileOut _ -> pr "%s" string
       );
       pr " -> ";
-  ) (snd style);
+  ) args;
   pr "IO (";
-  (match fst style with
+  (match ret with
    | RErr -> if not hs then pr "CInt"
    | RInt _ -> pr "%s" int
    | RInt64 _ -> pr "%s" int64
diff --git a/generator/generator_java.ml b/generator/generator_java.ml
index 2ccb1b6..b551740 100644
--- a/generator/generator_java.ml
+++ b/generator/generator_java.ml
@@ -100,10 +100,14 @@ public class GuestFS {
 ";
 
   List.iter (
-    fun (name, style, _, flags, _, shortdesc, longdesc) ->
+    fun (name, (ret, args, optargs as style), _, flags, _, shortdesc, longdesc) ->
       if not (List.mem NotInDocs flags); then (
         let doc = replace_str longdesc "C<guestfs_" "C<g." in
         let doc =
+          if optargs <> [] then
+            doc ^ "\n\nOptional arguments are supplied in the final Map<String,Object> parameter, which is a hash of the argument name to its value (cast to Object).  Pass an empty Map for no optional arguments."
+          else doc in
+        let doc =
           if List.mem ProtocolLimitWarning flags then
             doc ^ "\n\n" ^ protocol_limit_warning
           else doc in
@@ -138,9 +142,9 @@ public class GuestFS {
       pr "      throw new LibGuestFSException (\"%s: handle is closed\");\n"
         name;
       pr "    ";
-      if fst style <> RErr then pr "return ";
+      if ret <> RErr then pr "return ";
       pr "_%s " name;
-      generate_java_call_args ~handle:"g" (snd style);
+      generate_java_call_args ~handle:"g" style;
       pr ";\n";
       pr "  }\n";
       pr "  ";
@@ -152,19 +156,20 @@ public class GuestFS {
   pr "}\n"
 
 (* Generate Java call arguments, eg "(handle, foo, bar)" *)
-and generate_java_call_args ~handle args =
+and generate_java_call_args ~handle (_, args, optargs) =
   pr "(%s" handle;
   List.iter (fun arg -> pr ", %s" (name_of_argt arg)) args;
+  if optargs <> [] then pr ", optargs";
   pr ")"
 
 and generate_java_prototype ?(public=false) ?(privat=false) ?(native=false)
-    ?(semicolon=true) name style =
+    ?(semicolon=true) name (ret, args, optargs) =
   if privat then pr "private ";
   if public then pr "public ";
   if native then pr "native ";
 
   (* return type *)
-  (match fst style with
+  (match ret with
    | RErr -> pr "void ";
    | RInt _ -> pr "int ";
    | RInt64 _ -> pr "long ";
@@ -214,7 +219,13 @@ and generate_java_prototype ?(public=false) ?(privat=false) ?(native=false)
           pr "int %s" n
       | Int64 n ->
           pr "long %s" n
-  ) (snd style);
+  ) args;
+
+  if optargs <> [] then (
+    if !needs_comma then pr ", ";
+    needs_comma := true;
+    pr "HashMap optargs"
+  );
 
   pr ")\n";
   pr "    throws LibGuestFSException";
@@ -299,9 +310,9 @@ Java_com_redhat_et_libguestfs_GuestFS__1close
 ";
 
   List.iter (
-    fun (name, style, _, _, _, _, _) ->
+    fun (name, (ret, args, optargs as style), _, _, _, _, _) ->
       pr "JNIEXPORT ";
-      (match fst style with
+      (match ret with
        | RErr -> pr "void ";
        | RInt _ -> pr "jint ";
        | RInt64 _ -> pr "jlong ";
@@ -338,12 +349,14 @@ Java_com_redhat_et_libguestfs_GuestFS__1close
             pr ", jint j%s" n
         | Int64 n ->
             pr ", jlong j%s" n
-      ) (snd style);
+      ) args;
+      if optargs <> [] then
+        pr ", jobject joptargs";
       pr ")\n";
       pr "{\n";
       pr "  guestfs_h *g = (guestfs_h *) (long) jg;\n";
       let error_code, no_ret =
-        match fst style with
+        match ret with
         | RErr -> pr "  int r;\n"; "-1", ""
         | RBool _
         | RInt _ -> pr "  int r;\n"; "-1", "0"
@@ -397,10 +410,10 @@ Java_com_redhat_et_libguestfs_GuestFS__1close
             pr "  int %s;\n" n
         | Int64 n ->
             pr "  int64_t %s;\n" n
-      ) (snd style);
+      ) args;
 
       let needs_i =
-        (match fst style with
+        (match ret with
          | RStringList _ | RStructList _ -> true
          | RErr | RBool _ | RInt _ | RInt64 _ | RConstString _
          | RConstOptString _
@@ -408,7 +421,7 @@ Java_com_redhat_et_libguestfs_GuestFS__1close
           List.exists (function
                        | StringList _ -> true
                        | DeviceList _ -> true
-                       | _ -> false) (snd style) in
+                       | _ -> false) args in
       if needs_i then
         pr "  size_t i;\n";
 
@@ -445,10 +458,20 @@ Java_com_redhat_et_libguestfs_GuestFS__1close
         | Int n
         | Int64 n ->
             pr "  %s = j%s;\n" n n
-      ) (snd style);
+      ) args;
+
+      if optargs <> [] then (
+        (* XXX *)
+        pr "  throw_exception (env, \"%s: internal error: please let us know how to read a Java HashMap parameter from JNI bindings!\");\n" name;
+        pr "  return NULL;\n";
+        pr "  /*\n";
+      );
 
       (* Make the call. *)
-      pr "  r = guestfs_%s " name;
+      if optargs = [] then
+        pr "  r = guestfs_%s " name
+      else
+        pr "  r = guestfs_%s_argv " name;
       generate_c_call_args ~handle:"g" style;
       pr ";\n";
 
@@ -477,7 +500,7 @@ Java_com_redhat_et_libguestfs_GuestFS__1close
         | Bool n
         | Int n
         | Int64 n -> ()
-      ) (snd style);
+      ) args;
 
       (* Check for errors. *)
       pr "  if (r == %s) {\n" error_code;
@@ -486,7 +509,7 @@ Java_com_redhat_et_libguestfs_GuestFS__1close
       pr "  }\n";
 
       (* Return value. *)
-      (match fst style with
+      (match ret with
        | RErr -> ()
        | RInt _ -> pr "  return (jint) r;\n"
        | RBool _ -> pr "  return (jboolean) r;\n"
@@ -528,6 +551,9 @@ Java_com_redhat_et_libguestfs_GuestFS__1close
            pr "  return jr;\n"
       );
 
+      if optargs <> [] then
+        pr "  */\n";
+
       pr "}\n";
       pr "\n"
   ) all_functions
diff --git a/generator/generator_ocaml.ml b/generator/generator_ocaml.ml
index 9db69dc..8d6209d 100644
--- a/generator/generator_ocaml.ml
+++ b/generator/generator_ocaml.ml
@@ -122,7 +122,7 @@ class guestfs : unit -> object
 
   List.iter (
     function
-    | name, ((_, []) as style), _, _, _, _, _ ->
+    | name, ((_, [], []) as style), _, _, _, _, _ ->
         pr "  method %s : unit -> " name;
         generate_ocaml_function_type style;
         pr "\n"
@@ -182,7 +182,7 @@ class guestfs () =
 
   List.iter (
     function
-    | name, (_, []), _, _, _, _, _ ->   (* no params?  add explicit unit *)
+    | name, (_, [], []), _, _, _, _, _ -> (* no params?  add explicit unit *)
         pr "    method %s () = %s g\n" name name
     | name, _, _, _, _, _, _ ->
         pr "    method %s = %s g\n" name name
@@ -323,18 +323,25 @@ copy_table (char * const * argv)
 
   (* The wrappers. *)
   List.iter (
-    fun (name, style, _, _, _, _, _) ->
+    fun (name, (ret, args, optargs as style), _, _, _, _, _) ->
       pr "/* Automatically generated wrapper for function\n";
       pr " * ";
       generate_ocaml_prototype name style;
       pr " */\n";
       pr "\n";
 
+      (* If we run into this situation, we'll need to change the
+       * bindings a little.
+       *)
+      if args = [] && optargs <> [] then
+        failwithf "ocaml bindings don't support args = [], optargs <> []";
+
       let params =
-        "gv" :: List.map (fun arg -> name_of_argt arg ^ "v") (snd style) in
+        "gv" ::
+          List.map (fun arg -> name_of_argt arg ^ "v") (optargs @ args) in
 
       let needs_extra_vs =
-        match fst style with RConstOptString _ -> true | _ -> false in
+        match ret with RConstOptString _ -> true | _ -> false in
 
       pr "/* Emit prototype to appease gcc's -Wmissing-prototypes. */\n";
       pr "CAMLprim value ocaml_guestfs_%s (value %s" name (List.hd params);
@@ -393,9 +400,35 @@ copy_table (char * const * argv)
             pr "  int %s = Int_val (%sv);\n" n n
         | Int64 n ->
             pr "  int64_t %s = Int64_val (%sv);\n" n n
-      ) (snd style);
+      ) args;
+
+      (* Optional arguments. *)
+      if optargs <> [] then (
+        pr "  struct guestfs_%s_argv optargs_s = { .bitmask = 0 };\n" name;
+        pr "  struct guestfs_%s_argv *optargs = &optargs_s;\n" name;
+        let uc_name = String.uppercase name in
+        List.iter (
+          fun argt ->
+            let n = name_of_argt argt in
+            let uc_n = String.uppercase n in
+            pr "  if (%sv != Val_int (0)) {\n" n;
+            pr "    optargs_s.bitmask |= GUESTFS_%s_%s_BITMASK;\n" uc_name uc_n;
+            pr "    optargs_s.%s = " n;
+            (match argt with
+             | Bool _ -> pr "Bool_val (Field (%sv, 0))" n
+             | Int _ -> pr "Int_val (Field (%sv, 0))" n
+             | Int64 _ -> pr "Int64_val (Field (%sv, 0))" n
+             | String _ ->
+                 pr "guestfs_safe_strdup (g, String_val (Field (%sv, 0)))" n
+             | _ -> assert false
+            );
+            pr ";\n";
+            pr "  }\n";
+        ) optargs
+      );
+
       let error_code =
-        match fst style with
+        match ret with
         | RErr -> pr "  int r;\n"; "-1"
         | RInt _ -> pr "  int r;\n"; "-1"
         | RInt64 _ -> pr "  int64_t r;\n"; "-1"
@@ -422,7 +455,10 @@ copy_table (char * const * argv)
       pr "\n";
 
       pr "  caml_enter_blocking_section ();\n";
-      pr "  r = guestfs_%s " name;
+      if optargs = [] then
+        pr "  r = guestfs_%s " name
+      else
+        pr "  r = guestfs_%s_argv " name;
       generate_c_call_args ~handle:"g" style;
       pr ";\n";
       pr "  caml_leave_blocking_section ();\n";
@@ -436,13 +472,23 @@ copy_table (char * const * argv)
         | StringList n | DeviceList n ->
             pr "  ocaml_guestfs_free_strings (%s);\n" n;
         | Bool _ | Int _ | Int64 _ -> ()
-      ) (snd style);
+      ) args;
+      List.iter (
+        function
+        | String n ->
+            pr "  if (%sv != Val_int (0))\n" n;
+            pr "    free ((char *) optargs_s.%s);\n" n
+        | Bool _ | Int _ | Int64 _
+        | Pathname _ | Device _ | Dev_or_Path _ | OptString _
+        | FileIn _ | FileOut _ | BufferIn _ | Key _
+        | StringList _ | DeviceList _ -> ()
+      ) optargs;
 
       pr "  if (r == %s)\n" error_code;
       pr "    ocaml_guestfs_raise_error (g, \"%s\");\n" name;
       pr "\n";
 
-      (match fst style with
+      (match ret with
        | RErr -> pr "  rv = Val_unit;\n"
        | RInt _ -> pr "  rv = Val_int (r);\n"
        | RInt64 _ ->
@@ -522,13 +568,22 @@ and generate_ocaml_prototype ?(is_external = false) name style =
   generate_ocaml_function_type style;
   if is_external then (
     pr " = ";
-    if List.length (snd style) + 1 > 5 then
+    let _, args, optargs = style in
+    if List.length args + List.length optargs + 1 > 5 then
       pr "\"ocaml_guestfs_%s_byte\" " name;
     pr "\"ocaml_guestfs_%s\"" name
   );
   pr "\n"
 
-and generate_ocaml_function_type style =
+and generate_ocaml_function_type (ret, args, optargs) =
+  List.iter (
+    function
+    | Bool n -> pr "?%s:bool -> " n
+    | Int n -> pr "?%s:int -> " n
+    | Int64 n -> pr "?%s:int64 -> " n
+    | String n -> pr "?%s:string -> " n
+    | _ -> assert false
+  ) optargs;
   List.iter (
     function
     | Pathname _ | Device _ | Dev_or_Path _ | String _ | FileIn _ | FileOut _
@@ -538,8 +593,8 @@ and generate_ocaml_function_type style =
     | Bool _ -> pr "bool -> "
     | Int _ -> pr "int -> "
     | Int64 _ -> pr "int64 -> "
-  ) (snd style);
-  (match fst style with
+  ) args;
+  (match ret with
    | RErr -> pr "unit" (* all errors are turned into exceptions *)
    | RInt _ -> pr "int"
    | RInt64 _ -> pr "int64"
diff --git a/generator/generator_perl.ml b/generator/generator_perl.ml
index 020f1b2..96b8dd1 100644
--- a/generator/generator_perl.ml
+++ b/generator/generator_perl.ml
@@ -199,8 +199,8 @@ clear_progress_callback (g)
 ";
 
   List.iter (
-    fun (name, style, _, _, _, _, _) ->
-      (match fst style with
+    fun (name, (ret, args, optargs as style), _, _, _, _, _) ->
+      (match ret with
        | RErr -> pr "void\n"
        | RInt _ -> pr "SV *\n"
        | RInt64 _ -> pr "SV *\n"
@@ -218,7 +218,9 @@ clear_progress_callback (g)
       pr "%s (g" name;
       List.iter (
         fun arg -> pr ", %s" (name_of_argt arg)
-      ) (snd style);
+      ) args;
+      if optargs <> [] then
+        pr ", ...";
       pr ")\n";
       pr "      guestfs_h *g;\n";
       iteri (
@@ -240,11 +242,11 @@ clear_progress_callback (g)
           | Bool n -> pr "      int %s;\n" n
           | Int n -> pr "      int %s;\n" n
           | Int64 n -> pr "      int64_t %s;\n" n
-      ) (snd style);
+      ) args;
 
       (* PREINIT section (local variable declarations). *)
       pr "PREINIT:\n";
-      (match fst style with
+      (match ret with
        | RErr ->
            pr "      int r;\n";
        | RInt _
@@ -272,11 +274,17 @@ clear_progress_callback (g)
            pr "      size_t size;\n";
       );
 
+      if optargs <> [] then (
+        pr "      struct guestfs_%s_argv optargs_s = { .bitmask = 0 };\n" name;
+        pr "      struct guestfs_%s_argv *optargs = &optargs_s;\n" name;
+        pr "      size_t items_i;\n";
+      );
+
       (* CODE or PPCODE section.  PPCODE is used where we are
        * returning void, or where we push the return value on the stack
        * ourselves.  Using CODE means we will manipulate RETVAL.
        *)
-      (match fst style with
+      (match ret with
        | RErr ->
            pr " PPCODE:\n";
        | RInt n
@@ -299,8 +307,52 @@ clear_progress_callback (g)
            pr " PPCODE:\n";
       );
 
+      (* For optional arguments, convert these from the XSUB "items"
+       * variable by hand.
+       *)
+      if optargs <> [] then (
+        let uc_name = String.uppercase name in
+        let skip = List.length args + 1 in
+        pr "      if (((items - %d) & 1) != 0)\n" skip;
+        pr "        croak (\"expecting an even number of extra parameters\");\n";
+        pr "      for (items_i = %d; items_i < items; items_i += 2) {\n" skip;
+        pr "        uint64_t this_mask;\n";
+        pr "        const char *this_arg;\n";
+        pr "\n";
+        pr "        this_arg = SvPV_nolen (ST (items_i));\n";
+        pr "        ";
+        List.iter (
+          fun argt ->
+            let n = name_of_argt argt in
+            let uc_n = String.uppercase n in
+            pr "if (strcmp (this_arg, \"%s\") == 0) {\n" n;
+            pr "          optargs_s.%s = " n;
+            (match argt with
+             | Bool _
+             | Int _
+             | Int64 _ -> pr "SvIV (ST (items_i+1))"
+             | String _ -> pr "SvPV_nolen (ST (items_i+1))"
+             | _ -> assert false
+            );
+            pr ";\n";
+            pr "          this_mask = GUESTFS_%s_%s_BITMASK;\n" uc_name uc_n;
+            pr "        }\n";
+            pr "        else ";
+        ) optargs;
+        pr "croak (\"unknown optional argument '%%s'\", this_arg);\n";
+        pr "        if (optargs_s.bitmask & this_mask)\n";
+        pr "          croak (\"optional argument '%%s' given twice\",\n";
+        pr "                 this_arg);\n";
+        pr "        optargs_s.bitmask |= this_mask;\n";
+        pr "      }\n";
+        pr "\n";
+      );
+
       (* The call to the C function. *)
-      pr "      r = guestfs_%s " name;
+      if optargs = [] then
+        pr "      r = guestfs_%s " name
+      else
+        pr "      r = guestfs_%s_argv " name;
       generate_c_call_args ~handle:"g" style;
       pr ";\n";
 
@@ -312,10 +364,10 @@ clear_progress_callback (g)
         | FileIn _ | FileOut _
         | BufferIn _ | Key _ -> ()
         | StringList n | DeviceList n -> pr "      free (%s);\n" n
-      ) (snd style);
+      ) args;
 
       (* Check return value for errors and return it if necessary. *)
-      (match fst style with
+      (match ret with
        | RErr ->
            pr "      if (r == -1)\n";
            pr "        croak (\"%%s\", guestfs_last_error (g));\n";
@@ -469,9 +521,9 @@ Sys::Guestfs - Perl bindings for libguestfs
  use Sys::Guestfs;
 
  my $h = Sys::Guestfs->new ();
- $h->add_drive ('guest.img');
+ $h->add_drive_opts ('guest.img', format => 'raw');
  $h->launch ();
- $h->mount ('/dev/sda1', '/');
+ $h->mount_options ('', '/dev/sda1', '/');
  $h->touch ('/hello');
  $h->sync ();
 
@@ -675,8 +727,8 @@ L<Sys::Guestfs::Lib(3)>.
 =cut
 " copyright_years
 
-and generate_perl_prototype name style =
-  (match fst style with
+and generate_perl_prototype name (ret, args, optargs) =
+  (match ret with
    | RErr -> ()
    | RBool n
    | RInt n
@@ -703,5 +755,12 @@ and generate_perl_prototype name style =
           pr "$%s" n
       | StringList n | DeviceList n ->
           pr "\\@%s" n
-  ) (snd style);
+  ) args;
+  List.iter (
+    fun arg ->
+      if !comma then pr " [, " else pr "[";
+      comma := true;
+      let n = name_of_argt arg in
+      pr "%s => $%s]" n n
+  ) optargs;
   pr ");"
diff --git a/generator/generator_php.ml b/generator/generator_php.ml
index 13ef4c0..b71d5c3 100644
--- a/generator/generator_php.ml
+++ b/generator/generator_php.ml
@@ -52,7 +52,7 @@ PHP_FUNCTION (guestfs_last_error);
 ";
 
   List.iter (
-    fun (shortname, style, _, _, _, _, _) ->
+    fun (shortname, _, _, _, _, _, _) ->
       pr "PHP_FUNCTION (guestfs_%s);\n" shortname
   ) all_functions_sorted;
 
@@ -112,7 +112,7 @@ static function_entry guestfs_php_functions[] = {
 ";
 
   List.iter (
-    fun (shortname, style, _, _, _, _, _) ->
+    fun (shortname, _, _, _, _, _, _) ->
       pr "  PHP_FE (guestfs_%s, NULL)\n" shortname
   ) all_functions_sorted;
 
@@ -180,7 +180,7 @@ PHP_FUNCTION (guestfs_last_error)
 
   (* Now generate the PHP bindings for each action. *)
   List.iter (
-    fun (shortname, style, _, _, _, _, _) ->
+    fun (shortname, (ret, args, optargs as style), _, _, _, _, _) ->
       pr "PHP_FUNCTION (guestfs_%s)\n" shortname;
       pr "{\n";
       pr "  zval *z_g;\n";
@@ -202,7 +202,28 @@ PHP_FUNCTION (guestfs_last_error)
             pr "  zend_bool %s;\n" n
         | Int n | Int64 n ->
             pr "  long %s;\n" n
-        ) (snd style);
+        ) args;
+
+      if optargs <> [] then (
+        pr "  struct guestfs_%s_argv optargs_s = { .bitmask = 0 };\n" shortname;
+        pr "  struct guestfs_%s_argv *optargs = &optargs_s;\n" shortname;
+
+        (* XXX Ugh PHP doesn't have proper optional arguments, so we
+         * have to use sentinel values.
+         *)
+        (* Since we don't know if PHP types will exactly match structure
+         * types, declare some local variables here.
+         *)
+        List.iter (
+          function
+          | Bool n -> pr "  zend_bool optargs_t_%s = -1;\n" n
+          | Int n | Int64 n -> pr "  long optargs_t_%s = -1;\n" n
+          | String n ->
+              pr "  char *optargs_t_%s = NULL;\n" n;
+              pr "  int optargs_t_%s_size = -1;\n" n
+          | _ -> assert false
+        ) optargs
+      );
 
       pr "\n";
 
@@ -216,9 +237,23 @@ PHP_FUNCTION (guestfs_last_error)
           | StringList n | DeviceList n -> "a"
           | Bool n -> "b"
           | Int n | Int64 n -> "l"
-        ) (snd style)
+        ) args
       ) in
 
+      let param_string =
+        if optargs <> [] then
+          param_string ^ "|" ^
+            String.concat "" (
+              List.map (
+                function
+                | Bool _ -> "b"
+                | Int _ | Int64 _ -> "l"
+                | String _ -> "s"
+                | _ -> assert false
+              ) optargs
+            )
+        else param_string in
+
       pr "  if (zend_parse_parameters (ZEND_NUM_ARGS() TSRMLS_CC, \"r%s\",\n"
         param_string;
       pr "        &z_g";
@@ -234,7 +269,15 @@ PHP_FUNCTION (guestfs_last_error)
             pr ", &%s" n
         | Int n | Int64 n ->
             pr ", &%s" n
-      ) (snd style);
+      ) args;
+      List.iter (
+        function
+        | Bool n | Int n | Int64 n ->
+            pr ", &optargs_t_%s" n
+        | String n ->
+            pr ", &optargs_t_%s, &optargs_t_%s_size" n n
+        | _ -> assert false
+      ) optargs;
       pr ") == FAILURE) {\n";
       pr "    RETURN_FALSE;\n";
       pr "  }\n";
@@ -288,11 +331,34 @@ PHP_FUNCTION (guestfs_last_error)
             pr "  }\n";
             pr "\n"
         | Bool n | Int n | Int64 n -> ()
-        ) (snd style);
+        ) args;
+
+      (* Optional arguments. *)
+      if optargs <> [] then (
+        let uc_shortname = String.uppercase shortname in
+        List.iter (
+          fun argt ->
+            let n = name_of_argt argt in
+            let uc_n = String.uppercase n in
+            pr "  if (optargs_t_%s != " n;
+            (match argt with
+             | Bool _ -> pr "((zend_bool)-1)"
+             | Int _ | Int64 _ -> pr "-1"
+             | String _ -> pr "NULL"
+             | _ -> assert false
+            );
+            pr ") {\n";
+            pr "    optargs_s.%s = optargs_t_%s;\n" n n;
+            pr "    optargs_s.bitmask |= GUESTFS_%s_%s_BITMASK;\n"
+              uc_shortname uc_n;
+            pr "  }\n"
+        ) optargs;
+        pr "\n"
+      );
 
       (* Return value. *)
       let error_code =
-        match fst style with
+        match ret with
         | RErr -> pr "  int r;\n"; "-1"
         | RBool _
         | RInt _ -> pr "  int r;\n"; "-1"
@@ -315,7 +381,10 @@ PHP_FUNCTION (guestfs_last_error)
             "NULL" in
 
       (* Call the function. *)
-      pr "  r = guestfs_%s " shortname;
+      if optargs = [] then
+        pr "  r = guestfs_%s " shortname
+      else
+        pr "  r = guestfs_%s_argv " shortname;
       generate_c_call_args ~handle:"g" style;
       pr ";\n";
       pr "\n";
@@ -338,7 +407,7 @@ PHP_FUNCTION (guestfs_last_error)
             pr "  }\n";
             pr "\n"
         | Bool n | Int n | Int64 n -> ()
-        ) (snd style);
+        ) args;
 
       (* Check for errors. *)
       pr "  if (r == %s) {\n" error_code;
@@ -347,7 +416,7 @@ PHP_FUNCTION (guestfs_last_error)
       pr "\n";
 
       (* Convert the return value. *)
-      (match fst style with
+      (match ret with
        | RErr ->
            pr "  RETURN_TRUE;\n"
        | RBool _ ->
diff --git a/generator/generator_python.ml b/generator/generator_python.ml
index 2a6034d..b9457a8 100644
--- a/generator/generator_python.ml
+++ b/generator/generator_python.ml
@@ -279,7 +279,7 @@ py_guestfs_close (PyObject *self, PyObject *args)
 
   (* Python wrapper functions. *)
   List.iter (
-    fun (name, style, _, _, _, _, _) ->
+    fun (name, (ret, args, optargs as style), _, _, _, _, _) ->
       pr "static PyObject *\n";
       pr "py_guestfs_%s (PyObject *self, PyObject *args)\n" name;
       pr "{\n";
@@ -288,8 +288,13 @@ py_guestfs_close (PyObject *self, PyObject *args)
       pr "  guestfs_h *g;\n";
       pr "  PyObject *py_r;\n";
 
+      if optargs <> [] then (
+        pr "  struct guestfs_%s_argv optargs_s;\n" name;
+        pr "  struct guestfs_%s_argv *optargs = &optargs_s;\n" name;
+      );
+
       let error_code =
-        match fst style with
+        match ret with
         | RErr | RInt _ | RBool _ -> pr "  int r;\n"; "-1"
         | RInt64 _ -> pr "  int64_t r;\n"; "-1"
         | RConstString _ | RConstOptString _ ->
@@ -319,11 +324,33 @@ py_guestfs_close (PyObject *self, PyObject *args)
         | Bool n -> pr "  int %s;\n" n
         | Int n -> pr "  int %s;\n" n
         | Int64 n -> pr "  long long %s;\n" n
-      ) (snd style);
+      ) args;
+
+      if optargs <> [] then (
+        (* XXX This is horrible.  We have to use sentinel values on the
+         * Python side to denote values not set.
+         *)
+        (* Since we don't know if Python types will exactly match
+         * structure types, declare some local variables here.
+         *)
+        List.iter (
+          function
+          | Bool n
+          | Int n -> pr "  int optargs_t_%s = -1;\n" n
+          | Int64 n -> pr "  long long optargs_t_%s = -1;\n" n
+          | String n -> pr "  const char *optargs_t_%s = NULL;\n" n
+          | _ -> assert false
+        ) optargs
+      );
 
       pr "\n";
 
-      (* Convert the parameters. *)
+      if optargs <> [] then (
+        pr "  optargs_s.bitmask = 0;\n";
+        pr "\n"
+      );
+
+      (* Convert the required parameters. *)
       pr "  if (!PyArg_ParseTuple (args, (char *) \"O";
       List.iter (
         function
@@ -337,7 +364,19 @@ py_guestfs_close (PyObject *self, PyObject *args)
                              * emulate C's int/long/long long in Python?
                              *)
         | BufferIn _ -> pr "s#"
-      ) (snd style);
+      ) args;
+
+      (* Optional parameters. *)
+      if optargs <> [] then (
+        List.iter (
+          function
+          | Bool _ | Int _ -> pr "i"
+          | Int64 _ -> pr "L"
+          | String _ -> pr "z" (* because we use None to mean not set *)
+          | _ -> assert false
+        ) optargs;
+      );
+
       pr ":guestfs_%s\",\n" name;
       pr "                         &py_g";
       List.iter (
@@ -350,7 +389,13 @@ py_guestfs_close (PyObject *self, PyObject *args)
         | Int n -> pr ", &%s" n
         | Int64 n -> pr ", &%s" n
         | BufferIn n -> pr ", &%s, &%s_size" n n
-      ) (snd style);
+      ) args;
+
+      List.iter (
+        function
+        | Bool n | Int n | Int64 n | String n -> pr ", &optargs_t_%s" n
+        | _ -> assert false
+      ) optargs;
 
       pr "))\n";
       pr "    return NULL;\n";
@@ -364,11 +409,34 @@ py_guestfs_close (PyObject *self, PyObject *args)
         | StringList n | DeviceList n ->
             pr "  %s = get_string_list (py_%s);\n" n n;
             pr "  if (!%s) return NULL;\n" n
-      ) (snd style);
+      ) args;
 
       pr "\n";
 
-      pr "  r = guestfs_%s " name;
+      if optargs <> [] then (
+        let uc_name = String.uppercase name in
+        List.iter (
+          fun argt ->
+            let n = name_of_argt argt in
+            let uc_n = String.uppercase n in
+            pr "  if (optargs_t_%s != " n;
+            (match argt with
+             | Bool _ | Int _ | Int64 _ -> pr "-1"
+             | String _ -> pr "NULL"
+             | _ -> assert false
+            );
+            pr ") {\n";
+            pr "    optargs_s.%s = optargs_t_%s;\n" n n;
+            pr "    optargs_s.bitmask |= GUESTFS_%s_%s_BITMASK;\n" uc_name uc_n;
+            pr "  }\n"
+        ) optargs;
+        pr "\n"
+      );
+
+      if optargs = [] then
+        pr "  r = guestfs_%s " name
+      else
+        pr "  r = guestfs_%s_argv " name;
       generate_c_call_args ~handle:"g" style;
       pr ";\n";
 
@@ -379,7 +447,7 @@ py_guestfs_close (PyObject *self, PyObject *args)
         | BufferIn _ -> ()
         | StringList n | DeviceList n ->
             pr "  free (%s);\n" n
-      ) (snd style);
+      ) args;
 
       pr "  if (r == %s) {\n" error_code;
       pr "    PyErr_SetString (PyExc_RuntimeError, guestfs_last_error (g));\n";
@@ -387,7 +455,7 @@ py_guestfs_close (PyObject *self, PyObject *args)
       pr "  }\n";
       pr "\n";
 
-      (match fst style with
+      (match ret with
        | RErr ->
            pr "  Py_INCREF (Py_None);\n";
            pr "  py_r = Py_None;\n"
@@ -520,15 +588,21 @@ class GuestFS:
 ";
 
   List.iter (
-    fun (name, style, _, flags, _, _, longdesc) ->
-      pr "    def %s " name;
-      generate_py_call_args ~handle:"self" (snd style);
-      pr ":\n";
+    fun (name, (ret, args, optargs), _, flags, _, _, longdesc) ->
+      pr "    def %s (self" name;
+      List.iter (fun arg -> pr ", %s" (name_of_argt arg)) args;
+      List.iter (
+        function
+        | Bool n | Int n | Int64 n -> pr ", %s=-1" n
+        | String n -> pr ", %s=None" n
+        | _ -> assert false
+      ) optargs;
+      pr "):\n";
 
       if not (List.mem NotInDocs flags) then (
         let doc = replace_str longdesc "C<guestfs_" "C<g." in
         let doc =
-          match fst style with
+          match ret with
           | RErr | RInt _ | RInt64 _ | RBool _
           | RConstOptString _ | RConstString _
           | RString _ | RBufferOut _ -> doc
@@ -557,14 +631,7 @@ class GuestFS:
         let doc = String.concat "\n        " doc in
         pr "        u\"\"\"%s\"\"\"\n" doc;
       );
-      pr "        return libguestfsmod.%s " name;
-      generate_py_call_args ~handle:"self._o" (snd style);
-      pr "\n";
-      pr "\n";
+      pr "        return libguestfsmod.%s (self._o" name;
+      List.iter (fun arg -> pr ", %s" (name_of_argt arg)) (args at optargs);
+      pr ")\n\n";
   ) all_functions
-
-(* Generate Python call arguments, eg "(handle, foo, bar)" *)
-and generate_py_call_args ~handle args =
-  pr "(%s" handle;
-  List.iter (fun arg -> pr ", %s" (name_of_argt arg)) args;
-  pr ")"
diff --git a/generator/generator_ruby.ml b/generator/generator_ruby.ml
index 4ee3475..e87a57c 100644
--- a/generator/generator_ruby.ml
+++ b/generator/generator_ruby.ml
@@ -89,9 +89,11 @@ static VALUE ruby_guestfs_close (VALUE gv)
 ";
 
   List.iter (
-    fun (name, style, _, _, _, _, _) ->
+    fun (name, (ret, args, optargs as style), _, _, _, _, _) ->
       pr "static VALUE ruby_guestfs_%s (VALUE gv" name;
-      List.iter (fun arg -> pr ", VALUE %sv" (name_of_argt arg)) (snd style);
+      List.iter (fun arg -> pr ", VALUE %sv" (name_of_argt arg)) args;
+      if optargs <> [] then
+        pr ", VALUE optargsv";
       pr ")\n";
       pr "{\n";
       pr "  guestfs_h *g;\n";
@@ -139,11 +141,42 @@ static VALUE ruby_guestfs_close (VALUE gv)
             pr "  int %s = NUM2INT (%sv);\n" n n
         | Int64 n ->
             pr "  long long %s = NUM2LL (%sv);\n" n n
-      ) (snd style);
+      ) args;
       pr "\n";
 
+      (* Optional arguments are passed in a final hash parameter. *)
+      if optargs <> [] then (
+        let uc_name = String.uppercase name in
+        pr "  Check_Type (optargsv, T_HASH);\n";
+        pr "  struct guestfs_%s_argv optargs_s = { .bitmask = 0 };\n" name;
+        pr "  struct guestfs_%s_argv *optargs = &optargs_s;\n" name;
+        pr "  VALUE v;\n";
+        List.iter (
+          fun argt ->
+            let n = name_of_argt argt in
+            let uc_n = String.uppercase n in
+            pr "  v = rb_hash_lookup (optargsv, ID2SYM (rb_intern (\"%s\")));\n" n;
+            pr "  if (v != Qnil) {\n";
+            (match argt with
+             | Bool n ->
+                 pr "    optargs_s.%s = RTEST (v);\n" n;
+             | Int n ->
+                 pr "    optargs_s.%s = NUM2INT (v);\n" n;
+             | Int64 n ->
+                 pr "    optargs_s.%s = NUM2LL (v);\n" n;
+             | String _ ->
+                 pr "    Check_Type (v, T_STRING);\n";
+                 pr "    optargs_s.%s = StringValueCStr (v);\n" n
+             | _ -> assert false
+            );
+            pr "    optargs_s.bitmask |= GUESTFS_%s_%s_BITMASK;\n" uc_name uc_n;
+            pr "  }\n";
+        ) optargs;
+        pr "\n";
+      );
+
       let error_code =
-        match fst style with
+        match ret with
         | RErr | RInt _ | RBool _ -> pr "  int r;\n"; "-1"
         | RInt64 _ -> pr "  int64_t r;\n"; "-1"
         | RConstString _ | RConstOptString _ ->
@@ -159,7 +192,10 @@ static VALUE ruby_guestfs_close (VALUE gv)
             "NULL" in
       pr "\n";
 
-      pr "  r = guestfs_%s " name;
+      if optargs = [] then
+        pr "  r = guestfs_%s " name
+      else
+        pr "  r = guestfs_%s_argv " name;
       generate_c_call_args ~handle:"g" style;
       pr ";\n";
 
@@ -170,13 +206,13 @@ static VALUE ruby_guestfs_close (VALUE gv)
         | BufferIn _ -> ()
         | StringList n | DeviceList n ->
             pr "  free (%s);\n" n
-      ) (snd style);
+      ) args;
 
       pr "  if (r == %s)\n" error_code;
       pr "    rb_raise (e_Error, \"%%s\", guestfs_last_error (g));\n";
       pr "\n";
 
-      (match fst style with
+      (match ret with
        | RErr ->
            pr "  return Qnil;\n"
        | RInt _ | RBool _ ->
@@ -248,9 +284,10 @@ void Init__guestfs ()
 ";
   (* Define the rest of the methods. *)
   List.iter (
-    fun (name, style, _, _, _, _, _) ->
+    fun (name, (_, args, optargs), _, _, _, _, _) ->
+      let nr_args = List.length args + if optargs <> [] then 1 else 0 in
       pr "  rb_define_method (c_guestfs, \"%s\",\n" name;
-      pr "        ruby_guestfs_%s, %d);\n" name (List.length (snd style))
+      pr "        ruby_guestfs_%s, %d);\n" name nr_args
   ) all_functions;
 
   pr "}\n"
diff --git a/generator/generator_types.ml b/generator/generator_types.ml
index 03805cf..262fb20 100644
--- a/generator/generator_types.ml
+++ b/generator/generator_types.ml
@@ -20,7 +20,45 @@
 
 (* Types used to describe the API. *)
 
-type style = ret * args
+type style = ret * args * args
+    (* The [style] is a tuple which describes the return value and
+     * arguments of a function.
+     * 
+     * [ret] is the return value, one of the [R*] values below.
+     * 
+     * The second element is the list of required arguments, a list of
+     * [argt]s from the list below, eg. [Bool], [String] etc.  Each has
+     * a name so that for example [Int "foo"] corresponds in the C
+     * bindings to an [int foo] parameter.
+     * 
+     * The third element is the list of optional arguments.  These are
+     * mapped to optional arguments in the language binding, eg. in
+     * Perl to:
+     *   $g->fn (required1, required2, opt1 => val, opt2 => val);
+     * As the name suggests these are optional, and the function can
+     * tell which optional parameters were supplied by the caller.
+     * 
+     * Only [Bool], [Int], [Int64], [String] may currently appear in
+     * the optional argument list (we may permit more types in future).
+     *
+     * ABI and API considerations
+     * --------------------------
+     * 
+     * The return type and required arguments may not be changed after
+     * these have been published in a stable version of libguestfs,
+     * because doing so would break the ABI.
+     * 
+     * If a published function has one or more optional arguments,
+     * then the call can be extended without breaking the ABI or API.
+     * This is in fact the only way to change an existing function.
+     * There are limitations on this:
+     *
+     * (1) you may _only_ add extra elements at the end of the list
+     * (2) you may _not_ rearrange or rename or remove existing elements
+     * (3) you may _not_ add optional arguments to a function which did
+     *     not have any before (since this breaks the C ABI).
+     *)
+
 and ret =
     (* "RErr" as a return value means an int used as a simple error
      * indication, ie. 0 or -1.
@@ -108,13 +146,6 @@ and ret =
 
 and args = argt list	(* Function parameters, guestfs handle is implicit. *)
 
-    (* Note in future we should allow a "variable args" parameter as
-     * the final parameter, to allow commands like
-     *   chmod mode file [file(s)...]
-     * This is not implemented yet, but many commands (such as chmod)
-     * are currently defined with the argument order keeping this future
-     * possibility in mind.
-     *)
 and argt =
   | Bool of string	(* boolean *)
   | Int of string	(* int (smallish ints, signed, <= 31 bits) *)
diff --git a/generator/generator_utils.ml b/generator/generator_utils.ml
index b7401db..8511227 100644
--- a/generator/generator_utils.ml
+++ b/generator/generator_utils.ml
@@ -83,8 +83,8 @@ let rstructs_used_by functions =
   in
 
   List.iter (
-    fun (_, style, _, _, _, _, _) ->
-      match fst style with
+    fun (_, (ret, _, _), _, _, _, _, _) ->
+      match ret with
       | RStruct (_, structname) -> update structname RStructOnly
       | RStructList (_, structname) -> update structname RStructListOnly
       | _ -> ()
diff --git a/generator/generator_xdr.ml b/generator/generator_xdr.ml
index 7f2e6eb..4942149 100644
--- a/generator/generator_xdr.ml
+++ b/generator/generator_xdr.ml
@@ -64,10 +64,13 @@ let generate_xdr () =
   ) structs;
 
   List.iter (
-    fun (shortname, style, _, _, _, _, _) ->
+    fun (shortname, (ret, args, optargs), _, _, _, _, _) ->
+      if optargs <> [] then
+        failwithf "optional arguments not supported in XDR yet";
+
       let name = "guestfs_" ^ shortname in
 
-      (match snd style with
+      (match args with
        | [] -> ()
        | args ->
            pr "struct %s_args {\n" name;
@@ -86,7 +89,7 @@ let generate_xdr () =
            ) args;
            pr "};\n\n"
       );
-      (match fst style with
+      (match ret with
        | RErr -> ()
        | RInt n ->
            pr "struct %s_ret {\n" name;
diff --git a/ocaml/Makefile.am b/ocaml/Makefile.am
index e80d44a..77c5be9 100644
--- a/ocaml/Makefile.am
+++ b/ocaml/Makefile.am
@@ -65,10 +65,12 @@ TESTS_ENVIRONMENT = \
 TESTS = run-bindtests \
 	t/guestfs_005_load t/guestfs_010_launch t/guestfs_050_lvcreate \
 	t/guestfs_060_readdir t/guestfs_070_threads \
+	t/guestfs_080_optargs \
 	t/guestfs_400_progress
 noinst_DATA += bindtests \
 	t/guestfs_005_load t/guestfs_010_launch t/guestfs_050_lvcreate \
 	t/guestfs_060_readdir t/guestfs_070_threads \
+	t/guestfs_080_optargs \
 	t/guestfs_400_progress
 
 bindtests: bindtests.cmx mlguestfs.cmxa
@@ -95,6 +97,10 @@ t/guestfs_070_threads: t/guestfs_070_threads.cmx mlguestfs.cmxa
 	mkdir -p t
 	$(OCAMLFIND) ocamlopt -cclib -L$(top_builddir)/src/.libs -I . -package unix,threads -thread -linkpkg mlguestfs.cmxa $< -o $@
 
+t/guestfs_080_optargs: t/guestfs_080_optargs.cmx mlguestfs.cmxa
+	mkdir -p t
+	$(OCAMLFIND) ocamlopt -cclib -L$(top_builddir)/src/.libs -I . -package xml-light,unix -linkpkg mlguestfs.cmxa $< -o $@
+
 t/guestfs_400_progress: t/guestfs_400_progress.cmx mlguestfs.cmxa
 	mkdir -p t
 	$(OCAMLFIND) ocamlopt -cclib -L$(top_builddir)/src/.libs -I . -package xml-light,unix -linkpkg mlguestfs.cmxa $< -o $@
diff --git a/ocaml/t/guestfs_080_optargs.ml b/ocaml/t/guestfs_080_optargs.ml
new file mode 100644
index 0000000..2b37784
--- /dev/null
+++ b/ocaml/t/guestfs_080_optargs.ml
@@ -0,0 +1,30 @@
+(* libguestfs OCaml bindings
+ * Copyright (C) 2010 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.
+ *)
+
+open Unix
+
+let () =
+  let g = Guestfs.create () in
+
+  Guestfs.add_drive_opts g "/dev/null";
+  Guestfs.add_drive_opts g ~readonly:true "/dev/null";
+  Guestfs.add_drive_opts g ~readonly:true ~format:"raw" "/dev/null";
+  Guestfs.add_drive_opts g ~iface:"virtio" ~readonly:true ~format:"raw"
+    "/dev/null";
+
+  Guestfs.close g
diff --git a/perl/t/070-optargs.t b/perl/t/070-optargs.t
new file mode 100644
index 0000000..8187395
--- /dev/null
+++ b/perl/t/070-optargs.t
@@ -0,0 +1,34 @@
+# libguestfs Perl bindings -*- perl -*-
+# Copyright (C) 2010 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.
+
+use strict;
+use warnings;
+use Test::More tests => 4;
+
+use Sys::Guestfs;
+
+my $h = Sys::Guestfs->new ();
+ok ($h);
+
+$h->add_drive_opts ("/dev/null");
+ok (1);
+
+$h->add_drive_opts ("/dev/null", readonly => 1);
+ok (1);
+
+$h->add_drive_opts ("/dev/null", format => "qcow2", readonly => 0);
+ok (1);
diff --git a/php/extension/guestfs_php_004.phpt b/php/extension/guestfs_php_004.phpt
new file mode 100644
index 0000000..08e53a3
--- /dev/null
+++ b/php/extension/guestfs_php_004.phpt
@@ -0,0 +1,33 @@
+--TEST--
+Check function with optional arguments.
+--FILE--
+<?php
+
+// See comment in php/run-php-tests.sh.
+//putenv ('LIBGUESTFS_DEBUG=1');
+
+$g = guestfs_create ();
+if ($g == false) {
+  echo ("Failed to create guestfs_php handle.\n");
+  exit;
+}
+if (guestfs_add_drive_opts ($g, "/dev/null") == false) {
+  echo ("Failed add_drive_opts, no optional arguments.\n");
+  exit;
+}
+if (guestfs_add_drive_opts ($g, "/dev/null", 0) == false) {
+  echo ("Failed add_drive_opts, one optional argument.\n");
+  exit;
+}
+if (guestfs_add_drive_opts ($g, "/dev/null", 1) == false) {
+  echo ("Failed add_drive_opts, one optional argument.\n");
+  exit;
+}
+if (guestfs_add_drive_opts ($g, "/dev/null", 1, "qcow2") == false) {
+  echo ("Failed add_drive_opts, two optional arguments.\n");
+  exit;
+}
+echo ("Completed tests OK.\n");
+?>
+--EXPECT--
+Completed tests OK.
diff --git a/python/t/060-optargs.py b/python/t/060-optargs.py
new file mode 100644
index 0000000..78d07c8
--- /dev/null
+++ b/python/t/060-optargs.py
@@ -0,0 +1,25 @@
+# libguestfs Python bindings
+# Copyright (C) 2010 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.
+
+import os
+import guestfs
+
+g = guestfs.GuestFS()
+g.add_drive_opts ("/dev/null")
+g.add_drive_opts ("/dev/null", readonly = 1)
+g.add_drive_opts ("/dev/null", iface = "virtio", format = "qcow2")
+del g
diff --git a/ruby/tests/tc_060_optargs.rb b/ruby/tests/tc_060_optargs.rb
new file mode 100644
index 0000000..232286a
--- /dev/null
+++ b/ruby/tests/tc_060_optargs.rb
@@ -0,0 +1,33 @@
+# libguestfs Ruby bindings -*- ruby -*-
+# Copyright (C) 2010 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", "guestfs"))
+require 'guestfs'
+
+class TestLoad < Test::Unit::TestCase
+  def test_optargs
+    g = Guestfs::create()
+
+    g.add_drive_opts("/dev/null", {})
+    g.add_drive_opts("/dev/null", :readonly => 1)
+    g.add_drive_opts("/dev/null", :readonly => 1, :iface => "virtio")
+    g.add_drive_opts("/dev/null",
+                     :readonly => 1, :iface => "virtio", :format => "qcow2")
+  end
+end
diff --git a/src/guestfs.pod b/src/guestfs.pod
index d034c8e..c1e595c 100644
--- a/src/guestfs.pod
+++ b/src/guestfs.pod
@@ -124,7 +124,22 @@ disk, an actual block device, or simply an empty file of zeroes that
 you have created through L<posix_fallocate(3)>.  Libguestfs lets you
 do useful things to all of these.
 
-You can add a disk read-only using L</guestfs_add_drive_ro>, in which
+The call you should use in modern code for adding drives is
+L</guestfs_add_drive_opts>.  To add a disk image, allowing writes, and
+specifying that the format is raw, do:
+
+ guestfs_add_drive_opts (g, filename,
+                         GUESTFS_ADD_DRIVE_OPTS_FORMAT, "raw",
+                         -1);
+
+You can add a disk read-only using:
+
+ guestfs_add_drive_opts (g, filename,
+                         GUESTFS_ADD_DRIVE_OPTS_FORMAT, "raw",
+                         GUESTFS_ADD_DRIVE_OPTS_READONLY, 1,
+                         -1);
+
+or by calling the older function L</guestfs_add_drive_ro>.  In either
 case libguestfs won't modify the file.
 
 Be extremely cautious if the disk image is in use, eg. if it is being
@@ -835,7 +850,8 @@ L</MULTIPLE HANDLES AND MULTIPLE THREADS> below.
 
 Create a connection handle.
 
-You have to call L</guestfs_add_drive> on the handle at least once.
+You have to call L</guestfs_add_drive_opts> (or one of the equivalent
+calls) on the handle at least once.
 
 This function returns a non-NULL pointer to a handle on success or
 NULL on error.
@@ -1050,6 +1066,101 @@ package versioning:
 
  Requires: libguestfs >= 1.0.80
 
+=head1 CALLS WITH OPTIONAL ARGUMENTS
+
+A recent feature of the API is the introduction of calls which take
+optional arguments.  In C these are declared 3 ways.  The main way is
+as a call which takes variable arguments (ie. C<...>), as in this
+example:
+
+ int guestfs_add_drive_opts (guestfs_h *g, const char *filename, ...);
+
+Call this with a list of optional arguments, terminated by C<-1>.
+So to call with no optional arguments specified:
+
+ guestfs_add_drive_opts (g, filename, -1);
+
+With a single optional argument:
+
+ guestfs_add_drive_opts (g, filename,
+                         GUESTFS_ADD_DRIVE_OPTS_FORMAT, "qcow2",
+                         -1);
+
+With two:
+
+ guestfs_add_drive_opts (g, filename,
+                         GUESTFS_ADD_DRIVE_OPTS_FORMAT, "qcow2",
+                         GUESTFS_ADD_DRIVE_OPTS_READONLY, 1,
+                         -1);
+
+and so forth.  Don't forget the terminating C<-1> otherwise
+Bad Things will happen!
+
+=head2 USING va_list FOR OPTIONAL ARGUMENTS
+
+The second variant has the same name with the suffix C<_va>, which
+works the same way but takes a C<va_list>.  See the C manual for
+details.  For the example function, this is declared:
+
+ int guestfs_add_drive_opts_va (guestfs_h *g, const char *filename,
+                                va_list args);
+
+=head2 CONSTRUCTING OPTIONAL ARGUMENTS
+
+The third variant is useful where you need to construct these
+calls.  You pass in a structure where you fill in the optional
+fields.  The structure has a bitmask as the first element which
+you must set to indicate which fields you have filled in.  For
+our example function the structure and call are declared:
+
+ struct guestfs_add_drive_opts_argv {
+   uint64_t bitmask;
+   int readonly;
+   const char *format;
+   /* ... */
+ };
+ int guestfs_add_drive_opts_argv (guestfs_h *g, const char *filename,
+              const struct guestfs_add_drive_opts_argv *optargs);
+
+You could call it like this:
+
+ struct guestfs_add_drive_opts_argv optargs = {
+   .bitmask = GUESTFS_ADD_DRIVE_OPTS_READONLY_BITMASK |
+              GUESTFS_ADD_DRIVE_OPTS_FORMAT_BITMASK,
+   .readonly = 1,
+   .format = "qcow2"
+ };
+ 
+ guestfs_add_drive_opts_argv (g, filename, &optargs);
+
+Notes:
+
+=over 4
+
+=item *
+
+The C<_BITMASK> suffix on each option name when specifying the
+bitmask.
+
+=item *
+
+You do not need to fill in all fields of the structure.
+
+=item *
+
+There must be a one-to-one correspondence between fields of the
+structure that are filled in, and bits set in the bitmask.
+
+=back
+
+=head2 OPTIONAL ARGUMENTS IN OTHER LANGUAGES
+
+In other languages, optional arguments are expressed in the
+way that is natural for that language.  We refer you to the
+language-specific documentation for more details on that.
+
+For guestfish, see L<guestfish(1)/OPTIONAL ARGUMENTS>.
+
 =begin html
 
 <!-- old anchor for the next section -->
diff --git a/src/launch.c b/src/launch.c
index f9d8329..4fe6ada 100644
--- a/src/launch.c
+++ b/src/launch.c
@@ -131,64 +131,80 @@ guestfs__config (guestfs_h *g,
   return 0;
 }
 
-int
-guestfs__add_drive_with_if (guestfs_h *g, const char *filename,
-                            const char *drive_if)
+/* cache=off improves reliability in the event of a host crash.
+ *
+ * However this option causes qemu to try to open the file with
+ * O_DIRECT.  This fails on some filesystem types (notably tmpfs).
+ * So we check if we can open the file with or without O_DIRECT,
+ * and use cache=off (or not) accordingly.
+ */
+static int
+test_cache_off (guestfs_h *g, const char *filename)
 {
-  size_t len = strlen (filename) + 64;
-  char buf[len];
-
-  if (strchr (filename, ',') != NULL) {
-    error (g, _("filename cannot contain ',' (comma) character"));
-    return -1;
+  int fd = open (filename, O_RDONLY|O_DIRECT);
+  if (fd >= 0) {
+    close (fd);
+    return 1;
   }
 
-  /* cache=off improves reliability in the event of a host crash.
-   *
-   * However this option causes qemu to try to open the file with
-   * O_DIRECT.  This fails on some filesystem types (notably tmpfs).
-   * So we check if we can open the file with or without O_DIRECT,
-   * and use cache=off (or not) accordingly.
-   *
-   * This test also checks for the presence of the file, which
-   * is a documented semantic of this interface.
-   */
-  int fd = open (filename, O_RDONLY|O_DIRECT);
+  fd = open (filename, O_RDONLY);
   if (fd >= 0) {
     close (fd);
-    snprintf (buf, len, "file=%s,cache=off,if=%s", filename, drive_if);
-  } else {
-    fd = open (filename, O_RDONLY);
-    if (fd >= 0) {
-      close (fd);
-      snprintf (buf, len, "file=%s,if=%s", filename, drive_if);
-    } else {
-      perrorf (g, "%s", filename);
-      return -1;
-    }
+    return 0;
   }
 
-  return guestfs__config (g, "-drive", buf);
+  perrorf (g, "%s", filename);
+  return -1;
 }
 
 int
-guestfs__add_drive_ro_with_if (guestfs_h *g, const char *filename,
-                               const char *drive_if)
+guestfs__add_drive_opts (guestfs_h *g, const char *filename,
+                         const struct guestfs_add_drive_opts_argv *optargs)
 {
   if (strchr (filename, ',') != NULL) {
     error (g, _("filename cannot contain ',' (comma) character"));
     return -1;
   }
 
-  if (access (filename, F_OK) == -1) {
-    perrorf (g, "%s", filename);
+  int readonly =
+    optargs->bitmask & GUESTFS_ADD_DRIVE_OPTS_READONLY_BITMASK
+    ? optargs->readonly : 0;
+
+  const char *iface =
+    optargs->bitmask & GUESTFS_ADD_DRIVE_OPTS_IFACE_BITMASK
+    ? optargs->iface : DRIVE_IF;
+
+  const char *format =
+    optargs->bitmask & GUESTFS_ADD_DRIVE_OPTS_FORMAT_BITMASK
+    ? optargs->format : NULL;
+
+  /* For writable files, see if we can use cache=off.  This also
+   * checks for the existence of the file.  For readonly we have
+   * to do the check explicitly.
+   */
+  int use_cache_off = readonly ? 0 : test_cache_off (g, filename);
+  if (use_cache_off == -1)
     return -1;
+
+  if (readonly) {
+    if (access (filename, F_OK) == -1) {
+      perrorf (g, "%s", filename);
+      return -1;
+    }
   }
 
-  size_t len = strlen (filename) + 64;
+  /* Construct the final -drive parameter. */
+  size_t len = 64 + strlen (filename) + strlen (iface);
+  if (format) len += strlen (format);
   char buf[len];
 
-  snprintf (buf, len, "file=%s,snapshot=on,if=%s", filename, drive_if);
+  snprintf (buf, len, "file=%s%s%s%s%s,if=%s",
+            filename,
+            readonly ? ",snapshot=on" : "",
+            use_cache_off ? ",cache=off" : "",
+            format ? ",format=" : "",
+            format ? format : "",
+            iface);
 
   return guestfs__config (g, "-drive", buf);
 }
@@ -196,13 +212,48 @@ guestfs__add_drive_ro_with_if (guestfs_h *g, const char *filename,
 int
 guestfs__add_drive (guestfs_h *g, const char *filename)
 {
-  return guestfs__add_drive_with_if (g, filename, DRIVE_IF);
+  struct guestfs_add_drive_opts_argv optargs = {
+    .bitmask = 0,
+  };
+
+  return guestfs__add_drive_opts (g, filename, &optargs);
 }
 
 int
 guestfs__add_drive_ro (guestfs_h *g, const char *filename)
 {
-  return guestfs__add_drive_ro_with_if (g, filename, DRIVE_IF);
+  struct guestfs_add_drive_opts_argv optargs = {
+    .bitmask = GUESTFS_ADD_DRIVE_OPTS_READONLY_BITMASK,
+    .readonly = 1,
+  };
+
+  return guestfs__add_drive_opts (g, filename, &optargs);
+}
+
+int
+guestfs__add_drive_with_if (guestfs_h *g, const char *filename,
+                            const char *iface)
+{
+  struct guestfs_add_drive_opts_argv optargs = {
+    .bitmask = GUESTFS_ADD_DRIVE_OPTS_IFACE_BITMASK,
+    .iface = iface,
+  };
+
+  return guestfs__add_drive_opts (g, filename, &optargs);
+}
+
+int
+guestfs__add_drive_ro_with_if (guestfs_h *g, const char *filename,
+                               const char *iface)
+{
+  struct guestfs_add_drive_opts_argv optargs = {
+    .bitmask = GUESTFS_ADD_DRIVE_OPTS_IFACE_BITMASK
+             | GUESTFS_ADD_DRIVE_OPTS_READONLY_BITMASK,
+    .iface = iface,
+    .readonly = 1,
+  };
+
+  return guestfs__add_drive_opts (g, filename, &optargs);
 }
 
 int
-- 
1.7.3.1



More information about the Libguestfs mailing list