Defend all typedefs in <guestfs.h> with #ifdefs.
[libguestfs.git] / generator / generator_c.ml
index ca0907d..7ebd846 100644 (file)
@@ -24,39 +24,58 @@ open Generator_types
 open Generator_utils
 open Generator_pr
 open Generator_docstrings
+open Generator_api_versions
 open Generator_optgroups
 open Generator_actions
 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 =
+    ?(single_line = false) ?(indent = "") ?(newline = false)
+    ?(in_daemon = false)
+    ?(prefix = "") ?(suffix = "")
+    ?handle
+    ?(optarg_proto = Dots)
+    name (ret, args, optargs) =
+  pr "%s" indent;
   if extern then pr "extern ";
   if static then pr "static ";
-  (match fst style with
-   | RErr -> pr "int "
-   | RInt _ -> pr "int "
-   | RInt64 _ -> pr "int64_t "
-   | RBool _ -> pr "int "
-   | RConstString _ | RConstOptString _ -> pr "const char *"
-   | RString _ | RBufferOut _ -> pr "char *"
-   | RStringList _ | RHashtable _ -> pr "char **"
+  (match ret with
+   | RErr
+   | RInt _
+   | RBool _ ->
+       pr "int";
+       if single_line then pr " " else pr "\n%s" indent
+   | RInt64 _ ->
+       pr "int64_t";
+       if single_line then pr " " else pr "\n%s" indent
+   | RConstString _ | RConstOptString _ ->
+       pr "const char *";
+       if not single_line then pr "\n%s" indent
+   | RString _ | RBufferOut _ ->
+       pr "char *";
+       if not single_line then pr "\n%s" indent
+   | RStringList _ | RHashtable _ ->
+       pr "char **";
+       if not single_line then pr "\n%s" indent
    | RStruct (_, typ) ->
        if not in_daemon then pr "struct guestfs_%s *" typ
-       else pr "guestfs_int_%s *" typ
+       else pr "guestfs_int_%s *" typ;
+       if not single_line then pr "\n%s" indent
    | RStructList (_, typ) ->
        if not in_daemon then pr "struct guestfs_%s_list *" typ
-       else pr "guestfs_int_%s_list *" typ
+       else pr "guestfs_int_%s_list *" typ;
+       if not single_line then pr "\n%s" indent
   );
-  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
@@ -65,7 +84,12 @@ let rec generate_prototype ?(extern = true) ?(static = false)
     );
     let next () =
       if !comma then (
-        if single_line then pr ", " else pr ",\n\t\t"
+        if single_line then pr ", "
+        else (
+          let namelen = String.length prefix + String.length name +
+                        String.length suffix + 2 in
+          pr ",\n%s%s" indent (spaces namelen)
+        )
       );
       comma := true
     in
@@ -92,15 +116,25 @@ let rec generate_prototype ?(extern = true) ?(static = false)
           pr "const char *%s" n;
           next ();
           pr "size_t %s_size" n
-    ) (snd style);
+      | Pointer (t, n) ->
+          next ();
+          pr "%s %s" t n
+    ) 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 +153,55 @@ 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;
+        generate_prototype ~extern:false ~indent:" " ~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 _ ->
@@ -168,8 +229,7 @@ I<The caller must free the strings and the array after use>.\n\n"
 or NULL if there was an error.
 I<The caller must call C<guestfs_free_%s> after use>.\n\n" typ typ
          | RStructList (_, typ) ->
-             pr "This function returns a C<struct guestfs_%s_list *>
-(see E<lt>guestfs-structs.hE<gt>),
+             pr "This function returns a C<struct guestfs_%s_list *>,
 or NULL if there was an error.
 I<The caller must call C<guestfs_free_%s_list> after use>.\n\n" typ typ
          | RHashtable _ ->
@@ -189,13 +249,36 @@ 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@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
+        );
+        (match lookup_api_version name with
+         | Some version -> pr "(Added in %s)\n\n" version
+         | None -> ()
+        );
+
+        (* Handling of optional argument variants. *)
+        if optargs <> [] then (
+          pr "=head2 %s_va\n\n" name;
+          generate_prototype ~extern:false ~indent:" " ~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;
+          generate_prototype ~extern:false ~indent:" " ~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
 
@@ -253,22 +336,120 @@ and generate_availability_pod () =
   pr "=back\n";
   pr "\n"
 
-(* Generate the guestfs-structs.h file. *)
-and generate_structs_h () =
+(* Generate the guestfs.h file. *)
+and generate_guestfs_h () =
   generate_header CStyle LGPLv2plus;
 
-  (* This is a public exported header file containing various
-   * structures.  The structures are carefully written to have
-   * exactly the same in-memory format as the XDR structures that
-   * we use on the wire to the daemon.  The reason for creating
-   * copies of these structures here is just so we don't have to
-   * export the whole of guestfs_protocol.h (which includes much
-   * unrelated and XDR-dependent stuff that we don't want to be
-   * public, or required by clients).
-   *
-   * To reiterate, we will pass these structures to and from the
-   * client with a simple assignment or memcpy, so the format
-   * must be identical to what rpcgen / the RFC defines.
+  pr "\
+/* ---------- IMPORTANT NOTE ----------
+ *
+ * All API documentation is in the manpage, 'guestfs(3)'.
+ * To read it, type:           man 3 guestfs
+ * Or read it online here:     http://libguestfs.org/guestfs.3.html
+ *
+ * Go and read it now, I'll be right here waiting for you
+ * when you come back.
+ *
+ * ------------------------------------
+ */
+
+#ifndef GUESTFS_H_
+#define GUESTFS_H_
+
+#ifdef __cplusplus
+extern \"C\" {
+#endif
+
+#include <stdint.h>
+#include <stdarg.h>
+#include <rpc/types.h>
+#include <rpc/xdr.h>
+
+/* The handle. */
+#ifndef GUESTFS_TYPEDEF_GUESTFS_H
+#define GUESTFS_TYPEDEF_GUESTFS_H 1
+typedef struct guestfs_h guestfs_h;
+#endif
+
+/* Connection management. */
+extern guestfs_h *guestfs_create (void);
+extern void guestfs_close (guestfs_h *g);
+
+/* Error handling. */
+extern const char *guestfs_last_error (guestfs_h *g);
+#define LIBGUESTFS_HAVE_LAST_ERRNO 1
+extern int guestfs_last_errno (guestfs_h *g);
+
+#ifndef GUESTFS_TYPEDEF_GUESTFS_ERROR_HANDLER_CB
+#define GUESTFS_TYPEDEF_GUESTFS_ERROR_HANDLER_CB 1
+typedef void (*guestfs_error_handler_cb) (guestfs_h *g, void *opaque, const char *msg);
+#endif
+
+#ifndef GUESTFS_TYPEDEF_GUESTFS_ABORT_CB
+#define GUESTFS_TYPEDEF_GUESTFS_ABORT_CB 1
+typedef void (*guestfs_abort_cb) (void) __attribute__((__noreturn__));
+#endif
+
+extern void guestfs_set_error_handler (guestfs_h *g, guestfs_error_handler_cb cb, void *opaque);
+extern guestfs_error_handler_cb guestfs_get_error_handler (guestfs_h *g, void **opaque_rtn);
+
+extern void guestfs_set_out_of_memory_handler (guestfs_h *g, guestfs_abort_cb);
+extern guestfs_abort_cb guestfs_get_out_of_memory_handler (guestfs_h *g);
+
+/* Events. */
+#ifndef GUESTFS_TYPEDEF_GUESTFS_LOG_MESSAGE_CB
+#define GUESTFS_TYPEDEF_GUESTFS_LOG_MESSAGE_CB 1
+typedef void (*guestfs_log_message_cb) (guestfs_h *g, void *opaque, char *buf, int len);
+#endif
+
+#ifndef GUESTFS_TYPEDEF_GUESTFS_SUBPROCESS_QUIT_CB
+#define GUESTFS_TYPEDEF_GUESTFS_SUBPROCESS_QUIT_CB 1
+typedef void (*guestfs_subprocess_quit_cb) (guestfs_h *g, void *opaque);
+#endif
+
+#ifndef GUESTFS_TYPEDEF_GUESTFS_LAUNCH_DONE_CB
+#define GUESTFS_TYPEDEF_GUESTFS_LAUNCH_DONE_CB 1
+typedef void (*guestfs_launch_done_cb) (guestfs_h *g, void *opaque);
+#endif
+
+#ifndef GUESTFS_TYPEDEF_GUESTFS_CLOSE_CB
+#define GUESTFS_TYPEDEF_GUESTFS_CLOSE_CB 1
+typedef void (*guestfs_close_cb) (guestfs_h *g, void *opaque);
+#endif
+
+#ifndef GUESTFS_TYPEDEF_GUESTFS_PROGRESS_CB
+#define GUESTFS_TYPEDEF_GUESTFS_PROGRESS_CB 1
+typedef void (*guestfs_progress_cb) (guestfs_h *g, void *opaque, int proc_nr, int serial, uint64_t position, uint64_t total);
+#endif
+
+extern void guestfs_set_log_message_callback (guestfs_h *g, guestfs_log_message_cb cb, void *opaque);
+extern void guestfs_set_subprocess_quit_callback (guestfs_h *g, guestfs_subprocess_quit_cb cb, void *opaque);
+extern void guestfs_set_launch_done_callback (guestfs_h *g, guestfs_launch_done_cb cb, void *opaque);
+#define LIBGUESTFS_HAVE_SET_CLOSE_CALLBACK 1
+extern void guestfs_set_close_callback (guestfs_h *g, guestfs_close_cb cb, void *opaque);
+#define LIBGUESTFS_HAVE_SET_PROGRESS_CALLBACK 1
+extern void guestfs_set_progress_callback (guestfs_h *g, guestfs_progress_cb cb, void *opaque);
+
+/* Private data area. */
+#define LIBGUESTFS_HAVE_SET_PRIVATE 1
+extern void guestfs_set_private (guestfs_h *g, const char *key, void *data);
+#define LIBGUESTFS_HAVE_GET_PRIVATE 1
+extern void *guestfs_get_private (guestfs_h *g, const char *key);
+
+/* Structures. */
+";
+
+  (* The structures are carefully written to have exactly the same
+   * in-memory format as the XDR structures that we use on the wire to
+   * the daemon.  The reason for creating copies of these structures
+   * here is just so we don't have to export the whole of
+   * guestfs_protocol.h (which includes much unrelated and
+   * XDR-dependent stuff that we don't want to be public, or required
+   * by clients).
+   * 
+   * To reiterate, we will pass these structures to and from the client
+   * with a simple assignment or memcpy, so the format must be
+   * identical to what rpcgen / the RFC defines.
    *)
 
   (* Public structures. *)
@@ -299,15 +480,14 @@ and generate_structs_h () =
       pr "extern void guestfs_free_%s (struct guestfs_%s *);\n" typ typ;
       pr "extern void guestfs_free_%s_list (struct guestfs_%s_list *);\n" typ typ;
       pr "\n"
-  ) structs
+  ) structs;
 
-(* Generate the guestfs-actions.h file. *)
-and generate_actions_h () =
-  generate_header CStyle LGPLv2plus;
-  List.iter (
-    fun (shortname, style, _, flags, _, _, _) ->
-      let name = "guestfs_" ^ shortname in
+  pr "\
+/* Actions. */
+";
 
+  List.iter (
+    fun (shortname, (ret, args, optargs as style), _, flags, _, _, _) ->
       let deprecated =
         List.exists (function DeprecatedBy _ -> true | _ -> false) flags in
       let test0 =
@@ -318,17 +498,71 @@ 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
-  ) all_functions_sorted
+        ~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 (UINT64_C(1)<<%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;
+
+  pr "\
+
+/* Private functions.
+ *
+ * These are NOT part of the public, stable API, and can change at any
+ * time!  We export them because they are used by some of the language
+ * bindings.
+ */
+extern void *guestfs_safe_malloc (guestfs_h *g, size_t nbytes);
+extern void *guestfs_safe_calloc (guestfs_h *g, size_t n, size_t s);
+extern const char *guestfs_tmpdir (void);
+/* End of private functions. */
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* GUESTFS_H_ */
+"
 
 (* Generate the guestfs-internal-actions.h file. *)
 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. *)
@@ -346,6 +580,7 @@ and generate_client_actions () =
 #include \"guestfs-internal.h\"
 #include \"guestfs-internal-actions.h\"
 #include \"guestfs_protocol.h\"
+#include \"errnostring.h\"
 
 /* Check the return message from a call for validity. */
 static int
@@ -408,7 +643,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
@@ -422,11 +657,12 @@ check_state (guestfs_h *g, const char *caller)
       | BufferIn n
       | StringList n
       | DeviceList n
-      | Key n ->
+      | Key n
+      | Pointer (_, n) ->
           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 +673,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 & UINT64_C(0x%Lx)) {\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 *)
@@ -463,11 +734,12 @@ check_state (guestfs_h *g, const char *caller)
       | Pathname n
       | Dev_or_Path n
       | FileIn n
-      | FileOut n
-      | BufferIn n
-      | Key n ->
+      | FileOut n ->
           (* guestfish doesn't support string escaping, so neither do we *)
           pr "    fprintf (stderr, \" \\\"%%s\\\"\", %s);\n" n
+      | Key n ->
+          (* don't print keys *)
+          pr "    fprintf (stderr, \" \\\"***\\\"\");\n"
       | OptString n ->                 (* string option *)
           pr "    if (%s) fprintf (stderr, \" \\\"%%s\\\"\", %s);\n" n n;
           pr "    else fprintf (stderr, \" null\");\n"
@@ -486,7 +758,34 @@ 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);
+      | BufferIn n ->                   (* RHBZ#646822 *)
+          pr "    fputc (' ', stderr);\n";
+          pr "    guestfs___print_BufferIn (stderr, %s, %s_size);\n" n n
+      | Pointer (t, n) ->
+          pr "    fprintf (stderr, \" (%s)%%p\", %s);\n" t n
+    ) 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 +793,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 +815,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 +840,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 +855,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 +863,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)
@@ -582,6 +894,7 @@ check_state (guestfs_h *g, const char *caller)
                  pr "  }\n";
                  pr "  args.%s.%s_val = (char *) %s;\n" n n n;
                  pr "  args.%s.%s_len = %s_size;\n" n n n
+             | Pointer _ -> assert false
            ) args;
            pr "  serial = guestfs___send (g, GUESTFS_PROC_%s,\n"
              (String.uppercase shortname);
@@ -609,7 +922,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";
@@ -638,8 +951,18 @@ check_state (guestfs_h *g, const char *caller)
       pr "\n";
 
       pr "  if (hdr.status == GUESTFS_STATUS_ERROR) {\n";
-      pr "    error (g, \"%%s: %%s\", \"%s\", err.error_message);\n" shortname;
+      pr "    int errnum = 0;\n";
+      pr "    if (err.errno_string[0] != '\\0')\n";
+      pr "      errnum = guestfs___string_to_errno (err.errno_string);\n";
+      pr "    if (errnum <= 0)\n";
+      pr "      error (g, \"%%s: %%s\", \"%s\", err.error_message);\n"
+        shortname;
+      pr "    else\n";
+      pr "      guestfs_error_errno (g, errnum, \"%%s: %%s\", \"%s\",\n"
+        shortname;
+      pr "                           err.error_message);\n";
       pr "    free (err.error_message);\n";
+      pr "    free (err.errno_string);\n";
       pr "    guestfs___end_busy (g);\n";
       pr "    return %s;\n" error_code;
       pr "  }\n";
@@ -655,11 +978,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 +1051,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 = UINT64_C(1) << 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.
@@ -741,6 +1153,7 @@ and generate_linker_script () =
     "guestfs_get_error_handler";
     "guestfs_get_out_of_memory_handler";
     "guestfs_get_private";
+    "guestfs_last_errno";
     "guestfs_last_error";
     "guestfs_set_close_callback";
     "guestfs_set_error_handler";
@@ -761,8 +1174,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, _) ->