Add Erlang bindings.
authorRichard W.M. Jones <rjones@redhat.com>
Tue, 20 Sep 2011 17:03:58 +0000 (18:03 +0100)
committerRichard W.M. Jones <rjones@redhat.com>
Wed, 21 Sep 2011 14:21:58 +0000 (15:21 +0100)
25 files changed:
.gitignore
Makefile.am
configure.ac
erlang/Makefile.am [new file with mode: 0644]
erlang/README [new file with mode: 0644]
erlang/erl-guestfs-proto.c [new file with mode: 0644]
erlang/examples/LICENSE [new file with mode: 0644]
erlang/examples/Makefile.am [new file with mode: 0644]
erlang/examples/create_disk.erl [new file with mode: 0755]
erlang/examples/guestfs-erlang.pod [new file with mode: 0644]
erlang/examples/inspect_vm.erl [new file with mode: 0755]
examples/guestfs-examples.pod
examples/guestfs-recipes.pod
generator/.depend
generator/Makefile.am
generator/generator_docstrings.ml
generator/generator_erlang.ml [new file with mode: 0644]
generator/generator_main.ml
java/examples/guestfs-java.pod
ocaml/examples/guestfs-ocaml.pod
perl/examples/guestfs-perl.pod
po/POTFILES.in
python/examples/guestfs-python.pod
ruby/examples/guestfs-ruby.pod
src/guestfs.pod

index a4500fc..55e10db 100644 (file)
@@ -73,6 +73,12 @@ edit/stamp-virt-*.pod
 edit/virt-edit
 edit/virt-edit.1
 emptydisk
 edit/virt-edit
 edit/virt-edit.1
 emptydisk
+erlang/erl-guestfs
+erlang/erl-guestfs.c
+erlang/examples/guestfs-erlang.3
+erlang/examples/stamp-guestfs-erlang.pod
+erlang/guestfs.beam
+erlang/guestfs.erl
 examples/create_disk
 examples/guestfs-examples.3
 examples/guestfs-recipes.1
 examples/create_disk
 examples/guestfs-examples.3
 examples/guestfs-recipes.1
@@ -118,6 +124,7 @@ haskell/Guestfs.hs
 *.hi
 html/guestfish.1.html
 html/guestfs.3.html
 *.hi
 html/guestfish.1.html
 html/guestfs.3.html
+html/guestfs-erlang.3.html
 html/guestfs-examples.3.html
 html/guestfs-java.3.html
 html/guestfs-ocaml.3.html
 html/guestfs-examples.3.html
 html/guestfs-java.3.html
 html/guestfs-ocaml.3.html
index ca31727..c7166b7 100644 (file)
@@ -60,6 +60,9 @@ endif
 if HAVE_PHP
 SUBDIRS += php
 endif
 if HAVE_PHP
 SUBDIRS += php
 endif
+if HAVE_ERLANG
+SUBDIRS += erlang erlang/examples
+endif
 
 # Unconditional because nothing is built yet.
 SUBDIRS += csharp
 
 # Unconditional because nothing is built yet.
 SUBDIRS += csharp
@@ -131,6 +134,7 @@ EXTRA_DIST = \
 HTMLFILES = \
        html/guestfs.3.html \
        html/guestfs-examples.3.html \
 HTMLFILES = \
        html/guestfs.3.html \
        html/guestfs-examples.3.html \
+       html/guestfs-erlang.3.html \
        html/guestfs-java.3.html \
        html/guestfs-ocaml.3.html \
        html/guestfs-perl.3.html \
        html/guestfs-java.3.html \
        html/guestfs-ocaml.3.html \
        html/guestfs-perl.3.html \
index de66651..e4df2c6 100644 (file)
@@ -886,6 +886,44 @@ AS_IF([test "x$enable_php" != "xno"],
         ])
 AM_CONDITIONAL([HAVE_PHP], [test "x$PHP" != "xno" && test "x$PHPIZE" != "xno"])
 
         ])
 AM_CONDITIONAL([HAVE_PHP], [test "x$PHP" != "xno" && test "x$PHPIZE" != "xno"])
 
+dnl Erlang
+ERLC=no
+ERL_INTERFACEDIR=no
+AC_ARG_ENABLE([erlang],
+        AS_HELP_STRING([--disable-erlang], [Disable Erlang language bindings]),
+        [],
+        [enable_erlang=yes])
+AS_IF([test "x$enable_erlang" != "xno"],
+        [
+        ERLC=
+        AC_CHECK_PROG([ERLC],[erlc],[erlc],[no])
+
+        if test "x$ERLC" != "xno"; then
+            dnl Look for erl_interface directory in various places.
+            AC_MSG_CHECKING([for erl_interface])
+
+            for d in \
+                $libdir /usr/lib /usr/lib64 /usr/local/lib /usr/local/lib64
+            do
+                dir=`ls -1d $d/erlang/lib/erl_interface-* 2>/dev/null`
+                if test "x$dir" != "x" && test -d "$dir"; then
+                    AC_MSG_RESULT([$dir])
+                    ERL_INTERFACEDIR=$dir
+                    break
+                fi
+            done
+
+            if test "x$ERL_INTERFACEDIR" = "xno"; then
+                AC_MSG_RESULT([not found])
+            fi
+        fi
+
+        AC_SUBST([ERLC])
+        AC_SUBST([ERL_INTERFACEDIR])
+        ])
+AM_CONDITIONAL([HAVE_ERLANG],
+        [test "x$ERLC" != "xno" && test "x$ERL_INTERFACEDIR" != "xno"])
+
 dnl Check for Perl modules needed by Perl virt tools (virt-df, etc.)
 AS_IF([test "x$PERL" != "xno"],
         [
 dnl Check for Perl modules needed by Perl virt tools (virt-df, etc.)
 AS_IF([test "x$PERL" != "xno"],
         [
@@ -933,6 +971,8 @@ AC_CONFIG_FILES([Makefile
                  debian/changelog
                  df/Makefile
                  edit/Makefile
                  debian/changelog
                  df/Makefile
                  edit/Makefile
+                 erlang/Makefile
+                 erlang/examples/Makefile
                  examples/Makefile
                  fish/Makefile
                  fuse/Makefile
                  examples/Makefile
                  fish/Makefile
                  fuse/Makefile
@@ -994,6 +1034,8 @@ 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
 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 "Erlang bindings ..................... "
+if test "x$HAVE_ERLANG_TRUE" = "x"; then echo "yes"; else echo "no"; fi
 echo    "guestfish and C virt tools .......... yes"
 echo -n "Perl virt tools ..................... "
 if test "x$HAVE_TOOLS_TRUE" = "x"; then echo "yes"; else echo "no"; fi
 echo    "guestfish and C virt tools .......... yes"
 echo -n "Perl virt tools ..................... "
 if test "x$HAVE_TOOLS_TRUE" = "x"; then echo "yes"; else echo "no"; fi
diff --git a/erlang/Makefile.am b/erlang/Makefile.am
new file mode 100644 (file)
index 0000000..933e602
--- /dev/null
@@ -0,0 +1,54 @@
+# libguestfs Erlang bindings
+# Copyright (C) 2011 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 = \
+       guestfs.erl \
+       erl-guestfs.c
+
+EXTRA_DIST = \
+       $(generator_built) \
+       README
+
+if HAVE_ERLANG
+
+erlang_bindir = $(libdir)/erlang/lib/$(PACKAGE_NAME)-$(PACKAGE_VERSION)/ebin
+
+erlang_bin_DATA = guestfs.beam
+
+guestfs.beam: guestfs.erl
+       $(ERLC) +debug_info guestfs.erl
+
+bin_PROGRAMS = erl-guestfs
+
+erl_guestfs_SOURCES = erl-guestfs.c erl-guestfs-proto.c
+
+erl_guestfs_CFLAGS = \
+       -I$(top_srcdir)/src -I$(top_builddir)/src \
+       -I$(srcdir)/../gnulib/lib -I../gnulib/lib \
+       -I$(ERL_INTERFACEDIR)/include \
+       $(WARN_CFLAGS) $(WERROR_CFLAGS)
+
+erl_guestfs_LDADD = \
+       $(ERL_INTERFACEDIR)/lib/liberl_interface.a \
+       $(ERL_INTERFACEDIR)/lib/libei.a \
+       -lpthread \
+       $(top_builddir)/src/libguestfs.la \
+       ../gnulib/lib/libgnu.la
+
+endif
diff --git a/erlang/README b/erlang/README
new file mode 100644 (file)
index 0000000..4d710a9
--- /dev/null
@@ -0,0 +1,53 @@
+REAMDE for the Erlang bindings to libguestfs
+----------------------------------------------------------------------
+
+To get started, take a look at the man page guestfs-erlang(3) and the
+example programs.
+
+Note that to run the examples, the "erl-guestfs" binary must be on the
+path.  To run the examples without installing, do:
+
+ cd erlang
+ PATH=.:$PATH ../run ./examples/create_disk.erl
+ PATH=.:$PATH ../run ./examples/inspect_vm.erl /path/to/vm_disk.img
+
+To simplify the implementation we currently don't support events or
+user cancellation.  However it would be pretty simple to add both of
+these.  Patches welcome!
+
+Implementation notes
+----------------------------------------------------------------------
+
+These bindings are done using a port that launches an external
+program, following this example:
+http://www.erlang.org/doc/tutorial/erl_interface.html
+
+The reason for this is that the libguestfs API is synchronous and
+calls may take a long time.  If we used a linked-in driver then that
+would require us to start a POSIX thread in the Erlang interpreter and
+manage concurrency issues.  Using an external process per handle
+simplifies the implementation and makes it much less likely to break
+the Erlang interpreter.
+
+The external C program is called "erl-guestfs".  It is normally
+installed in $(bindir), eg. /usr/bin/erl-guestfs.
+
+You need to make sure that the Erlang code and erl-guestfs are the
+same version.  The protocol used between the Erlang code (guestfs.erl)
+and erl-guestfs might change in future versions.
+
+There is not really any type checking done in the erl-guestfs binary,
+which means you can get undefined behaviour if you send incorrect
+argument types.  Patches welcome to improve this situation.
+
+Licensing
+----------------------------------------------------------------------
+
+Because the C program runs in a separate process, it is licensed as
+GPLv2+.  The Erlang part which "links" into the Erlang interpreter is
+licensed as LGPLv2+.  We believe this means there is no impediment to
+using libguestfs from closed source Erlang programs.
+
+The example programs are under a separate, very permissive license,
+which basically allows you to do what you want with them.  See
+erlang/examples/LICENSE.
diff --git a/erlang/erl-guestfs-proto.c b/erlang/erl-guestfs-proto.c
new file mode 100644 (file)
index 0000000..d1eb48b
--- /dev/null
@@ -0,0 +1,278 @@
+/* libguestfs Erlang bindings.
+ * Copyright (C) 2011 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.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+
+#include <erl_interface.h>
+#include <ei.h>
+
+#include "error.h"
+#include "full-read.h"
+#include "full-write.h"
+
+#include "guestfs.h"
+
+guestfs_h *g;
+
+extern ETERM *dispatch (ETERM *message);
+extern int atom_equals (ETERM *atom, const char *name);
+extern ETERM *make_error (const char *funname);
+extern ETERM *unknown_optarg (const char *funname, ETERM *optargname);
+extern ETERM *unknown_function (ETERM *fun);
+extern ETERM *make_string_list (char **r);
+extern ETERM *make_table (char **r);
+extern ETERM *make_bool (int r);
+extern char **get_string_list (ETERM *term);
+extern int get_bool (ETERM *term);
+extern void free_strings (char **r);
+
+/* This stops things getting out of hand, but also lets us detect
+ * protocol problems quickly.
+ */
+#define MAX_MESSAGE_SIZE (32*1024*1024)
+
+static unsigned char *read_message (void);
+static void write_reply (ETERM *);
+
+int
+main (void)
+{
+  unsigned char *buf;
+  ETERM *ret, *message;
+
+  erl_init (NULL, 0);
+
+  /* This process has a single libguestfs handle.  If the Erlang
+   * system creates more than one handle, then more than one of these
+   * processes will be running.
+   */
+  g = guestfs_create ();
+  if (g == NULL)
+    error (EXIT_FAILURE, 0, "could not create guestfs handle");
+
+  guestfs_set_error_handler (g, NULL, NULL);
+
+  while ((buf = read_message ()) != NULL) {
+    message = erl_decode (buf);
+    free (buf);
+
+    ret = dispatch (message);
+    erl_free_term (message);
+
+    write_reply (ret);
+    erl_free_term (ret);
+  }
+
+  guestfs_close (g);
+
+  exit (EXIT_SUCCESS);
+}
+
+/* The Erlang port always sends the length of the buffer as 4
+ * bytes in network byte order, followed by the message buffer.
+ */
+static unsigned char *
+read_message (void)
+{
+  unsigned char buf[4];
+  size_t size;
+  unsigned char *r;
+
+  errno = 0;
+  if (full_read (0, buf, 4) != 4) {
+    if (errno == 0) /* ok - closed connection normally */
+      return NULL;
+    else
+      error (EXIT_FAILURE, errno, "read message size");
+  }
+
+  size = buf[0] << 24 | buf[1] << 16 | buf[2] << 8 | buf[3];
+
+  if (size > MAX_MESSAGE_SIZE)
+    error (EXIT_FAILURE, 0, "message larger than MAX_MESSAGE_SIZE");
+
+  r = malloc (size);
+  if (r == NULL)
+    error (EXIT_FAILURE, errno, "malloc");
+
+  if (full_read (0, r, size) != size)
+    error (EXIT_FAILURE, errno, "read message content");
+
+  return r;
+}
+
+static void
+write_reply (ETERM *term)
+{
+  size_t size;
+  unsigned char sbuf[4];
+  unsigned char *buf;
+
+  size = erl_term_len (term);
+
+  buf = malloc (size);
+  if (buf == NULL)
+    error (EXIT_FAILURE, errno, "malloc");
+
+  erl_encode (term, buf);
+
+  sbuf[0] = (size >> 24) & 0xff;
+  sbuf[1] = (size >> 16) & 0xff;
+  sbuf[2] = (size >> 8) & 0xff;
+  sbuf[3] = size & 0xff;
+
+  if (full_write (1, sbuf, 4) != 4)
+    error (EXIT_FAILURE, errno, "write message size");
+
+  if (full_write (1, buf, size) != size)
+    error (EXIT_FAILURE, errno, "write message content");
+
+  free (buf);
+}
+
+/* Note that all published Erlang code/examples etc uses strncmp in
+ * a buggy way.  This is the right way to do it.
+ */
+int
+atom_equals (ETERM *atom, const char *name)
+{
+  size_t namelen = strlen (name);
+  size_t atomlen = ERL_ATOM_SIZE (atom);
+  if (namelen != atomlen) return 0;
+  return strncmp (ERL_ATOM_PTR (atom), name, atomlen) == 0;
+}
+
+ETERM *
+make_error (const char *funname)
+{
+  ETERM *error = erl_mk_atom ("error");
+  ETERM *msg = erl_mk_string (guestfs_last_error (g));
+  ETERM *num = erl_mk_int (guestfs_last_errno (g));
+  ETERM *t[3] = { error, msg, num };
+  return erl_mk_tuple (t, 3);
+}
+
+ETERM *
+unknown_function (ETERM *fun)
+{
+  ETERM *unknown = erl_mk_atom ("unknown");
+  ETERM *funcopy = erl_copy_term (fun);
+  ETERM *t[2] = { unknown, funcopy };
+  return erl_mk_tuple (t, 2);
+}
+
+ETERM *
+unknown_optarg (const char *funname, ETERM *optargname)
+{
+  ETERM *unknownarg = erl_mk_atom ("unknownarg");
+  ETERM *copy = erl_copy_term (optargname);
+  ETERM *t[2] = { unknownarg, copy };
+  return erl_mk_tuple (t, 2);
+}
+
+ETERM *
+make_string_list (char **r)
+{
+  size_t i, size;
+
+  for (size = 0; r[size] != NULL; ++size)
+    ;
+
+  ETERM *t[size];
+
+  for (i = 0; r[i] != NULL; ++i)
+    t[i] = erl_mk_string (r[i]);
+
+  return erl_mk_list (t, size);
+}
+
+/* Make a hash table.  The number of elements returned by the C
+ * function is always even.
+ */
+ETERM *
+make_table (char **r)
+{
+  size_t i, size;
+
+  for (size = 0; r[size] != NULL; ++size)
+    ;
+
+  ETERM *t[size/2];
+  ETERM *a[2];
+
+  for (i = 0; r[i] != NULL; i += 2) {
+    a[0] = erl_mk_string (r[i]);
+    a[1] = erl_mk_string (r[i+1]);
+    t[i/2] = erl_mk_tuple (a, 2);
+  }
+
+  return erl_mk_list (t, size/2);
+}
+
+ETERM *
+make_bool (int r)
+{
+  if (r)
+    return erl_mk_atom ("true");
+  else
+    return erl_mk_atom ("false");
+}
+
+char **
+get_string_list (ETERM *term)
+{
+  ETERM *t;
+  size_t i, size;
+  char **r;
+
+  for (size = 0, t = term; !ERL_IS_EMPTY_LIST (t);
+       size++, t = ERL_CONS_TAIL (t))
+    ;
+
+  r = malloc ((size+1) * sizeof (char *));
+  if (r == NULL)
+    error (EXIT_FAILURE, errno, "malloc");
+
+  for (i = 0, t = term; !ERL_IS_EMPTY_LIST (t); i++, t = ERL_CONS_TAIL (t))
+    r[i] = erl_iolist_to_string (ERL_CONS_HEAD (t));
+  r[size] = NULL;
+
+  return r;
+}
+
+int
+get_bool (ETERM *term)
+{
+  if (atom_equals (term, "true"))
+    return 1;
+  else
+    return 0;
+}
+
+void
+free_strings (char **r)
+{
+  size_t i;
+
+  for (i = 0; r[i] != NULL; ++i)
+    free (r[i]);
+  free (r);
+}
diff --git a/erlang/examples/LICENSE b/erlang/examples/LICENSE
new file mode 100644 (file)
index 0000000..555f04d
--- /dev/null
@@ -0,0 +1,2 @@
+All the examples in the erlang/examples/ subdirectory may be freely
+copied without any restrictions.
diff --git a/erlang/examples/Makefile.am b/erlang/examples/Makefile.am
new file mode 100644 (file)
index 0000000..9fa82bb
--- /dev/null
@@ -0,0 +1,39 @@
+# libguestfs Erlang examples
+# Copyright (C) 2011 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.
+
+EXTRA_DIST = \
+       LICENSE \
+       create_disk.erl \
+       inspect_vm.erl \
+       guestfs-erlang.pod
+
+CLEANFILES = stamp-guestfs-erlang.pod
+
+man_MANS = guestfs-erlang.3
+noinst_DATA = $(top_builddir)/html/guestfs-erlang.3.html
+
+guestfs-erlang.3 $(top_builddir)/html/guestfs-erlang.3.html: stamp-guestfs-erlang.pod
+
+stamp-guestfs-erlang.pod: guestfs-erlang.pod create_disk.erl inspect_vm.erl
+       $(top_builddir)/podwrapper.sh \
+         --section 3 \
+         --man guestfs-erlang.3 \
+         --html $(top_builddir)/html/guestfs-erlang.3.html \
+         --verbatim $(srcdir)/create_disk.erl:@EXAMPLE1@ \
+         --verbatim $(srcdir)/inspect_vm.erl:@EXAMPLE2@ \
+         $<
+       touch $@
diff --git a/erlang/examples/create_disk.erl b/erlang/examples/create_disk.erl
new file mode 100755 (executable)
index 0000000..231c398
--- /dev/null
@@ -0,0 +1,65 @@
+#!/usr/bin/env escript
+%%! -smp enable -sname create_disk debug verbose
+% Example showing how to create a disk image.
+
+main(_) ->
+    Output = "disk.img",
+
+    {ok, G} = guestfs:create(),
+
+    % Create a raw-format sparse disk image, 512 MB in size.
+    {ok, File} = file:open(Output, [raw, write, binary]),
+    {ok, _} = file:position(File, 512 * 1024 * 1024 - 1),
+    ok = file:write(File, " "),
+    ok = file:close(File),
+
+    % Set the trace flag so that we can see each libguestfs call.
+    ok = guestfs:set_trace(G, true),
+
+    % Set the autosync flag so that the disk will be synchronized
+    % automatically when the libguestfs handle is closed.
+    ok = guestfs:set_autosync(G, true),
+
+    % Attach the disk image to libguestfs.
+    ok = guestfs:add_drive_opts(G, Output,
+                                [{format, "raw"}, {readonly, false}]),
+
+    % Run the libguestfs back-end.
+    ok = guestfs:launch(G),
+
+    % Get the list of devices.  Because we only added one drive
+    % above, we expect that this list should contain a single
+    % element.
+    [Device] = guestfs:list_devices(G),
+
+    % Partition the disk as one single MBR partition.
+    ok = guestfs:part_disk(G, Device, "mbr"),
+
+    % Get the list of partitions.  We expect a single element, which
+    % is the partition we have just created.
+    [Partition] = guestfs:list_partitions(G),
+
+    % Create a filesystem on the partition.
+    ok = guestfs:mkfs(G, "ext4", Partition),
+
+    % Now mount the filesystem so that we can add files. *)
+    ok = guestfs:mount_options(G, "", Partition, "/"),
+
+    % Create some files and directories. *)
+    ok = guestfs:touch(G, "/empty"),
+    Message = "Hello, world\n",
+    ok = guestfs:write(G, "/hello", Message),
+    ok = guestfs:mkdir(G, "/foo"),
+
+    % This one uploads the local file /etc/resolv.conf into
+    % the disk image.
+    ok = guestfs:upload(G, "/etc/resolv.conf", "/foo/resolv.conf"),
+
+    % Because 'autosync' was set (above) we can just close the handle
+    % and the disk contents will be synchronized.  You can also do
+    % this manually by calling guestfs:umount_all and guestfs:sync.
+    %
+    % Note also that handles are automatically closed if they are
+    % reaped by the garbage collector.  You only need to call close
+    % if you want to close the handle right away.
+    ok = guestfs:close(G).
diff --git a/erlang/examples/guestfs-erlang.pod b/erlang/examples/guestfs-erlang.pod
new file mode 100644 (file)
index 0000000..8721318
--- /dev/null
@@ -0,0 +1,133 @@
+=encoding utf8
+
+=head1 NAME
+
+guestfs-erlang - How to use libguestfs from Erlang
+
+=head1 SYNOPSIS
+
+ {ok, G} = guestfs:create(),
+ ok = guestfs:add_drive_opts(G, Disk,
+                             [{format, "raw"}, {readonly, true}]),
+ ok = guestfs:launch(G),
+ [Device] = guestfs:list_devices(G),
+ ok = guestfs:close(G).
+
+=head1 DESCRIPTION
+
+This manual page documents how to call libguestfs from the Erlang
+programming language.  This page just documents the differences from
+the C API and gives some examples.  If you are not familiar with using
+libguestfs, you also need to read L<guestfs(3)>.
+
+=head2 OPENING AND CLOSING THE HANDLE
+
+The Erlang bindings are implemented using an external program called
+C<erl-guestfs>.  This program must be on the current PATH, or else you
+should specify the full path to the program:
+
+ {ok, G} = guestfs:create().
+
+ {ok, G} = guestfs:create("/path/to/erl-guestfs").
+
+C<G> is the libguestfs handle which you should pass to other
+functions.
+
+To close the handle:
+
+ ok = guestfs:close(G).
+
+=head2 FUNCTIONS WITH OPTIONAL ARGUMENTS
+
+For functions that take optional arguments, the first arguments are
+the non-optional ones.  The last argument is a list of tuples
+supplying the remaining optional arguments.
+
+ ok = guestfs:add_drive_opts(G, Disk,
+                             [{format, "raw"}, {readonly, true}]).
+
+If the last argument would be an empty list, you can also omit it:
+
+ ok = guestfs:add_drive_opts(G, Disk).
+
+=head2 RETURN VALUES AND ERRORS
+
+On success, most functions return a C<Result> term (which could be a
+list, string, tuple etc.).  If there is nothing for the function to
+return, then the atom C<ok> is returned.
+
+On error, you would see one of the following tuples:
+
+=over 4
+
+=item C<{error, Msg, Errno}>
+
+This indicates an ordinary error from the function.
+
+C<Msg> is the error message (string) and C<Errno> is the Unix error
+(integer).
+
+C<Errno> can be zero.  See L<guestfs(3)/guestfs_last_errno>.
+
+=item C<{unknown, Function}>
+
+This indicates that the function you called is not known.  Generally
+this means you are mixing C<erl-guestfs> from another version of
+libguestfs, which you should not do.
+
+C<Function> is the name of the unknown function.
+
+=item C<{unknownarg, Arg}>
+
+This indicates that you called a function with optional arguments,
+with an unknown argument name.
+
+C<Arg> is the name of the unknown argument.
+
+=back
+
+=head1 EXAMPLE 1: CREATE A DISK IMAGE
+
+@EXAMPLE1@
+
+=head1 EXAMPLE 2: INSPECT A VIRTUAL MACHINE DISK IMAGE
+
+@EXAMPLE2@
+
+=head1 SEE ALSO
+
+L<guestfs(3)>,
+L<guestfs-examples(3)>,
+L<guestfs-java(3)>,
+L<guestfs-ocaml(3)>,
+L<guestfs-perl(3)>,
+L<guestfs-python(3)>,
+L<guestfs-recipes(1)>,
+L<guestfs-ruby(3)>,
+L<http://www.erlang.org/>.
+L<http://libguestfs.org/>.
+
+=head1 AUTHORS
+
+Richard W.M. Jones (C<rjones at redhat dot com>)
+
+=head1 COPYRIGHT
+
+Copyright (C) 2011 Red Hat Inc. L<http://libguestfs.org/>
+
+The examples in this manual page may be freely copied, modified and
+distributed without any restrictions.
+
+This library is free software; you can redistribute it and/or
+modify it under the terms of the GNU Lesser General Public
+License as published by the Free Software Foundation; either
+version 2 of the License, or (at your option) any later version.
+
+This library 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
+Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public
+License along with this library; if not, write to the Free Software
+Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
diff --git a/erlang/examples/inspect_vm.erl b/erlang/examples/inspect_vm.erl
new file mode 100755 (executable)
index 0000000..87d751c
--- /dev/null
@@ -0,0 +1,79 @@
+#!/usr/bin/env escript
+%%! -smp enable -sname inspect_vm debug verbose
+% Example showing how to inspect a virtual machine disk.
+
+main([Disk]) ->
+    {ok, G} = guestfs:create(),
+
+    % Attach the disk image read-only to libguestfs.
+    ok = guestfs:add_drive_opts(G, Disk, [{readonly, true}]),
+
+    % Run the libguestfs back-end.
+    ok = guestfs:launch(G),
+
+    % Ask libguestfs to inspect for operating systems.
+    case guestfs:inspect_os(G) of
+        [] ->
+            io:fwrite("inspect_vm: no operating systems found~n"),
+            exit(no_operating_system);
+        Roots ->
+            list_os(G, Roots)
+    end.
+
+list_os(_, []) ->
+    ok;
+list_os(G, [Root|Roots]) ->
+    io:fwrite("Root device: ~s~n", [Root]),
+
+    % Print basic information about the operating system.
+    Product_name = guestfs:inspect_get_product_name(G, Root),
+    io:fwrite("  Product name: ~s~n", [Product_name]),
+    Major = guestfs:inspect_get_major_version(G, Root),
+    Minor = guestfs:inspect_get_minor_version(G, Root),
+    io:fwrite("  Version:      ~w.~w~n", [Major, Minor]),
+    Type = guestfs:inspect_get_type(G, Root),
+    io:fwrite("  Type:         ~s~n", [Type]),
+    Distro = guestfs:inspect_get_distro(G, Root),
+    io:fwrite("  Distro:       ~s~n", [Distro]),
+
+    % Mount up the disks, like guestfish -i.
+    Mps = sort_mps(guestfs:inspect_get_mountpoints(G, Root)),
+    mount_mps(G, Mps),
+
+    % If /etc/issue.net file exists, print up to 3 lines. *)
+    Filename = "/etc/issue.net",
+    Is_file = guestfs:is_file(G, Filename),
+    if Is_file ->
+            io:fwrite("--- ~s ---~n", [Filename]),
+            Lines = guestfs:head_n(G, 3, Filename),
+            write_lines(Lines);
+       true -> ok
+    end,
+
+    % Unmount everything.
+    ok = guestfs:umount_all(G),
+
+    list_os(G, Roots).
+
+% Sort keys by length, shortest first, so that we end up
+% mounting the filesystems in the correct order.
+sort_mps(Mps) ->
+    Cmp = fun ({A,_}, {B,_}) ->
+                  length(A) =< length(B) end,
+    lists:sort(Cmp, Mps).
+
+mount_mps(_, []) ->
+    ok;
+mount_mps(G, [{Mp, Dev}|Mps]) ->
+    case guestfs:mount_ro(G, Dev, Mp) of
+        ok -> ok;
+        { error, Msg, _ } ->
+            io:fwrite("~s (ignored)~n", [Msg])
+    end,
+    mount_mps(G, Mps).
+
+write_lines([]) ->
+    ok;
+write_lines([Line|Lines]) ->
+    io:fwrite("~s~n", [Line]),
+    write_lines(Lines).
index 39259e1..588f917 100644 (file)
@@ -33,6 +33,7 @@ libguestfs, you also need to read L<guestfs(3)>.
 =head1 SEE ALSO
 
 L<guestfs(3)>,
 =head1 SEE ALSO
 
 L<guestfs(3)>,
+L<guestfs-erlang(3)>,
 L<guestfs-java(3)>,
 L<guestfs-ocaml(3)>,
 L<guestfs-perl(3)>,
 L<guestfs-java(3)>,
 L<guestfs-ocaml(3)>,
 L<guestfs-perl(3)>,
index 97a8053..e4e4875 100644 (file)
@@ -386,6 +386,7 @@ https://rwmj.wordpress.com/2011/05/10/tip-use-libguestfs-on-vmware-esx-guests/#c
 L<guestfs(3)>,
 L<guestfish(1)>,
 L<guestfs-examples(3)>,
 L<guestfs(3)>,
 L<guestfish(1)>,
 L<guestfs-examples(3)>,
+L<guestfs-erlang(3)>,
 L<guestfs-java(3)>,
 L<guestfs-ocaml(3)>,
 L<guestfs-perl(3)>,
 L<guestfs-java(3)>,
 L<guestfs-ocaml(3)>,
 L<guestfs-perl(3)>,
index b2963f2..85f228d 100644 (file)
@@ -124,6 +124,14 @@ generator_php.cmo: generator_utils.cmi generator_types.cmo \
 generator_php.cmx: generator_utils.cmx generator_types.cmx \
     generator_structs.cmx generator_pr.cmx generator_optgroups.cmx \
     generator_docstrings.cmx generator_c.cmx generator_actions.cmx
 generator_php.cmx: generator_utils.cmx generator_types.cmx \
     generator_structs.cmx generator_pr.cmx generator_optgroups.cmx \
     generator_docstrings.cmx generator_c.cmx generator_actions.cmx
+generator_erlang.cmo: generator_utils.cmi generator_types.cmo \
+    generator_structs.cmi generator_pr.cmi generator_optgroups.cmo \
+    generator_events.cmo generator_docstrings.cmo generator_c.cmo \
+    generator_actions.cmi
+generator_erlang.cmx: generator_utils.cmx generator_types.cmx \
+    generator_structs.cmx generator_pr.cmx generator_optgroups.cmx \
+    generator_events.cmx generator_docstrings.cmx generator_c.cmx \
+    generator_actions.cmx
 generator_bindtests.cmo: generator_utils.cmi generator_types.cmo \
     generator_structs.cmi generator_pr.cmi generator_optgroups.cmo \
     generator_docstrings.cmo generator_c.cmo generator_actions.cmi
 generator_bindtests.cmo: generator_utils.cmi generator_types.cmo \
     generator_structs.cmi generator_pr.cmi generator_optgroups.cmo \
     generator_docstrings.cmo generator_c.cmo generator_actions.cmi
@@ -138,13 +146,13 @@ generator_main.cmo: generator_xdr.cmo generator_structs.cmi \
     generator_ruby.cmo generator_python.cmo generator_pr.cmi \
     generator_php.cmo generator_perl.cmo generator_ocaml.cmo \
     generator_java.cmo generator_haskell.cmo generator_fish.cmo \
     generator_ruby.cmo generator_python.cmo generator_pr.cmi \
     generator_php.cmo generator_perl.cmo generator_ocaml.cmo \
     generator_java.cmo generator_haskell.cmo generator_fish.cmo \
-    generator_errnostring.cmo generator_daemon.cmo generator_csharp.cmo \
-    generator_capitests.cmo generator_c.cmo generator_bindtests.cmo \
-    generator_api_versions.cmi
+    generator_errnostring.cmo generator_erlang.cmo generator_daemon.cmo \
+    generator_csharp.cmo generator_capitests.cmo generator_c.cmo \
+    generator_bindtests.cmo generator_api_versions.cmi
 generator_main.cmx: generator_xdr.cmx generator_structs.cmx \
     generator_ruby.cmx generator_python.cmx generator_pr.cmx \
     generator_php.cmx generator_perl.cmx generator_ocaml.cmx \
     generator_java.cmx generator_haskell.cmx generator_fish.cmx \
 generator_main.cmx: generator_xdr.cmx generator_structs.cmx \
     generator_ruby.cmx generator_python.cmx generator_pr.cmx \
     generator_php.cmx generator_perl.cmx generator_ocaml.cmx \
     generator_java.cmx generator_haskell.cmx generator_fish.cmx \
-    generator_errnostring.cmx generator_daemon.cmx generator_csharp.cmx \
-    generator_capitests.cmx generator_c.cmx generator_bindtests.cmx \
-    generator_api_versions.cmx
+    generator_errnostring.cmx generator_erlang.cmx generator_daemon.cmx \
+    generator_csharp.cmx generator_capitests.cmx generator_c.cmx \
+    generator_bindtests.cmx generator_api_versions.cmx
index a127a87..51a9462 100644 (file)
@@ -1,5 +1,5 @@
 # libguestfs
 # libguestfs
-# Copyright (C) 2010 Red Hat Inc.
+# Copyright (C) 2010-2011 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
 #
 # 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
@@ -46,6 +46,7 @@ SOURCES = \
        generator_haskell.ml \
        generator_csharp.ml \
        generator_php.ml \
        generator_haskell.ml \
        generator_csharp.ml \
        generator_php.ml \
+       generator_erlang.ml \
        generator_bindtests.ml \
        generator_errnostring.ml \
        generator_main.ml
        generator_bindtests.ml \
        generator_errnostring.ml \
        generator_main.ml
index baccdd6..406bd55 100644 (file)
@@ -62,6 +62,7 @@ let copyright_years =
 (* Generate a header block in a number of standard styles. *)
 type comment_style =
     CStyle | CPlusPlusStyle | HashStyle | OCamlStyle | HaskellStyle
 (* Generate a header block in a number of standard styles. *)
 type comment_style =
     CStyle | CPlusPlusStyle | HashStyle | OCamlStyle | HaskellStyle
+  | ErlangStyle
 type license = GPLv2plus | LGPLv2plus
 
 let generate_header ?(extra_inputs = []) comment license =
 type license = GPLv2plus | LGPLv2plus
 
 let generate_header ?(extra_inputs = []) comment license =
@@ -71,7 +72,8 @@ let generate_header ?(extra_inputs = []) comment license =
     | CPlusPlusStyle -> pr "// "; "//"
     | HashStyle ->      pr "# ";  "#"
     | OCamlStyle ->     pr "(* "; " *"
     | CPlusPlusStyle -> pr "// "; "//"
     | HashStyle ->      pr "# ";  "#"
     | OCamlStyle ->     pr "(* "; " *"
-    | HaskellStyle ->   pr "{- "; "  " in
+    | HaskellStyle ->   pr "{- "; "  "
+    | ErlangStyle ->    pr "%% "; "% " in
   pr "libguestfs generated file\n";
   pr "%s WARNING: THIS FILE IS GENERATED FROM:\n" c;
   List.iter (pr "%s   %s\n" c) inputs;
   pr "libguestfs generated file\n";
   pr "%s WARNING: THIS FILE IS GENERATED FROM:\n" c;
   List.iter (pr "%s   %s\n" c) inputs;
@@ -113,6 +115,7 @@ let generate_header ?(extra_inputs = []) comment license =
   (match comment with
    | CStyle -> pr " */\n"
    | CPlusPlusStyle
   (match comment with
    | CStyle -> pr " */\n"
    | CPlusPlusStyle
+   | ErlangStyle
    | HashStyle -> ()
    | OCamlStyle -> pr " *)\n"
    | HaskellStyle -> pr "-}\n"
    | HashStyle -> ()
    | OCamlStyle -> pr " *)\n"
    | HaskellStyle -> pr "-}\n"
diff --git a/generator/generator_erlang.ml b/generator/generator_erlang.ml
new file mode 100644 (file)
index 0000000..d166ef2
--- /dev/null
@@ -0,0 +1,438 @@
+(* libguestfs
+ * Copyright (C) 2011 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ *)
+
+(* Please read generator/README first. *)
+
+open Printf
+
+open Generator_types
+open Generator_utils
+open Generator_pr
+open Generator_docstrings
+open Generator_optgroups
+open Generator_actions
+open Generator_structs
+open Generator_c
+open Generator_events
+
+let rec generate_erlang_erl () =
+  generate_header ErlangStyle LGPLv2plus;
+
+  pr "-module(guestfs).\n";
+  pr "\n";
+  pr "-export([create/0, create/1, close/1, init/1]).\n";
+  pr "\n";
+
+  (* Export the public actions. *)
+  List.iter (
+    fun (name, (_, args, optargs), _, _, _, _, _) ->
+      let nr_args = List.length args in
+      if optargs = [] then
+        pr "-export([%s/%d]).\n" name (nr_args+1)
+      else
+        pr "-export([%s/%d, %s/%d]).\n" name (nr_args+1) name (nr_args+2)
+  ) all_functions_sorted;
+
+  pr "\n";
+
+  pr "\
+create() ->
+  create(\"erl-guestfs\").
+
+create(ExtProg) ->
+  G = spawn(?MODULE, init, [ExtProg]),
+  {ok, G}.
+
+close(G) ->
+  G ! close,
+  ok.
+
+call_port(G, Args) ->
+  G ! {call, self(), Args},
+  receive
+    {guestfs, Result} ->
+      Result
+  end.
+
+init(ExtProg) ->
+  process_flag(trap_exit, true),
+  Port = open_port({spawn, ExtProg}, [{packet, 4}, binary]),
+  loop(Port).
+loop(Port) ->
+  receive
+    {call, Caller, Args} ->
+      Port ! { self(), {command, term_to_binary(Args)}},
+      receive
+        {Port, {data, Result}} ->
+          Caller ! { guestfs, binary_to_term(Result)}
+      end,
+      loop(Port);
+    close ->
+      port_close(Port),
+      exit(normal);
+    { 'EXIT', Port, _ } ->
+      exit(port_terminated)
+  end.
+
+";
+
+  (* These bindings just marshal the parameters and call the back-end
+   * process which dispatches them to the port.
+   *)
+  List.iter (
+    fun (name, (_, args, optargs), _, _, _, _, _) ->
+      pr "%s(G" name;
+      List.iter (
+        fun arg ->
+          pr ", %s" (String.capitalize (name_of_argt arg))
+      ) args;
+      if optargs <> [] then
+        pr ", Optargs";
+      pr ") ->\n";
+
+      pr "  call_port(G, {%s" name;
+      List.iter (
+        fun arg ->
+          pr ", %s" (String.capitalize (name_of_argt arg))
+      ) args;
+      if optargs <> [] then
+        pr ", Optargs";
+      pr "}).\n";
+
+      (* For functions with optional arguments, make a variant that
+       * has no optarg array, which just calls the function above with
+       * an empty list as the final arg.
+       *)
+      if optargs <> [] then (
+        pr "%s(G" name;
+        List.iter (
+          fun arg ->
+            pr ", %s" (String.capitalize (name_of_argt arg))
+        ) args;
+        pr ") ->\n";
+
+        pr "  %s(G" name;
+        List.iter (
+          fun arg ->
+            pr ", %s" (String.capitalize (name_of_argt arg))
+        ) args;
+        pr ", []";
+        pr ").\n"
+      );
+
+      pr "\n"
+  ) all_functions_sorted
+
+and generate_erlang_c () =
+  generate_header CStyle GPLv2plus;
+
+  pr "\
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+
+#include <erl_interface.h>
+#include <ei.h>
+
+#include \"guestfs.h\"
+
+extern guestfs_h *g;
+
+extern ETERM *dispatch (ETERM *message);
+extern int atom_equals (ETERM *atom, const char *name);
+extern ETERM *make_error (const char *funname);
+extern ETERM *unknown_optarg (const char *funname, ETERM *optargname);
+extern ETERM *unknown_function (ETERM *fun);
+extern ETERM *make_string_list (char **r);
+extern ETERM *make_table (char **r);
+extern ETERM *make_bool (int r);
+extern char **get_string_list (ETERM *term);
+extern int get_bool (ETERM *term);
+extern void free_strings (char **r);
+
+#define ARG(i) (ERL_TUPLE_ELEMENT(message,(i)+1))
+
+";
+
+  (* Struct copy functions. *)
+  let emit_copy_list_function typ =
+    pr "static ETERM *\n";
+    pr "make_%s_list (const struct guestfs_%s_list *%ss)\n" typ typ typ;
+    pr "{\n";
+    pr "  ETERM *t[%ss->len];\n" typ;
+    pr "  size_t i;\n";
+    pr "\n";
+    pr "  for (i = 0; i < %ss->len; ++i)\n" typ;
+    pr "    t[i] = make_%s (&%ss->val[i]);\n" typ typ;
+    pr "\n";
+    pr "  return erl_mk_list (t, %ss->len);\n" typ;
+    pr "}\n";
+    pr "\n";
+  in
+
+  List.iter (
+    fun (typ, cols) ->
+      pr "static ETERM *\n";
+      pr "make_%s (const struct guestfs_%s *%s)\n" typ typ typ;
+      pr "{\n";
+      pr "  ETERM *t[%d];\n" (List.length cols);
+      pr "\n";
+      iteri (
+        fun i col ->
+          (match col with
+           | name, FString ->
+               pr "  t[%d] = erl_mk_string (%s->%s);\n" i typ name
+           | name, FBuffer ->
+               pr "  t[%d] = erl_mk_estring (%s->%s, %s->%s_len);\n"
+                 i typ name typ name
+           | name, FUUID ->
+               pr "  t[%d] = erl_mk_estring (%s->%s, 32);\n" i typ name
+           | name, (FBytes|FInt64|FUInt64) ->
+               pr "  t[%d] = erl_mk_longlong (%s->%s);\n" i typ name
+           | name, (FInt32|FUInt32) ->
+               pr "  t[%d] = erl_mk_int (%s->%s);\n" i typ name
+           | name, FOptPercent ->
+               pr "  if (%s->%s >= 0)\n" typ name;
+               pr "    t[%d] = erl_mk_float (%s->%s);\n" i typ name;
+               pr "  else\n";
+               pr "    t[%d] = erl_mk_atom (\"undefined\");\n" i;
+           | name, FChar ->
+               pr "  t[%d] = erl_mk_int (%s->%s);\n" i typ name
+          );
+      ) cols;
+      pr "\n";
+      pr "  return erl_mk_list (t, %d);\n" (List.length cols);
+      pr "}\n";
+      pr "\n";
+  ) structs;
+
+  (* Emit a copy_TYPE_list function definition only if that function is used. *)
+  List.iter (
+    function
+    | typ, (RStructListOnly | RStructAndList) ->
+        (* generate the function for typ *)
+        emit_copy_list_function typ
+    | typ, _ -> () (* empty *)
+  ) (rstructs_used_by all_functions);
+
+  (* The wrapper functions. *)
+  List.iter (
+    fun (name, ((ret, args, optargs) as style), _, _, _, _, _) ->
+      pr "static ETERM *\n";
+      pr "run_%s (ETERM *message)\n" name;
+      pr "{\n";
+
+      iteri (
+        fun i ->
+          function
+          | Pathname n
+          | Device n | Dev_or_Path n
+          | String n
+          | FileIn n
+          | FileOut n
+          | Key n ->
+            pr "  char *%s = erl_iolist_to_string (ARG (%d));\n" n i
+          | OptString n ->
+            pr "  char *%s;\n" n;
+            pr "  if (atom_equals (ARG (%d), \"undefined\"))\n" i;
+            pr "    %s = NULL;\n" n;
+            pr "  else\n";
+            pr "    %s = erl_iolist_to_string (ARG (%d));\n" n i
+          | BufferIn n ->
+            pr "  size_t %s_size = erl_iolist_length (ARG (%d));\n" n i;
+            pr "  char *%s = erl_iolist_to_string (ARG (%d));\n" n i
+          | StringList n | DeviceList n ->
+            pr "  char **%s = get_string_list (ARG (%d));\n" n i
+          | Bool n ->
+            pr "  int %s = get_bool (ARG (%d));\n" n i
+          | Int n ->
+            pr "  int %s = ERL_INT_VALUE (ARG (%d));\n" n i
+          | Int64 n ->
+            pr "  int64_t %s = ERL_LL_VALUE (ARG (%d));\n" n i
+          | Pointer (t, n) ->
+            assert false
+      ) args;
+
+      let uc_name = String.uppercase name in
+
+      (* Optional arguments. *)
+      if optargs <> [] then (
+        pr "\n";
+        pr "  struct guestfs_%s_argv optargs_s = { .bitmask = 0 };\n" name;
+        pr "  struct guestfs_%s_argv *optargs = &optargs_s;\n" name;
+        pr "  ETERM *optargst = ARG (%d);\n" (List.length args);
+        pr "  while (!ERL_IS_EMPTY_LIST (optargst)) {\n";
+        pr "    ETERM *hd = ERL_CONS_HEAD (optargst);\n";
+        pr "    ETERM *hd_name = ERL_TUPLE_ELEMENT (hd, 0);\n";
+        pr "    ETERM *hd_value = ERL_TUPLE_ELEMENT (hd, 1);\n";
+        pr "\n";
+        List.iter (
+          fun argt ->
+            let n = name_of_argt argt in
+            let uc_n = String.uppercase n in
+            pr "    if (atom_equals (hd_name, \"%s\")) {\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 "get_bool (hd_value)"
+             | Int _ -> pr "ERL_INT_VALUE (hd_value)"
+             | Int64 _ -> pr "ERL_LL_VALUE (hd_value)"
+             | String _ -> pr "erl_iolist_to_string (hd_value)"
+             | _ -> assert false
+            );
+            pr ";\n";
+            pr "    }\n";
+            pr "    else\n";
+        ) optargs;
+        pr "      return unknown_optarg (\"%s\", hd_name);\n" name;
+        pr "    optargst = ERL_CONS_TAIL (optargst);\n";
+        pr "  }\n";
+        pr "\n";
+      );
+
+      (match ret with
+       | RErr -> pr "  int r;\n"
+       | RInt _ -> pr "  int r;\n"
+       | RInt64 _ -> pr "  int64_t r;\n"
+       | RBool _ -> pr "  int r;\n"
+       | RConstString _ | RConstOptString _ ->
+           pr "  const char *r;\n"
+       | RString _ -> pr "  char *r;\n"
+       | RStringList _ ->
+           pr "  size_t i;\n";
+           pr "  char **r;\n"
+       | RStruct (_, typ) ->
+           pr "  struct guestfs_%s *r;\n" typ
+       | RStructList (_, typ) ->
+           pr "  struct guestfs_%s_list *r;\n" typ
+       | RHashtable _ ->
+           pr "  size_t i;\n";
+           pr "  char **r;\n"
+       | RBufferOut _ ->
+           pr "  char *r;\n";
+           pr "  size_t size;\n"
+      );
+      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";
+
+      (* Free strings if we copied them above. *)
+      List.iter (
+        function
+        | Pathname n | Device n | Dev_or_Path n | String n | OptString n
+        | FileIn n | FileOut n | BufferIn n | Key n ->
+            pr "  free (%s);\n" n
+        | StringList n | DeviceList n ->
+            pr "  free_strings (%s);\n" n;
+        | Bool _ | Int _ | Int64 _ | Pointer _ -> ()
+      ) args;
+      List.iter (
+        function
+        | String n ->
+            let uc_n = String.uppercase n in
+            pr "  if ((optargs_s.bitmask & GUESTFS_%s_%s_BITMASK))\n"
+              uc_name uc_n;
+            pr "    free ((char *) optargs_s.%s);\n" n
+        | Bool _ | Int _ | Int64 _
+        | Pathname _ | Device _ | Dev_or_Path _ | OptString _
+        | FileIn _ | FileOut _ | BufferIn _ | Key _
+        | StringList _ | DeviceList _ | Pointer _ -> ()
+      ) optargs;
+
+      (match errcode_of_ret ret with
+       | `CannotReturnError -> ()
+       | `ErrorIsMinusOne ->
+           pr "  if (r == -1)\n";
+           pr "    return make_error (\"%s\");\n" name;
+       | `ErrorIsNULL ->
+           pr "  if (r == NULL)\n";
+           pr "    return make_error (\"%s\");\n" name;
+      );
+      pr "\n";
+
+      (match ret with
+       | RErr -> pr "  return erl_mk_atom (\"ok\");\n"
+       | RInt _ -> pr "  return erl_mk_int (r);\n"
+       | RInt64 _ -> pr "  return erl_mk_longlong (r);\n"
+       | RBool _ -> pr "  return make_bool (r);\n"
+       | RConstString _ -> pr "  return erl_mk_string (r);\n"
+       | RConstOptString _ ->
+           pr "  ETERM *rt;\n";
+           pr "  if (r)\n";
+           pr "    rt = erl_mk_string (r);\n";
+           pr "  else\n";
+           pr "    rt = erl_mk_atom (\"undefined\");\n";
+           pr "  return rt;\n"
+       | RString _ ->
+           pr "  ETERM *rt = erl_mk_string (r);\n";
+           pr "  free (r);\n";
+           pr "  return rt;\n"
+       | RStringList _ ->
+           pr "  ETERM *rt = make_string_list (r);\n";
+           pr "  free_strings (r);\n\n";
+           pr "  return rt;\n"
+       | RStruct (_, typ) ->
+           pr "  ETERM *rt = make_%s (r);\n" typ;
+           pr "  guestfs_free_%s (r);\n" typ;
+           pr "  return rt;\n"
+       | RStructList (_, typ) ->
+           pr "  ETERM *rt = make_%s_list (r);\n" typ;
+           pr "  guestfs_free_%s_list (r);\n" typ;
+           pr "  return rt;\n"
+       | RHashtable _ ->
+           pr "  ETERM *rt = make_table (r);\n";
+           pr "  free_strings (r);\n";
+           pr "  return rt;\n"
+       | RBufferOut _ ->
+           pr "  ETERM *rt = erl_mk_estring (r, size);\n";
+           pr "  free (r);\n";
+           pr "  return rt;\n"
+      );
+
+      pr "}\n";
+      pr "\n";
+  ) all_functions_sorted;
+
+  pr "\
+
+ETERM *
+dispatch (ETERM *message)
+{
+  ETERM *fun;
+
+  fun = ERL_TUPLE_ELEMENT (message, 0);
+
+  /* XXX We should use gperf here. */
+  ";
+
+  List.iter (
+    fun (name, (ret, args, optargs), _, _, _, _, _) ->
+      pr "if (atom_equals (fun, \"%s\"))\n" name;
+      pr "    return run_%s (message);\n" name;
+      pr "  else ";
+  ) all_functions_sorted;
+
+  pr "return unknown_function (fun);
+}
+";
index f6e99a5..716e7b5 100644 (file)
@@ -38,6 +38,7 @@ open Generator_java
 open Generator_haskell
 open Generator_csharp
 open Generator_php
 open Generator_haskell
 open Generator_csharp
 open Generator_php
+open Generator_erlang
 open Generator_bindtests
 open Generator_errnostring
 
 open Generator_bindtests
 open Generator_errnostring
 
@@ -132,6 +133,8 @@ Run it from the top source directory using the command
   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;
   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;
+  output_to "erlang/guestfs.erl" generate_erlang_erl;
+  output_to "erlang/erl-guestfs.c" generate_erlang_c;
 
   (* Generate the list of files generated -- last. *)
   printf "generated %d lines of code\n" (get_lines_generated ());
 
   (* Generate the list of files generated -- last. *)
   printf "generated %d lines of code\n" (get_lines_generated ());
index abf62db..cda4ed2 100644 (file)
@@ -46,6 +46,7 @@ Calling any method on a closed handle raises the same exception.
 
 L<guestfs(3)>,
 L<guestfs-examples(3)>,
 
 L<guestfs(3)>,
 L<guestfs-examples(3)>,
+L<guestfs-erlang(3)>,
 L<guestfs-ocaml(3)>,
 L<guestfs-perl(3)>,
 L<guestfs-python(3)>,
 L<guestfs-ocaml(3)>,
 L<guestfs-perl(3)>,
 L<guestfs-python(3)>,
index 0978650..7d0c041 100644 (file)
@@ -79,6 +79,7 @@ function that you called.
 
 L<guestfs(3)>,
 L<guestfs-examples(3)>,
 
 L<guestfs(3)>,
 L<guestfs-examples(3)>,
+L<guestfs-erlang(3)>,
 L<guestfs-java(3)>,
 L<guestfs-perl(3)>,
 L<guestfs-python(3)>,
 L<guestfs-java(3)>,
 L<guestfs-perl(3)>,
 L<guestfs-python(3)>,
index 0973382..f83d1fd 100644 (file)
@@ -41,6 +41,7 @@ C<croak> (see L<Carp(3)>).
 L<Sys::Guestfs(3)>,
 L<guestfs(3)>,
 L<guestfs-examples(3)>,
 L<Sys::Guestfs(3)>,
 L<guestfs(3)>,
 L<guestfs-examples(3)>,
+L<guestfs-erlang(3)>,
 L<guestfs-java(3)>,
 L<guestfs-ocaml(3)>,
 L<guestfs-python(3)>,
 L<guestfs-java(3)>,
 L<guestfs-ocaml(3)>,
 L<guestfs-python(3)>,
index 43e7ffa..df54873 100644 (file)
@@ -81,6 +81,8 @@ df/domains.c
 df/main.c
 df/output.c
 edit/virt-edit.c
 df/main.c
 df/output.c
 edit/virt-edit.c
+erlang/erl-guestfs-proto.c
+erlang/erl-guestfs.c
 fish/alloc.c
 fish/cmds.c
 fish/cmds_gperf.c
 fish/alloc.c
 fish/cmds.c
 fish/cmds_gperf.c
index cab7e9b..cc87ef4 100644 (file)
@@ -43,6 +43,7 @@ Type:
 
 L<guestfs(3)>,
 L<guestfs-examples(3)>,
 
 L<guestfs(3)>,
 L<guestfs-examples(3)>,
+L<guestfs-erlang(3)>,
 L<guestfs-java(3)>,
 L<guestfs-ocaml(3)>,
 L<guestfs-perl(3)>,
 L<guestfs-java(3)>,
 L<guestfs-ocaml(3)>,
 L<guestfs-perl(3)>,
index 9197f9b..1af133d 100644 (file)
@@ -37,6 +37,7 @@ string).
 
 L<guestfs(3)>,
 L<guestfs-examples(3)>,
 
 L<guestfs(3)>,
 L<guestfs-examples(3)>,
+L<guestfs-erlang(3)>,
 L<guestfs-java(3)>,
 L<guestfs-ocaml(3)>,
 L<guestfs-perl(3)>,
 L<guestfs-java(3)>,
 L<guestfs-ocaml(3)>,
 L<guestfs-perl(3)>,
index 06c3530..7ff313d 100644 (file)
@@ -42,8 +42,8 @@ FUSE.
 
 Libguestfs is a library that can be linked with C and C++ management
 programs (or management programs written in OCaml, Perl, Python, Ruby,
 
 Libguestfs is a library that can be linked with C and C++ management
 programs (or management programs written in OCaml, Perl, Python, Ruby,
-Java, PHP, Haskell or C#).  You can also use it from shell scripts or the
-command line.
+Java, PHP, Erlang, 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
 need enough permissions to access the disk images.
 
 You don't need to be root to use libguestfs, although obviously you do
 need enough permissions to access the disk images.
@@ -719,6 +719,10 @@ used.
 The C# bindings are highly experimental.  Please read the warnings
 at the top of C<csharp/Libguestfs.cs>.
 
 The C# bindings are highly experimental.  Please read the warnings
 at the top of C<csharp/Libguestfs.cs>.
 
+=item B<Erlang>
+
+See L<guestfs-erlang(3)>.
+
 =item B<Haskell>
 
 This is the only language binding that is working but incomplete.
 =item B<Haskell>
 
 This is the only language binding that is working but incomplete.
@@ -2898,6 +2902,8 @@ will work with libguestfs.
 
 =item C<csharp>
 
 
 =item C<csharp>
 
+=item C<erlang>
+
 =item C<haskell>
 
 =item C<java>
 =item C<haskell>
 
 =item C<java>
@@ -3135,6 +3141,7 @@ enough.
 =head1 SEE ALSO
 
 L<guestfs-examples(3)>,
 =head1 SEE ALSO
 
 L<guestfs-examples(3)>,
+L<guestfs-erlang(3)>,
 L<guestfs-java(3)>,
 L<guestfs-ocaml(3)>,
 L<guestfs-perl(3)>,
 L<guestfs-java(3)>,
 L<guestfs-ocaml(3)>,
 L<guestfs-perl(3)>,