fish: Add guestfish -N lv for creating disks with LVs.
[libguestfs.git] / src / generator.ml
index fb9735a..363e78f 100755 (executable)
@@ -2338,7 +2338,7 @@ C<filename> can also be a named pipe.
 
 See also C<guestfs_download>.");
 
-  ("download", (RErr, [Dev_or_Path "remotefilename"; FileOut "filename"]), 67, [],
+  ("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"];
@@ -4889,7 +4889,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],
+  ("zero_device", (RErr, [Device "device"]), 228, [DangerWillRobinson; Progress],
    [InitBasicFSonLVM, Always, TestRun (
       [["zero_device"; "/dev/VG/LV"]])],
    "write zeroes to an entire device",
@@ -5619,6 +5619,52 @@ type callt =
   | CallBool of bool
   | CallBuffer of string
 
+(* Used for the guestfish -N (prepared disk images) option.
+ * Note that the longdescs are indented by 2 spaces.
+ *)
+let prepopts = [
+  ("disk",
+   "create a blank disk",
+   [ "size", "100M", "the size of the disk image" ],
+   "  Create a blank disk, size 100MB (by default).
+
+  The default size can be changed by supplying an optional parameter.");
+
+  ("part",
+   "create a partitioned disk",
+   [ "size", "100M", "the size of the disk image";
+     "partition", "mbr", "partition table type" ],
+   "  Create a disk with a single partition.  By default the size of the disk
+  is 100MB (the available space in the partition will be a tiny bit smaller)
+  and the partition table will be MBR (old DOS-style).
+
+  These defaults can be changed by supplying optional parameters.");
+
+  ("fs",
+   "create a filesystem",
+   [ "filesystem", "ext2", "the type of filesystem to use";
+     "size", "100M", "the size of the disk image";
+     "partition", "mbr", "partition table type" ],
+   "  Create a disk with a single partition, with the partition containing
+  an empty filesystem.  This defaults to creating a 100MB disk (the available
+  space in the filesystem will be a tiny bit smaller) with an MBR (old
+  DOS-style) partition table and an ext2 filesystem.
+
+  These defaults can be changed by supplying optional parameters.");
+
+  ("lv",
+   "create a disk with logical volume",
+   [ "name", "/dev/VG/LV", "the name of the VG and LV to use";
+     "size", "100M", "the size of the disk image";
+     "partition", "mbr", "partition table type" ],
+   "  Create a disk with a single partition, set up the partition as an
+  LVM2 physical volume, and place a volume group and logical volume
+  on there.  This defaults to creating a 100MB disk with the VG and
+  LV called /dev/VG/LV.  You can change the name of the VG and LV
+  by supplying an alternate name as the first optional parameter.");
+
+]
+
 (* Used to memoize the result of pod2text. *)
 let pod2text_memo_filename = "src/.pod2text.data"
 let pod2text_memo : ((int * string * string), string list) Hashtbl.t =
@@ -6451,11 +6497,21 @@ and generate_structs_h () =
 and generate_actions_h () =
   generate_header CStyle LGPLv2plus;
   List.iter (
-    fun (shortname, style, _, _, _, _, _) ->
+    fun (shortname, style, _, flags, _, _, _) ->
       let name = "guestfs_" ^ shortname in
+
+      let deprecated =
+        List.exists (function DeprecatedBy _ -> true | _ -> false) flags in
+      let test0 =
+        String.length shortname >= 5 && String.sub shortname 0 5 = "test0" in
+      let debug =
+        String.length shortname >= 5 && String.sub shortname 0 5 = "debug" in
+      if not deprecated && not test0 && not debug then
+        pr "#define LIBGUESTFS_HAVE_%s 1\n" (String.uppercase shortname);
+
       generate_prototype ~single_line:true ~newline:true ~handle:"g"
         name style
-  ) all_functions
+  ) all_functions_sorted
 
 (* Generate the guestfs-internal-actions.h file. *)
 and generate_internal_actions_h () =
@@ -6890,12 +6946,14 @@ and generate_linker_script () =
     "guestfs_close";
     "guestfs_get_error_handler";
     "guestfs_get_out_of_memory_handler";
+    "guestfs_get_private";
     "guestfs_last_error";
     "guestfs_set_close_callback";
     "guestfs_set_error_handler";
     "guestfs_set_launch_done_callback";
     "guestfs_set_log_message_callback";
     "guestfs_set_out_of_memory_handler";
+    "guestfs_set_private";
     "guestfs_set_progress_callback";
     "guestfs_set_subprocess_quit_callback";
 
@@ -8798,6 +8856,84 @@ Guestfish will prompt for these separately.\n\n";
       | Some txt -> pr "%s\n\n" txt
   ) all_functions_sorted
 
+and generate_fish_prep_options_h () =
+  generate_header CStyle GPLv2plus;
+
+  pr "#ifndef PREPOPTS_H\n";
+  pr "\n";
+
+  pr "\
+struct prep {
+  const char *name;             /* eg. \"fs\" */
+
+  size_t nr_params;             /* optional parameters */
+  struct prep_param *params;
+
+  const char *shortdesc;        /* short description */
+  const char *longdesc;         /* long description */
+
+                                /* functions to implement it */
+  void (*prelaunch) (const char *filename, prep_data *);
+  void (*postlaunch) (const char *filename, prep_data *, const char *device);
+};
+
+struct prep_param {
+  const char *pname;            /* parameter name */
+  const char *pdefault;         /* parameter default */
+  const char *pdesc;            /* parameter description */
+};
+
+extern const struct prep preps[];
+#define NR_PREPS %d
+
+" (List.length prepopts);
+
+  List.iter (
+    fun (name, shortdesc, args, longdesc) ->
+      pr "\
+extern void prep_prelaunch_%s (const char *filename, prep_data *data);
+extern void prep_postlaunch_%s (const char *filename, prep_data *data, const char *device);
+
+" name name;
+  ) prepopts;
+
+  pr "\n";
+  pr "#endif /* PREPOPTS_H */\n"
+
+and generate_fish_prep_options_c () =
+  generate_header CStyle GPLv2plus;
+
+  pr "\
+#include \"fish.h\"
+#include \"prepopts.h\"
+
+";
+
+  List.iter (
+    fun (name, shortdesc, args, longdesc) ->
+      pr "static struct prep_param %s_args[] = {\n" name;
+      List.iter (
+        fun (n, default, desc) ->
+          pr "  { \"%s\", \"%s\", \"%s\" },\n" n default desc
+      ) args;
+      pr "};\n";
+      pr "\n";
+  ) prepopts;
+
+  pr "const struct prep preps[] = {\n";
+  List.iter (
+    fun (name, shortdesc, args, longdesc) ->
+      pr "  { \"%s\", %d, %s_args,
+    \"%s\",
+    \"%s\",
+    prep_prelaunch_%s, prep_postlaunch_%s },
+"
+        name (List.length args) name
+        (c_quote shortdesc) (c_quote longdesc)
+        name name;
+  ) prepopts;
+  pr "};\n"
+
 (* Generate a C function prototype. *)
 and generate_prototype ?(extern = true) ?(static = false) ?(semicolon = true)
     ?(single_line = false) ?(newline = false) ?(in_daemon = false)
@@ -8928,6 +9064,28 @@ val close : t -> unit
     unreferenced, but callers can call this in order to provide
     predictable cleanup. *)
 
+type progress_cb = int -> int -> int64 -> int64 -> unit
+
+val set_progress_callback : t -> progress_cb -> unit
+(** [set_progress_callback g f] sets [f] as the progress callback function.
+    For some long-running functions, [f] will be called repeatedly
+    during the function with progress updates.
+
+    The callback is [f proc_nr serial position total].  See
+    the description of [guestfs_set_progress_callback] in guestfs(3)
+    for the meaning of these four numbers.
+
+    Note that if the closure captures a reference to the handle,
+    this reference will prevent the handle from being
+    automatically closed by the garbage collector.  There are
+    three ways to avoid this: be careful not to capture the handle
+    in the closure, or use a weak reference, or call
+    {!Guestfs.clear_progress_callback} to remove the reference. *)
+
+val clear_progress_callback : t -> unit
+(** [clear_progress_callback g] removes any progress callback function
+    associated with the handle.  See {!Guestfs.set_progress_callback}. *)
+
 ";
   generate_ocaml_structure_decls ();
 
@@ -8952,6 +9110,13 @@ exception Handle_closed of string
 external create : unit -> t = \"ocaml_guestfs_create\"
 external close : t -> unit = \"ocaml_guestfs_close\"
 
+type progress_cb = int -> int -> int64 -> int64 -> unit
+
+external set_progress_callback : t -> progress_cb -> unit
+  = \"ocaml_guestfs_set_progress_callback\"
+external clear_progress_callback : t -> unit
+  = \"ocaml_guestfs_clear_progress_callback\"
+
 (* Give the exceptions names, so they can be raised from the C code. *)
 let () =
   Callback.register_exception \"ocaml_guestfs_error\" (Error \"\");
@@ -9399,6 +9564,46 @@ XS_unpack_charPtrPtr (SV *arg) {
   return ret;
 }
 
+#define PROGRESS_KEY \"_perl_progress_cb\"
+
+static void
+_clear_progress_callback (guestfs_h *g)
+{
+  guestfs_set_progress_callback (g, NULL, NULL);
+  SV *cb = guestfs_get_private (g, PROGRESS_KEY);
+  if (cb) {
+    guestfs_set_private (g, PROGRESS_KEY, NULL);
+    SvREFCNT_dec (cb);
+  }
+}
+
+/* http://www.perlmonks.org/?node=338857 */
+static void
+_progress_callback (guestfs_h *g, void *cb,
+                    int proc_nr, int serial, uint64_t position, uint64_t total)
+{
+  dSP;
+  ENTER;
+  SAVETMPS;
+  PUSHMARK (SP);
+  XPUSHs (sv_2mortal (newSViv (proc_nr)));
+  XPUSHs (sv_2mortal (newSViv (serial)));
+  XPUSHs (sv_2mortal (my_newSVull (position)));
+  XPUSHs (sv_2mortal (my_newSVull (total)));
+  PUTBACK;
+  call_sv ((SV *) cb, G_VOID | G_DISCARD | G_EVAL);
+  FREETMPS;
+  LEAVE;
+}
+
+static void
+_close_handle (guestfs_h *g)
+{
+  assert (g != NULL);
+  _clear_progress_callback (g);
+  guestfs_close (g);
+}
+
 MODULE = Sys::Guestfs  PACKAGE = Sys::Guestfs
 
 PROTOTYPES: ENABLE
@@ -9426,19 +9631,34 @@ DESTROY (sv)
       SV **svp = hv_fetch (hv, \"_g\", 2, 0);
       if (svp != NULL) {
         guestfs_h *g = (guestfs_h *) SvIV (*svp);
-        assert (g != NULL);
-        guestfs_close (g);
+        _close_handle (g);
       }
 
 void
 close (g)
       guestfs_h *g;
  PPCODE:
-      guestfs_close (g);
+      _close_handle (g);
       /* Avoid double-free in DESTROY method. */
       HV *hv = (HV *) SvRV (ST(0));
       (void) hv_delete (hv, \"_g\", 2, G_DISCARD);
 
+void
+set_progress_callback (g, cb)
+      guestfs_h *g;
+      SV *cb;
+ PPCODE:
+      _clear_progress_callback (g);
+      SvREFCNT_inc (cb);
+      guestfs_set_private (g, PROGRESS_KEY, cb);
+      guestfs_set_progress_callback (g, _progress_callback, cb);
+
+void
+clear_progress_callback (g)
+      guestfs_h *g;
+ PPCODE:
+      _clear_progress_callback (g);
+
 ";
 
   List.iter (
@@ -9812,6 +10032,25 @@ C<close> the program must not call any method (including C<close>)
 on the handle (but the implicit call to C<DESTROY> that happens
 when the final reference is cleaned up is OK).
 
+=item $h->set_progress_callback (\\&cb);
+
+Set the progress notification callback for this handle
+to the Perl closure C<cb>.
+
+C<cb> will be called whenever a long-running operation
+generates a progress notification message.  The 4 parameters
+to the function are: C<proc_nr>, C<serial>, C<position>
+and C<total>.
+
+You should carefully read the documentation for
+L<guestfs(3)/guestfs_set_progress_callback> before using
+this function.
+
+=item $h->clear_progress_callback ();
+
+This removes any progress callback function associated with
+the handle.
+
 =cut
 
 " max_proc_nr;
@@ -9845,6 +10084,55 @@ when the final reference is cleaned up is OK).
 
 =back
 
+=head1 AVAILABILITY
+
+From time to time we add new libguestfs APIs.  Also some libguestfs
+APIs won't be available in all builds of libguestfs (the Fedora
+build is full-featured, but other builds may disable features).
+How do you test whether the APIs that your Perl program needs are
+available in the version of C<Sys::Guestfs> that you are using?
+
+To test if a particular function is available in the C<Sys::Guestfs>
+class, use the ordinary Perl UNIVERSAL method C<can(METHOD)>
+(see L<perlobj(1)>).  For example:
+
+ use Sys::Guestfs;
+ if (defined (Sys::Guestfs->can (\"set_verbose\"))) {
+   print \"\\$h->set_verbose is available\\n\";
+ }
+
+To test if particular features are supported by the current
+build, use the L</available> method like the example below.  Note
+that the appliance must be launched first.
+
+ $h->available ( [\"augeas\"] );
+
+Since the L</available> method croaks if the feature is not supported,
+you might also want to wrap this in an eval and return a boolean.
+In fact this has already been done for you: use
+L<Sys::Guestfs::Lib(3)/feature_available>.
+
+For further discussion on this topic, refer to
+L<guestfs(3)/AVAILABILITY>.
+
+=head1 STORING DATA IN THE HANDLE
+
+The handle returned from L</new> is a hash reference.  The hash
+normally contains a single element:
+
+ {
+   _g => [private data used by libguestfs]
+ }
+
+Callers can add other elements to this hash to store data for their own
+purposes.  The data lasts for the lifetime of the handle.
+
+Any fields whose names begin with an underscore are reserved
+for private use by libguestfs.  We may add more in future.
+
+It is recommended that callers prefix the name of their field(s)
+with some unique string, to avoid conflicts with other users.
+
 =head1 COPYRIGHT
 
 Copyright (C) %s Red Hat Inc.
@@ -10681,6 +10969,10 @@ void Init__guestfs ()
   c_guestfs = rb_define_class_under (m_guestfs, \"Guestfs\", rb_cObject);
   e_Error = rb_define_class_under (m_guestfs, \"Error\", rb_eStandardError);
 
+#ifdef HAVE_RB_DEFINE_ALLOC_FUNC
+  rb_define_alloc_func (c_guestfs, ruby_guestfs_create);
+#endif
+
   rb_define_module_function (m_guestfs, \"create\", ruby_guestfs_create, 0);
   rb_define_method (c_guestfs, \"close\", ruby_guestfs_close, 0);
 
@@ -11810,6 +12102,431 @@ namespace Guestfs
 }
 "
 
+and generate_php_h () =
+  generate_header CStyle LGPLv2plus;
+
+  pr "\
+#ifndef PHP_GUESTFS_PHP_H
+#define PHP_GUESTFS_PHP_H 1
+
+#ifdef ZTS
+#include \"TSRM.h\"
+#endif
+
+#define PHP_GUESTFS_PHP_EXTNAME \"guestfs_php\"
+#define PHP_GUESTFS_PHP_VERSION \"1.0\"
+
+PHP_MINIT_FUNCTION (guestfs_php);
+
+#define PHP_GUESTFS_HANDLE_RES_NAME \"guestfs_h\"
+
+PHP_FUNCTION (guestfs_create);
+PHP_FUNCTION (guestfs_last_error);
+";
+
+  List.iter (
+    fun (shortname, style, _, _, _, _, _) ->
+      pr "PHP_FUNCTION (guestfs_%s);\n" shortname
+  ) all_functions_sorted;
+
+  pr "\
+
+extern zend_module_entry guestfs_php_module_entry;
+#define phpext_guestfs_php_ptr &guestfs_php_module_entry
+
+#endif /* PHP_GUESTFS_PHP_H */
+"
+
+and generate_php_c () =
+  generate_header CStyle LGPLv2plus;
+
+  pr "\
+/* NOTE: Be very careful with all macros in PHP header files.  The
+ * morons who wrote them aren't good at making them safe for inclusion
+ * in arbitrary places in C code, eg. not using 'do ... while(0)'
+ * or parenthesizing any of the arguments.
+ */
+
+/* NOTE (2): Some parts of the API can't be used on 32 bit platforms.
+ * Any 64 bit numbers will be truncated.  There's no easy way around
+ * this in PHP.
+ */
+
+#include <config.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+
+#include <php.h>
+#include <php_guestfs_php.h>
+
+#include \"guestfs.h\"
+
+static int res_guestfs_h;
+
+static void
+guestfs_php_handle_dtor (zend_rsrc_list_entry *rsrc TSRMLS_DC)
+{
+  guestfs_h *g = (guestfs_h *) rsrc->ptr;
+  if (g != NULL)
+    guestfs_close (g);
+}
+
+PHP_MINIT_FUNCTION (guestfs_php)
+{
+  res_guestfs_h =
+    zend_register_list_destructors_ex (guestfs_php_handle_dtor,
+    NULL, PHP_GUESTFS_HANDLE_RES_NAME, module_number);
+}
+
+static function_entry guestfs_php_functions[] = {
+  PHP_FE (guestfs_create, NULL)
+  PHP_FE (guestfs_last_error, NULL)
+";
+
+  List.iter (
+    fun (shortname, style, _, _, _, _, _) ->
+      pr "  PHP_FE (guestfs_%s, NULL)\n" shortname
+  ) all_functions_sorted;
+
+  pr "  { NULL, NULL, NULL }
+};
+
+zend_module_entry guestfs_php_module_entry = {
+#if ZEND_MODULE_API_NO >= 20010901
+  STANDARD_MODULE_HEADER,
+#endif
+  PHP_GUESTFS_PHP_EXTNAME,
+  guestfs_php_functions,
+  PHP_MINIT (guestfs_php),
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+#if ZEND_MODULE_API_NO >= 20010901
+  PHP_GUESTFS_PHP_VERSION,
+#endif
+  STANDARD_MODULE_PROPERTIES
+};
+
+#ifdef COMPILE_DL_GUESTFS_PHP
+ZEND_GET_MODULE (guestfs_php)
+#endif
+
+PHP_FUNCTION (guestfs_create)
+{
+  guestfs_h *g = guestfs_create ();
+  if (g == NULL) {
+    RETURN_FALSE;
+  }
+
+  guestfs_set_error_handler (g, NULL, NULL);
+
+  ZEND_REGISTER_RESOURCE (return_value, g, res_guestfs_h);
+}
+
+PHP_FUNCTION (guestfs_last_error)
+{
+  zval *z_g;
+  guestfs_h *g;
+
+  if (zend_parse_parameters (ZEND_NUM_ARGS() TSRMLS_CC, \"r\",
+                             &z_g) == FAILURE) {
+    RETURN_FALSE;
+  }
+
+  ZEND_FETCH_RESOURCE (g, guestfs_h *, &z_g, -1, PHP_GUESTFS_HANDLE_RES_NAME,
+                       res_guestfs_h);
+  if (g == NULL) {
+    RETURN_FALSE;
+  }
+
+  const char *err = guestfs_last_error (g);
+  if (err) {
+    RETURN_STRING (err, 1);
+  } else {
+    RETURN_NULL ();
+  }
+}
+
+";
+
+  (* Now generate the PHP bindings for each action. *)
+  List.iter (
+    fun (shortname, style, _, _, _, _, _) ->
+      pr "PHP_FUNCTION (guestfs_%s)\n" shortname;
+      pr "{\n";
+      pr "  zval *z_g;\n";
+      pr "  guestfs_h *g;\n";
+
+      List.iter (
+        function
+        | String n | Device n | Pathname n | Dev_or_Path n
+        | FileIn n | FileOut n | Key n
+        | OptString n
+        | BufferIn n ->
+            pr "  char *%s;\n" n;
+            pr "  int %s_size;\n" n
+        | StringList n
+        | DeviceList n ->
+            pr "  zval *z_%s;\n" n;
+            pr "  char **%s;\n" n;
+        | Bool n ->
+            pr "  zend_bool %s;\n" n
+        | Int n | Int64 n ->
+            pr "  long %s;\n" n
+        ) (snd style);
+
+      pr "\n";
+
+      (* Parse the parameters. *)
+      let param_string = String.concat "" (
+        List.map (
+          function
+          | String n | Device n | Pathname n | Dev_or_Path n
+          | FileIn n | FileOut n | BufferIn n | Key n -> "s"
+          | OptString n -> "s!"
+          | StringList n | DeviceList n -> "a"
+          | Bool n -> "b"
+          | Int n | Int64 n -> "l"
+        ) (snd style)
+      ) in
+
+      pr "  if (zend_parse_parameters (ZEND_NUM_ARGS() TSRMLS_CC, \"r%s\",\n"
+        param_string;
+      pr "        &z_g";
+      List.iter (
+        function
+        | String n | Device n | Pathname n | Dev_or_Path n
+        | FileIn n | FileOut n | BufferIn n | Key n
+        | OptString n ->
+            pr ", &%s, &%s_size" n n
+        | StringList n | DeviceList n ->
+            pr ", &z_%s" n
+        | Bool n ->
+            pr ", &%s" n
+        | Int n | Int64 n ->
+            pr ", &%s" n
+      ) (snd style);
+      pr ") == FAILURE) {\n";
+      pr "    RETURN_FALSE;\n";
+      pr "  }\n";
+      pr "\n";
+      pr "  ZEND_FETCH_RESOURCE (g, guestfs_h *, &z_g, -1, PHP_GUESTFS_HANDLE_RES_NAME,\n";
+      pr "                       res_guestfs_h);\n";
+      pr "  if (g == NULL) {\n";
+      pr "    RETURN_FALSE;\n";
+      pr "  }\n";
+      pr "\n";
+
+      List.iter (
+        function
+        | String n | Device n | Pathname n | Dev_or_Path n
+        | FileIn n | FileOut n | Key n
+        | OptString n ->
+            (* Just need to check the string doesn't contain any ASCII
+             * NUL characters, which won't be supported by the C API.
+             *)
+            pr "  if (strlen (%s) != %s_size) {\n" n n;
+            pr "    fprintf (stderr, \"libguestfs: %s: parameter '%s' contains embedded ASCII NUL.\\n\");\n" shortname n;
+            pr "    RETURN_FALSE;\n";
+            pr "  }\n";
+            pr "\n"
+        | BufferIn n -> ()
+        | StringList n
+        | DeviceList n ->
+            (* Convert array to list of strings.
+             * http://marc.info/?l=pecl-dev&m=112205192100631&w=2
+             *)
+            pr "  {\n";
+            pr "    HashTable *a;\n";
+            pr "    int n;\n";
+            pr "    HashPosition p;\n";
+            pr "    zval **d;\n";
+            pr "    size_t c = 0;\n";
+            pr "\n";
+            pr "    a = Z_ARRVAL_P (z_%s);\n" n;
+            pr "    n = zend_hash_num_elements (a);\n";
+            pr "    %s = safe_emalloc (n + 1, sizeof (char *), 0);\n" n;
+            pr "    for (zend_hash_internal_pointer_reset_ex (a, &p);\n";
+            pr "         zend_hash_get_current_data_ex (a, (void **) &d, &p) == SUCCESS;\n";
+            pr "         zend_hash_move_forward_ex (a, &p)) {\n";
+            pr "      zval t = **d;\n";
+            pr "      zval_copy_ctor (&t);\n";
+            pr "      convert_to_string (&t);\n";
+            pr "      %s[c] = Z_STRVAL (t);\n" n;
+            pr "      c++;\n";
+            pr "    }\n";
+            pr "    %s[c] = NULL;\n" n;
+            pr "  }\n";
+            pr "\n"
+        | Bool n | Int n | Int64 n -> ()
+        ) (snd style);
+
+      (* Return value. *)
+      let error_code =
+        match fst style with
+        | RErr -> pr "  int r;\n"; "-1"
+        | RBool _
+        | RInt _ -> pr "  int r;\n"; "-1"
+        | RInt64 _ -> pr "  int64_t r;\n"; "-1"
+        | RConstString _ -> pr "  const char *r;\n"; "NULL"
+        | RConstOptString _ -> pr "  const char *r;\n"; "NULL"
+        | RString _ ->
+            pr "  char *r;\n"; "NULL"
+        | RStringList _ ->
+            pr "  char **r;\n"; "NULL"
+        | RStruct (_, typ) ->
+            pr "  struct guestfs_%s *r;\n" typ; "NULL"
+        | RStructList (_, typ) ->
+            pr "  struct guestfs_%s_list *r;\n" typ; "NULL"
+        | RHashtable _ ->
+            pr "  char **r;\n"; "NULL"
+        | RBufferOut _ ->
+            pr "  char *r;\n";
+            pr "  size_t size;\n";
+            "NULL" in
+
+      (* Call the function. *)
+      pr "  r = guestfs_%s " shortname;
+      generate_c_call_args ~handle:"g" style;
+      pr ";\n";
+      pr "\n";
+
+      (* Free up parameters. *)
+      List.iter (
+        function
+        | String n | Device n | Pathname n | Dev_or_Path n
+        | FileIn n | FileOut n | Key n
+        | OptString n -> ()
+        | BufferIn n -> ()
+        | StringList n
+        | DeviceList n ->
+            pr "  {\n";
+            pr "    size_t c = 0;\n";
+            pr "\n";
+            pr "    for (c = 0; %s[c] != NULL; ++c)\n" n;
+            pr "      efree (%s[c]);\n" n;
+            pr "    efree (%s);\n" n;
+            pr "  }\n";
+            pr "\n"
+        | Bool n | Int n | Int64 n -> ()
+        ) (snd style);
+
+      (* Check for errors. *)
+      pr "  if (r == %s) {\n" error_code;
+      pr "    RETURN_FALSE;\n";
+      pr "  }\n";
+      pr "\n";
+
+      (* Convert the return value. *)
+      (match fst style with
+       | RErr ->
+           pr "  RETURN_TRUE;\n"
+       | RBool _ ->
+           pr "  RETURN_BOOL (r);\n"
+       | RInt _ ->
+           pr "  RETURN_LONG (r);\n"
+       | RInt64 _ ->
+           pr "  RETURN_LONG (r);\n"
+       | RConstString _ ->
+           pr "  RETURN_STRING (r, 1);\n"
+       | RConstOptString _ ->
+           pr "  if (r) { RETURN_STRING (r, 1); }\n";
+           pr "  else { RETURN_NULL (); }\n"
+       | RString _ ->
+           pr "  char *r_copy = estrdup (r);\n";
+           pr "  free (r);\n";
+           pr "  RETURN_STRING (r_copy, 0);\n"
+       | RBufferOut _ ->
+           pr "  char *r_copy = estrndup (r, size);\n";
+           pr "  free (r);\n";
+           pr "  RETURN_STRING (r_copy, 0);\n"
+       | RStringList _ ->
+           pr "  size_t c = 0;\n";
+           pr "  array_init (return_value);\n";
+           pr "  for (c = 0; r[c] != NULL; ++c) {\n";
+           pr "    add_next_index_string (return_value, r[c], 1);\n";
+           pr "    free (r[c]);\n";
+           pr "  }\n";
+           pr "  free (r);\n";
+       | RHashtable _ ->
+           pr "  size_t c = 0;\n";
+           pr "  array_init (return_value);\n";
+           pr "  for (c = 0; r[c] != NULL; c += 2) {\n";
+           pr "    add_assoc_string (return_value, r[c], r[c+1], 1);\n";
+           pr "    free (r[c]);\n";
+           pr "    free (r[c+1]);\n";
+           pr "  }\n";
+           pr "  free (r);\n";
+       | RStruct (_, typ) ->
+           let cols = cols_of_struct typ in
+           generate_php_struct_code typ cols
+       | RStructList (_, typ) ->
+           let cols = cols_of_struct typ in
+           generate_php_struct_list_code typ cols
+      );
+
+      pr "}\n";
+      pr "\n"
+  ) all_functions_sorted
+
+and generate_php_struct_code typ cols =
+  pr "  array_init (return_value);\n";
+  List.iter (
+    function
+    | name, FString ->
+        pr "  add_assoc_string (return_value, \"%s\", r->%s, 1);\n" name name
+    | name, FBuffer ->
+        pr "  add_assoc_stringl (return_value, \"%s\", r->%s, r->%s_len, 1);\n"
+          name name name
+    | name, FUUID ->
+        pr "  add_assoc_stringl (return_value, \"%s\", r->%s, 32, 1);\n"
+          name name
+    | name, (FBytes|FUInt64|FInt64|FInt32|FUInt32) ->
+        pr "  add_assoc_long (return_value, \"%s\", r->%s);\n"
+          name name
+    | name, FChar ->
+        pr "  add_assoc_stringl (return_value, \"%s\", &r->%s, 1, 1);\n"
+          name name
+    | name, FOptPercent ->
+        pr "  add_assoc_double (return_value, \"%s\", r->%s);\n"
+          name name
+  ) cols;
+  pr "  guestfs_free_%s (r);\n" typ
+
+and generate_php_struct_list_code typ cols =
+  pr "  array_init (return_value);\n";
+  pr "  size_t c = 0;\n";
+  pr "  for (c = 0; c < r->len; ++c) {\n";
+  pr "    zval *z_elem;\n";
+  pr "    ALLOC_INIT_ZVAL (z_elem);\n";
+  pr "    array_init (z_elem);\n";
+  List.iter (
+    function
+    | name, FString ->
+        pr "    add_assoc_string (z_elem, \"%s\", r->val[c].%s, 1);\n"
+          name name
+    | name, FBuffer ->
+        pr "    add_assoc_stringl (z_elem, \"%s\", r->val[c].%s, r->val[c].%s_len, 1);\n"
+          name name name
+    | name, FUUID ->
+        pr "    add_assoc_stringl (z_elem, \"%s\", r->val[c].%s, 32, 1);\n"
+          name name
+    | name, (FBytes|FUInt64|FInt64|FInt32|FUInt32) ->
+        pr "    add_assoc_long (z_elem, \"%s\", r->val[c].%s);\n"
+          name name
+    | name, FChar ->
+        pr "    add_assoc_stringl (z_elem, \"%s\", &r->val[c].%s, 1, 1);\n"
+          name name
+    | name, FOptPercent ->
+        pr "    add_assoc_double (z_elem, \"%s\", r->val[c].%s);\n"
+          name name
+  ) cols;
+  pr "    add_next_index_zval (return_value, z_elem);\n";
+  pr "  }\n";
+  pr "  guestfs_free_%s_list (r);\n" typ
+
 and generate_bindtests () =
   generate_header CStyle LGPLv2plus;
 
@@ -12322,6 +13039,8 @@ Run it from the top source directory using the command
   output_to "fish/cmds.c" generate_fish_cmds;
   output_to "fish/completion.c" generate_fish_completion;
   output_to "fish/guestfish-actions.pod" generate_fish_actions_pod;
+  output_to "fish/prepopts.c" generate_fish_prep_options_c;
+  output_to "fish/prepopts.h" generate_fish_prep_options_h;
   output_to "ocaml/guestfs.mli" generate_ocaml_mli;
   output_to "ocaml/guestfs.ml" generate_ocaml_ml;
   output_to "ocaml/guestfs_c_actions.c" generate_ocaml_c;
@@ -12349,6 +13068,8 @@ Run it from the top source directory using the command
   output_to "haskell/Guestfs.hs" generate_haskell_hs;
   output_to "haskell/Bindtests.hs" generate_haskell_bindtests;
   output_to "csharp/Libguestfs.cs" generate_csharp;
+  output_to "php/extension/php_guestfs_php.h" generate_php_h;
+  output_to "php/extension/guestfs_php.c" generate_php_c;
 
   (* Always generate this file last, and unconditionally.  It's used
    * by the Makefile to know when we must re-run the generator.