PHP bindings.
authorRichard Jones <rjones@redhat.com>
Fri, 3 Sep 2010 11:15:00 +0000 (12:15 +0100)
committerRichard Jones <rjones@redhat.com>
Sat, 4 Sep 2010 12:38:03 +0000 (13:38 +0100)
Note that these are not complete on 32 bit architectures.  PHP doesn't
offer any convenient 64 bit type (on 32 bit).  Therefore you should
always use these PHP bindings on 64 bit.

15 files changed:
.gitignore
Makefile.am
README
configure.ac
php/Makefile.am [new file with mode: 0644]
php/README-PHP [new file with mode: 0644]
php/extension/config.m4 [new file with mode: 0644]
php/extension/guestfs_php_001.phpt [new file with mode: 0644]
php/extension/guestfs_php_002.phpt [new file with mode: 0644]
php/extension/guestfs_php_003.phpt [new file with mode: 0644]
php/guestfs_php.ini [new file with mode: 0644]
php/run-php-tests.sh [new file with mode: 0755]
po/POTFILES.in
src/generator.ml
src/guestfs.pod

index 56ad9bd..cfea549 100644 (file)
@@ -181,6 +181,24 @@ perl/Makefile-pl
 perl/Makefile-pl.old
 perl/Makefile.PL
 perl/pm_to_blib
+php/extension/Makefile.fragments
+php/extension/Makefile.global
+php/extension/Makefile.objects
+php/extension/acinclude.m4
+php/extension/build/
+php/extension/config.nice
+php/extension/configure.in
+php/extension/guestfs_php.c
+php/extension/guestfs_php_*.diff
+php/extension/guestfs_php_*.exp
+php/extension/guestfs_php_*.log
+php/extension/guestfs_php_*.out
+php/extension/guestfs_php_*.php
+php/extension/mkinstalldirs
+php/extension/modules/
+php/extension/php_guestfs_php.h
+php/extension/run-tests.php
+php/extension/tmp-php.ini
 pod2htm?.tmp
 po/*.gmo
 po/Makevars.template
index ce2f940..e9b38ea 100644 (file)
@@ -54,6 +54,9 @@ endif
 if HAVE_HASKELL
 SUBDIRS += haskell
 endif
+if HAVE_PHP
+SUBDIRS += php
+endif
 
 # Virt-inspector, tools and guestmount.
 if HAVE_INSPECTOR
diff --git a/README b/README
index 199f595..f3e348f 100644 (file)
--- a/README
+++ b/README
@@ -18,7 +18,7 @@ FUSE.
 
 Libguestfs is a library that can be linked with C and C++ management
 programs (or management programs written in OCaml, Perl, Python, Ruby,
-Java, Haskell or C#).  You can also use it from shell scripts or the
+Java, PHP, Haskell or C#).  You can also use it from shell scripts or the
 command line.
 
 Libguestfs was written by Richard W.M. Jones (rjones@redhat.com) and
@@ -96,6 +96,8 @@ in virt-inspector).
 
 - (Optional) po4a for translating manpages and POD files.
 
+- (Optional) PHP, phpize if you want to build the PHP bindings
+
 Running ./configure will check you have all the requirements installed
 on your machine.
 
index 935abdc..5a44d8b 100644 (file)
@@ -772,6 +772,11 @@ dnl po4a for translating man pages and POD files (optional).
 AC_CHECK_PROG([PO4A],[po4a],[po4a],[no])
 AM_CONDITIONAL([HAVE_PO4A], [test "x$PO4A" != "xno"])
 
+dnl PHP
+AC_CHECK_PROG([PHP],[php],[php],[no])
+AC_CHECK_PROG([PHPIZE],[phpize],[phpize],[no])
+AM_CONDITIONAL([HAVE_PHP], [test "x$PHP" != "xno" -a "x$PHPIZE" != "xno"])
+
 dnl Library versioning.
 MAX_PROC_NR=`cat $srcdir/src/MAX_PROC_NR`
 AC_SUBST(MAX_PROC_NR)
@@ -808,6 +813,7 @@ AC_CONFIG_FILES([Makefile
                  fuse/Makefile
                  po-docs/Makefile
                  po-docs/ja/Makefile
+                 php/Makefile
                  ocaml/META perl/Makefile.PL])
 AC_OUTPUT
 
@@ -834,6 +840,8 @@ echo -n "Java bindings ....................... "
 if test "x$HAVE_JAVA_TRUE" = "x"; then echo "yes"; else echo "no"; fi
 echo -n "Haskell bindings .................... "
 if test "x$HAVE_HASKELL_TRUE" = "x"; then echo "yes"; else echo "no"; fi
+echo -n "PHP bindings ........................ "
+if test "x$HAVE_PHP_TRUE" = "x"; then echo "yes"; else echo "no"; fi
 echo -n "virt-inspector ...................... "
 if test "x$HAVE_INSPECTOR_TRUE" = "x"; then echo "yes"; else echo "no"; fi
 echo -n "virt-* tools ........................ "
diff --git a/php/Makefile.am b/php/Makefile.am
new file mode 100644 (file)
index 0000000..49efcde
--- /dev/null
@@ -0,0 +1,58 @@
+# libguestfs PHP 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.
+
+include $(top_srcdir)/subdir-rules.mk
+
+generator_built = \
+       extension/php_guestfs_php.h \
+       extension/guestfs_php.c
+
+EXTRA_DIST = \
+       $(generator_built) \
+       run-php-tests.sh \
+       extension/guestfs_php_*.phpt \
+       extension/config.m4 \
+       README-PHP \
+       guestfs_php.ini
+
+if HAVE_PHP
+
+phpdir = $(sysconfdir)/php.d
+php_DATA = guestfs_php.ini
+
+# In theory: EXTRA_LIBS="-lguestfs"  In fact this doesn't work
+# and we need to add the library to EXTRA_LDFLAGS.
+all: extension/config.h
+       $(MAKE) -C extension \
+         EXTRA_INCLUDES="-I$(abs_srcdir)/../src" \
+         EXTRA_LDFLAGS="-L$(abs_srcdir)/../src/.libs -lguestfs" \
+         all
+
+extension/config.h: extension/config.m4 ../config.status
+       cd extension && phpize
+       cd extension && ./configure --prefix=$(prefix) --libdir=$(libdir)
+       test -f "$@" && touch -- $@
+
+TESTS = run-php-tests.sh
+
+clean-local:
+       $(MAKE) -C extension clean
+
+install-data-hook:
+       $(MAKE) -C extension INSTALL_ROOT=$(DESTDIR) install
+
+endif
diff --git a/php/README-PHP b/php/README-PHP
new file mode 100644 (file)
index 0000000..b5ad3c3
--- /dev/null
@@ -0,0 +1,54 @@
+NOTE: The PHP API is not complete on 32 bit architectures.  PHP
+doesn't offer any convenient 64 bit type (on 32 bit).  Any 64 bit
+parameters or return values will be truncated to 32 bits on these
+platforms.  You should always use these PHP bindings on a 64 bit
+operating system.
+
+To install the extension manually, copy guestfs_php.so into the
+modules directory (eg. /usr/local/lib/php/modules/) and copy
+guestfs_php.ini into the config directory (eg. /etc/php.d/).
+[Note: On packaged Linux distributions you don't need to do this]
+
+The PHP API follows the C API.  Refer to guestfs(3) or
+http://libguestfs.org/guestfs.3.html for the details of the C API.
+
+To create a handle, use guestfs_create() like this:
+
+  <?php
+  $g = guestfs_create ();
+  if ($g == false) {
+    echo ("Failed to create guestfs_php handle.\n");
+    exit;
+  }
+  ?>
+
+Handles are closed implicitly by the PHP dtor.
+
+All of the usual functions from the C API are available.  By
+convention these return 'false' for errors, so:
+
+  <?php
+  //...
+  if (guestfs_launch ($g) == false) {
+    echo ("Error: ".guestfs_last_error ($g)."\n");
+    exit;
+  }
+  ?>
+
+or:
+
+  <?php
+  //...
+  $version = guestfs_version ($g);
+  if ($version == false) {
+    echo ("Error: ".guestfs_last_error ($g)."\n");
+    exit;
+  }
+  echo ("libguestfs version = ".
+        $version["major"].".".$version["minor"].".".$version["release"].
+        $version["extra"]."\n");
+  ?>
+
+C API structs are mapped to associative arrays.  C API lists of
+structs are mapped to arrays of associative arrays.  Other C API
+parameters and return values are mapped to natural PHP types.
diff --git a/php/extension/config.m4 b/php/extension/config.m4
new file mode 100644 (file)
index 0000000..2bac2ea
--- /dev/null
@@ -0,0 +1,24 @@
+# libguestfs PHP 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.
+
+PHP_ARG_ENABLE(guestfs_php, enable libguestfs PHP bindings,
+               [ --enable-guestfs-php   Enable libguestfs support])
+
+if test "$PHP_GUESTFS_PHP" = "yes"; then
+  AC_DEFINE(HAVE_GUESTFS_PHP, 1, [Whether you have libguestfs PHP bindings])
+  PHP_NEW_EXTENSION(guestfs_php, guestfs_php.c, $ext_shared)
+fi
diff --git a/php/extension/guestfs_php_001.phpt b/php/extension/guestfs_php_001.phpt
new file mode 100644 (file)
index 0000000..771592f
--- /dev/null
@@ -0,0 +1,17 @@
+--TEST--
+Load the module and create a handle.
+--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;
+}
+echo ("Created guestfs_php handle.\n");
+?>
+--EXPECT--
+Created guestfs_php handle.
diff --git a/php/extension/guestfs_php_002.phpt b/php/extension/guestfs_php_002.phpt
new file mode 100644 (file)
index 0000000..48ee0b6
--- /dev/null
@@ -0,0 +1,36 @@
+--TEST--
+Launch the appliance.
+--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 ($g, "/dev/null") == false) {
+  echo ("Error: ".guestfs_last_error ($g)."\n");
+  exit;
+}
+if (guestfs_launch ($g) == false) {
+  echo ("Error: ".guestfs_last_error ($g)."\n");
+  exit;
+}
+$version = guestfs_version ($g);
+if ($version == false) {
+  echo ("Error: ".guestfs_last_error ($g)."\n");
+  exit;
+}
+if (!is_int ($version["major"]) ||
+    !is_int ($version["minor"]) ||
+    !is_int ($version["release"]) ||
+    !is_string ($version["extra"])) {
+  echo ("Error: incorrect return type from guestfs_version\n");
+}
+echo ("OK\n");
+?>
+--EXPECT--
+OK
diff --git a/php/extension/guestfs_php_003.phpt b/php/extension/guestfs_php_003.phpt
new file mode 100644 (file)
index 0000000..c4eb5b0
--- /dev/null
@@ -0,0 +1,39 @@
+--TEST--
+Create a disk containing LV and filesystem.
+--FILE--
+<?php
+
+// See comment in php/run-php-tests.sh.
+//putenv ('LIBGUESTFS_DEBUG=1');
+
+$g = guestfs_create ();
+if ($g == false) {
+  die ("Failed to create guestfs_php handle.\n");
+}
+
+$tmp = dirname(__FILE__)."/test.img";
+$size = 100 * 1024 * 1024;
+if (! $fp = fopen ($tmp, 'r+')) {
+  die ("Error: cannot create file '".$tmp."'\n");
+}
+ftruncate ($fp, $size);
+fclose ($fp);
+
+if (! guestfs_add_drive ($g, "test.img") ||
+    ! guestfs_launch ($g) ||
+    ! guestfs_part_disk ($g, "/dev/sda", "mbr") ||
+    ! guestfs_pvcreate ($g, "/dev/sda") ||
+    ! guestfs_vgcreate ($g, "VG", array ("/dev/sda")) ||
+    ! guestfs_lvcreate ($g, "LV", "VG", 64) ||
+    ! guestfs_mkfs ($g, "ext2", "/dev/VG/LV")) {
+  die ("Error: ".guestfs_last_error ($g)."\n");
+}
+echo ("OK\n");
+?>
+--CLEAN--
+<?php
+$tmp = dirname(__FILE__)."/test.img";
+unlink ($tmp);
+?>
+--EXPECT--
+OK
diff --git a/php/guestfs_php.ini b/php/guestfs_php.ini
new file mode 100644 (file)
index 0000000..b490a44
--- /dev/null
@@ -0,0 +1,2 @@
+; Enable guestfs_php extension module
+extension=guestfs_php.so
diff --git a/php/run-php-tests.sh b/php/run-php-tests.sh
new file mode 100755 (executable)
index 0000000..38a5e38
--- /dev/null
@@ -0,0 +1,35 @@
+#!/bin/sh -
+# 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.
+
+set -e
+cd extension
+
+TESTS=$(echo guestfs_php_*.phpt)
+echo TESTS: $TESTS
+
+# The PHP test script cleans the environment, so LIBGUESTFS_DEBUG=1
+# won't get passed down to the script.  Furthermore, setting
+# LIBGUESTFS_DEBUG=1 isn't very useful anyway because the PHP test
+# script mixes stdout and stderr together and compares this to the
+# expected output, so you'd just get failures for every test.  So
+# there is no good way to debug libguestfs failures in PHP tests, but
+# if an individual test fails locally then you can edit the
+# guestfs_php_*.phpt and uncomment the putenv statement, then look at
+# the output.
+unset LIBGUESTFS_DEBUG
+
+make test TESTS="$TESTS"
index 3faa1fb..a06249a 100644 (file)
@@ -98,6 +98,7 @@ perl/Guestfs.c
 perl/bindtests.pl
 perl/lib/Sys/Guestfs.pm
 perl/lib/Sys/Guestfs/Lib.pm
+php/extension/guestfs_php.c
 python/guestfs-py.c
 regressions/rhbz501893.c
 regressions/test-lvm-mapping.pl
index 1d5707d..2a5ecfb 100755 (executable)
@@ -11978,6 +11978,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;
 
@@ -12517,6 +12942,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.
index 24d5aef..82fd7a1 100644 (file)
@@ -43,7 +43,7 @@ FUSE.
 
 Libguestfs is a library that can be linked with C and C++ management
 programs (or management programs written in OCaml, Perl, Python, Ruby,
-Java, Haskell or C#).  You can also use it from shell scripts or the
+Java, PHP, Haskell or C#).  You can also use it from shell scripts or the
 command line.
 
 You don't need to be root to use libguestfs, although obviously you do
@@ -626,6 +626,13 @@ For documentation see the file C<guestfs.mli>.
 
 For documentation see L<Sys::Guestfs(3)>.
 
+=item B<PHP>
+
+For documentation see C<README-PHP> supplied with libguestfs
+sources or in the php-libguestfs package for your distribution.
+
+The PHP binding only works correctly on 64 bit machines.
+
 =item B<Python>
 
 For documentation do: