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
+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
@@ -118,6 +124,7 @@ haskell/Guestfs.hs
 *.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
index ca31727..c7166b7 100644 (file)
@@ -60,6 +60,9 @@ endif
 if HAVE_PHP
 SUBDIRS += php
 endif
+if HAVE_ERLANG
+SUBDIRS += erlang erlang/examples
+endif
 
 # Unconditional because nothing is built yet.
 SUBDIRS += csharp
@@ -131,6 +134,7 @@ EXTRA_DIST = \
 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 \
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"])
 
+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"],
         [
@@ -933,6 +971,8 @@ AC_CONFIG_FILES([Makefile
                  debian/changelog
                  df/Makefile
                  edit/Makefile
+                 erlang/Makefile
+                 erlang/examples/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
+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
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)>,
+L<guestfs-erlang(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-erlang(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_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
@@ -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_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_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
-# 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
@@ -46,6 +46,7 @@ SOURCES = \
        generator_haskell.ml \
        generator_csharp.ml \
        generator_php.ml \
+       generator_erlang.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
+  | ErlangStyle
 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 "(* "; " *"
-    | 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;
@@ -113,6 +115,7 @@ let generate_header ?(extra_inputs = []) comment license =
   (match comment with
    | CStyle -> pr " */\n"
    | CPlusPlusStyle
+   | ErlangStyle
    | 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_erlang
 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 "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 ());
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-erlang(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-erlang(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<guestfs-erlang(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
+erlang/erl-guestfs-proto.c
+erlang/erl-guestfs.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-erlang(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-erlang(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,
-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.
@@ -719,6 +719,10 @@ used.
 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.
@@ -2898,6 +2902,8 @@ will work with libguestfs.
 
 =item C<csharp>
 
+=item C<erlang>
+
 =item C<haskell>
 
 =item C<java>
@@ -3135,6 +3141,7 @@ enough.
 =head1 SEE ALSO
 
 L<guestfs-examples(3)>,
+L<guestfs-erlang(3)>,
 L<guestfs-java(3)>,
 L<guestfs-ocaml(3)>,
 L<guestfs-perl(3)>,