summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorRichard W.M. Jones <rjones@redhat.com>2012-11-17 10:10:13 +0000
committerRichard W.M. Jones <rjones@redhat.com>2012-11-17 20:02:42 +0000
commitff8bfd3e92f21955d0a91b481583131f356b8fe4 (patch)
tree7c3b36c732ea0b83209180099ba03f5ef1948238
parent4a2e8e89577ad4031aba1b2e5afb65ca58c2c39d (diff)
downloadlibguestfs-ff8bfd3e92f21955d0a91b481583131f356b8fe4.tar.gz
libguestfs-ff8bfd3e92f21955d0a91b481583131f356b8fe4.tar.xz
libguestfs-ff8bfd3e92f21955d0a91b481583131f356b8fe4.zip
Add Lua bindings.
These are relatively complete, although only lightly tested. Missing: - events - last_errno - user_cancel
-rw-r--r--.gitignore6
-rw-r--r--Makefile.am4
-rw-r--r--configure.ac26
-rw-r--r--examples/guestfs-examples.pod1
-rw-r--r--examples/guestfs-recipes.pod1
-rw-r--r--generator/Makefile.am1
-rw-r--r--generator/lua.ml561
-rw-r--r--generator/main.ml2
-rw-r--r--java/examples/guestfs-java.pod1
-rw-r--r--lua/Makefile.am79
-rw-r--r--lua/examples/LICENSE2
-rw-r--r--lua/examples/Makefile.am40
-rw-r--r--lua/examples/create_disk.lua66
-rw-r--r--lua/examples/guestfs-lua.pod97
-rw-r--r--lua/examples/inspect_vm.lua62
-rwxr-xr-xlua/tests/010-load.lua19
-rwxr-xr-xlua/tests/020-create.lua21
-rwxr-xr-xlua/tests/025-create-flags.lua21
-rwxr-xr-xlua/tests/030-config.lua42
-rwxr-xr-xlua/tests/050-lvcreate.lua47
-rwxr-xr-xlua/tests/060-readdir.lua65
-rwxr-xr-xlua/tests/070-optargs.lua25
-rw-r--r--ocaml/examples/guestfs-ocaml.pod1
-rw-r--r--perl/examples/guestfs-perl.pod1
-rw-r--r--po-docs/ja/Makefile.am1
-rw-r--r--po-docs/podfiles1
-rw-r--r--po-docs/uk/Makefile.am1
-rw-r--r--po/POTFILES1
-rw-r--r--python/examples/guestfs-python.pod1
-rw-r--r--ruby/examples/guestfs-ruby.pod1
-rwxr-xr-xrun.in8
-rw-r--r--src/guestfs.pod7
32 files changed, 1212 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore
index a8d9155f..d0c91a7e 100644
--- a/.gitignore
+++ b/.gitignore
@@ -164,6 +164,7 @@ Makefile.in
/html/guestfs-examples.3.html
/html/guestfs-faq.1.html
/html/guestfs-java.3.html
+/html/guestfs-lua.3.html
/html/guestfs-ocaml.3.html
/html/guestfs-performance.1.html
/html/guestfs-perl.3.html
@@ -215,6 +216,11 @@ Makefile.in
/libtool
/local*
/ltmain.sh
+/lua/examples/guestfs-lua.3
+/lua/examples/stamp-guestfs-lua.pod
+/lua/guestfs.so
+/lua/lua-guestfs.c
+/lua/test.img
/m4/ChangeLog
/m4/gnulib-cache.m4
/m4/intmax.m4
diff --git a/Makefile.am b/Makefile.am
index 12896f40..1ae78496 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -94,6 +94,9 @@ endif
if HAVE_ERLANG
SUBDIRS += erlang erlang/examples
endif
+if HAVE_LUA
+SUBDIRS += lua lua/examples
+endif
if HAVE_GOBJECT
SUBDIRS += gobject
endif
@@ -175,6 +178,7 @@ HTMLFILES = \
html/guestfs-erlang.3.html \
html/guestfs-faq.1.html \
html/guestfs-java.3.html \
+ html/guestfs-lua.3.html \
html/guestfs-ocaml.3.html \
html/guestfs-performance.1.html \
html/guestfs-perl.3.html \
diff --git a/configure.ac b/configure.ac
index 73183698..9fe7d4ae 100644
--- a/configure.ac
+++ b/configure.ac
@@ -1240,6 +1240,28 @@ if test "x$enable_erlang" != "xno"; then
fi
AM_CONDITIONAL([HAVE_ERLANG], [test "x$ERLANG" != "xno" && test "x$ERLC" != "xno"])
+dnl Lua
+LUA=no
+AC_ARG_ENABLE([lua],
+ AS_HELP_STRING([--disable-lua], [disable Lua language bindings]),
+ [],
+ [enable_lua=yes])
+AS_IF([test "x$enable_lua" != "xno"],[
+ LUA=
+ AC_CHECK_PROG([LUA],[lua],[lua],[no])
+ AC_CHECK_HEADER([lua.h],[have_lua_h=yes])
+ AC_CHECK_HEADER([lauxlib.h],[have_lauxlib_h=yes])
+ AC_CHECK_LIB([lua],[lua_checkstack],[have_lua_lib=yes])
+
+ AS_IF([test "x$LUA" != "xno"],[
+ AC_MSG_CHECKING([for Lua version])
+ LUA_VERSION=`$LUA -e 'print(_VERSION)' | awk '{print $2}'`
+ AC_MSG_RESULT([$LUA_VERSION])
+ AC_SUBST([LUA_VERSION])
+ ])
+])
+AM_CONDITIONAL([HAVE_LUA], [test "x$LUA" != "xno" && test "x$have_lua_h" = "xyes" && test "x$have_lauxlib_h" = "xyes" && test "x$have_lua_lib" = "xyes"])
+
dnl Check for Perl modules needed by Perl virt tools (virt-df, etc.)
AS_IF([test "x$PERL" != "xno"],
[
@@ -1368,6 +1390,8 @@ AC_CONFIG_FILES([Makefile
inspector/Makefile
java/Makefile
java/examples/Makefile
+ lua/Makefile
+ lua/examples/Makefile
ocaml/META
ocaml/Makefile
ocaml/examples/Makefile
@@ -1452,6 +1476,8 @@ AS_ECHO_N(["PHP bindings ........................ "])
if test "x$HAVE_PHP_TRUE" = "x"; then echo "yes"; else echo "no"; fi
AS_ECHO_N(["Erlang bindings ..................... "])
if test "x$HAVE_ERLANG_TRUE" = "x"; then echo "yes"; else echo "no"; fi
+AS_ECHO_N(["Lua bindings ........................ "])
+if test "x$HAVE_LUA_TRUE" = "x"; then echo "yes"; else echo "no"; fi
AS_ECHO_N(["gobject bindings .................... "])
if test "x$HAVE_GOBJECT_TRUE" = "x"; then echo "yes"; else echo "no"; fi
AS_ECHO_N(["gobject introspection ............... "])
diff --git a/examples/guestfs-examples.pod b/examples/guestfs-examples.pod
index ef507edb..aba7caa9 100644
--- a/examples/guestfs-examples.pod
+++ b/examples/guestfs-examples.pod
@@ -35,6 +35,7 @@ libguestfs, you also need to read L<guestfs(3)>.
L<guestfs(3)>,
L<guestfs-erlang(3)>,
L<guestfs-java(3)>,
+L<guestfs-lua(3)>,
L<guestfs-ocaml(3)>,
L<guestfs-perl(3)>,
L<guestfs-python(3)>,
diff --git a/examples/guestfs-recipes.pod b/examples/guestfs-recipes.pod
index 1a1e51c4..0a12703f 100644
--- a/examples/guestfs-recipes.pod
+++ b/examples/guestfs-recipes.pod
@@ -500,6 +500,7 @@ L<guestfish(1)>,
L<guestfs-examples(3)>,
L<guestfs-erlang(3)>,
L<guestfs-java(3)>,
+L<guestfs-lua(3)>,
L<guestfs-ocaml(3)>,
L<guestfs-perl(3)>,
L<guestfs-python(3)>,
diff --git a/generator/Makefile.am b/generator/Makefile.am
index e218ffac..bc920708 100644
--- a/generator/Makefile.am
+++ b/generator/Makefile.am
@@ -47,6 +47,7 @@ SOURCES = \
csharp.ml \
php.ml \
erlang.ml \
+ lua.ml \
gobject.ml \
bindtests.ml \
errnostring.ml \
diff --git a/generator/lua.ml b/generator/lua.ml
new file mode 100644
index 00000000..4f652e96
--- /dev/null
+++ b/generator/lua.ml
@@ -0,0 +1,561 @@
+(* libguestfs
+ * Copyright (C) 2012 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 Types
+open Utils
+open Pr
+open Docstrings
+open Optgroups
+open Actions
+open Structs
+open C
+open Events
+
+let generate_lua_c () =
+ generate_header CStyle LGPLv2plus;
+
+ pr "\
+#include <config.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <inttypes.h>
+#include <string.h>
+
+/*#define LUA_LIB*/
+#include <lua.h>
+#include <lauxlib.h>
+
+#include <guestfs.h>
+
+#define LUA_GUESTFS_HANDLE \"guestfs handle\"
+
+/* This struct is managed on the Lua heap. If the GC collects it,
+ * the Lua '__gc' function is called which ends up calling
+ * lua_guestfs_finalizer. If we need to store other per-handle
+ * data in future, that can be placed into this struct.
+ */
+struct userdata {
+ guestfs_h *g; /* Libguestfs handle, NULL if closed. */
+};
+
+static struct userdata *get_handle (lua_State *L, int index);
+static char **get_string_list (lua_State *L, int index);
+static void push_string_list (lua_State *L, char **strs);
+static void push_table (lua_State *L, char **table);
+static int64_t get_int64 (lua_State *L, int index);
+static void push_int64 (lua_State *L, int64_t i64);
+
+";
+
+ List.iter (
+ function
+ | typ, RStructOnly ->
+ pr "static void push_%s (lua_State *L, struct guestfs_%s *v);\n" typ typ;
+ | typ, (RStructListOnly | RStructAndList) ->
+ pr "static void push_%s (lua_State *L, struct guestfs_%s *v);\n" typ typ;
+ pr "static void push_%s_list (lua_State *L, struct guestfs_%s_list *v);\n" typ typ
+ ) (rstructs_used_by all_functions);
+
+ pr "\
+
+#define OPTARG_IF_SET(index, name, code) \\
+ do { \\
+ lua_pushliteral (L, name); \\
+ lua_gettable (L, index); \\
+ if (!lua_isnil (L, -1)) { \\
+ code \\
+ } \\
+ lua_pop (L, 1); \\
+ } while (0)
+
+/* Create a new connection. */
+static int
+lua_guestfs_create (lua_State *L)
+{
+ guestfs_h *g;
+ struct userdata *u;
+ unsigned flags = 0;
+
+ if (lua_gettop (L) == 1) {
+ OPTARG_IF_SET (1, \"environment\",
+ if (! lua_toboolean (L, -1))
+ flags |= GUESTFS_CREATE_NO_ENVIRONMENT;
+ );
+ OPTARG_IF_SET (1, \"close_on_exit\",
+ if (! lua_toboolean (L, -1))
+ flags |= GUESTFS_CREATE_NO_CLOSE_ON_EXIT;
+ );
+ }
+ else if (lua_gettop (L) > 1)
+ return luaL_error (L, \"Guestfs.create: too many arguments\");
+
+ g = guestfs_create_flags (flags);
+ if (!g)
+ return luaL_error (L, \"Guestfs.create: cannot create handle: %%m\");
+
+ u = lua_newuserdata (L, sizeof (struct userdata));
+ luaL_getmetatable (L, LUA_GUESTFS_HANDLE);
+ lua_setmetatable (L, -2);
+
+ u->g = g;
+
+ return 1;
+}
+
+/* Finalizer. */
+static int
+lua_guestfs_finalizer (lua_State *L)
+{
+ struct userdata *u = get_handle (L, 1);
+
+ if (u->g)
+ guestfs_close (u->g);
+
+ /* u will be freed by Lua when we return. */
+
+ return 0;
+}
+
+/* Explicit close. */
+static int
+lua_guestfs_close (lua_State *L)
+{
+ struct userdata *u = get_handle (L, 1);
+
+ if (u->g) {
+ guestfs_close (u->g);
+ u->g = NULL;
+ }
+
+ return 0;
+}
+
+";
+
+ List.iter (
+ fun { name = name; style = (ret, args, optargs as style);
+ c_function = c_function; c_optarg_prefix = c_optarg_prefix } ->
+ pr "static int\n";
+ pr "lua_guestfs_%s (lua_State *L)\n" name;
+ pr "{\n";
+
+ (match ret with
+ | RErr ->
+ pr " int r;\n";
+ | RInt _
+ | RBool _ ->
+ pr " int r;\n";
+ | RInt64 _ ->
+ pr " int64_t r;\n";
+ | RConstString _ ->
+ pr " const char *r;\n";
+ | RConstOptString _ ->
+ pr " const char *r;\n";
+ | RString _ ->
+ pr " char *r;\n";
+ | RStringList _ | RHashtable _ ->
+ pr " char **r;\n";
+ | RStruct (_, typ) ->
+ pr " struct guestfs_%s *r;\n" typ;
+ | RStructList (_, typ) ->
+ pr " struct guestfs_%s_list *r;\n" typ;
+ | RBufferOut _ ->
+ pr " char *r;\n";
+ pr " size_t size;\n";
+ );
+
+ (* Handle, arguments. *)
+ pr " struct userdata *u = get_handle (L, 1);\n";
+ pr " guestfs_h *g = u->g;\n";
+
+ List.iter (
+ function
+ | Pathname n | Device n | Dev_or_Path n | String n
+ | FileIn n | FileOut n | Key n ->
+ pr " const char *%s;\n" n
+ | BufferIn n ->
+ pr " const char *%s;\n" n;
+ pr " size_t %s_size;\n" n;
+ | OptString n ->
+ pr " const char *%s;\n" n;
+ | StringList n | DeviceList n ->
+ pr " char **%s;\n" n
+ | Bool n -> pr " int %s;\n" n
+ | Int n -> pr " int %s;\n" n
+ | Int64 n -> pr " int64_t %s;\n" n
+ | Pointer (t, n) -> pr " %s %s;\n" t n
+ ) args;
+ if optargs <> [] then (
+ pr " struct %s optargs_s = { .bitmask = 0 };\n" c_function;
+ pr " struct %s *optargs = &optargs_s;\n" c_function
+ );
+ pr "\n";
+
+ pr " if (g == NULL)\n";
+ pr " luaL_error (L, \"Guestfs.%%s: handle is closed\",\n";
+ pr " \"%s\");\n" name;
+ pr "\n";
+
+ iteri (
+ fun i ->
+ let i = i+2 in (* Lua indexes from 1(!), plus the handle. *)
+ function
+ | Pathname n | Device n | Dev_or_Path n | String n
+ | FileIn n | FileOut n | Key n ->
+ pr " %s = luaL_checkstring (L, %d);\n" n i
+ | BufferIn n ->
+ pr " %s = luaL_checklstring (L, %d, &%s_size);\n" n i n
+ | OptString n ->
+ pr " %s = luaL_optstring (L, %d, NULL);\n" n i
+ | StringList n | DeviceList n ->
+ pr " %s = get_string_list (L, %d);\n" n i
+ | Bool n ->
+ pr " %s = lua_toboolean (L, %d);\n" n i
+ | Int n ->
+ pr " %s = lua_tointeger (L, %d);\n" n i
+ | Int64 n ->
+ pr " %s = get_int64 (L, %d);\n" n i
+ | Pointer (t, n) -> assert false
+ ) args;
+
+ if optargs <> [] then (
+ (* Index of the optarg table on the stack. *)
+ let optarg_index = List.length args + 2 in
+
+ pr "\n";
+ pr " /* Check for optional arguments, encoded in a table. */\n";
+ pr " if (lua_type (L, %d) == LUA_TTABLE) {\n" optarg_index;
+
+ List.iter (
+ fun optarg ->
+ let n = name_of_optargt optarg in
+ let uc_n = String.uppercase n in
+ pr " OPTARG_IF_SET (%d, \"%s\",\n" optarg_index n;
+ pr " optargs_s.bitmask |= %s_%s_BITMASK;\n"
+ c_optarg_prefix uc_n;
+ (match optarg with
+ | OBool n ->
+ pr " optargs_s.%s = lua_toboolean (L, -1);\n" n
+ | OInt n ->
+ pr " optargs_s.%s = lua_tointeger (L, -1);\n" n
+ | OInt64 n ->
+ pr " optargs_s.%s = get_int64 (L, -1);\n" n
+ | OString n ->
+ pr " optargs_s.%s = luaL_checkstring (L, -1);\n" n
+ | OStringList n ->
+ pr " optargs_s.%s = get_string_list (L, -1);\n" n
+ );
+ pr " );\n"
+ ) optargs;
+
+ pr " }\n";
+ );
+ pr "\n";
+
+ (* Invoke the C function. *)
+ pr " r = %s " c_function;
+ generate_c_call_args ~handle:"g" style;
+ pr ";\n";
+
+ (* Free temporary data. *)
+ List.iter (
+ function
+ | Pathname _ | Device _ | Dev_or_Path _ | String _
+ | FileIn _ | FileOut _ | Key _
+ | BufferIn _ | OptString _
+ | Bool _ | Int _ | Int64 _
+ | Pointer _ -> ()
+ | StringList n | DeviceList n ->
+ pr " free (%s);\n" n
+ ) args;
+ List.iter (
+ function
+ | OBool _ | OInt _ | OInt64 _ | OString _ -> ()
+ | OStringList n ->
+ pr " free ((char *) optargs_s.%s);\n" n
+ ) optargs;
+
+ (* Handle errors. *)
+ (match errcode_of_ret ret with
+ | `CannotReturnError -> ()
+ | `ErrorIsMinusOne ->
+ pr " if (r == -1)\n";
+ pr " return luaL_error (L, \"Guestfs.%%s: %%s\",\n";
+ pr " \"%s\", guestfs_last_error (g));\n" name;
+ pr "\n"
+ | `ErrorIsNULL ->
+ pr " if (r == NULL)\n";
+ pr " return luaL_error (L, \"Guestfs.%%s: %%s\",\n";
+ pr " \"%s\", guestfs_last_error (g));\n" name;
+ pr "\n";
+ );
+
+ (* Push return value on the stack. *)
+ (match ret with
+ | RErr -> ()
+ | RInt _ ->
+ pr " lua_pushinteger (L, r);\n"
+ | RBool _ ->
+ pr " lua_pushboolean (L, r);\n"
+ | RInt64 _ ->
+ pr " push_int64 (L, r);\n"
+ | RConstString _
+ | RConstOptString _
+ | RString _ ->
+ pr " lua_pushstring (L, r);\n"
+ | RStringList _ ->
+ pr " push_string_list (L, r);\n"
+ | RHashtable _ ->
+ pr " push_table (L, r);\n"
+ | RStruct (_, typ) ->
+ pr " push_%s (L, r);\n" typ
+ | RStructList (_, typ) ->
+ pr " push_%s_list (L, r);\n" typ
+ | RBufferOut _ ->
+ pr " lua_pushlstring (L, r, size);\n"
+ );
+
+ if ret = RErr then
+ pr " return 0;\n"
+ else
+ pr " return 1;\n";
+ pr "}\n";
+ pr "\n"
+ ) all_functions_sorted;
+
+ pr "\
+static struct userdata *
+get_handle (lua_State *L, int index)
+{
+ struct userdata *u;
+
+ u = luaL_checkudata (L, index, LUA_GUESTFS_HANDLE);
+ return u;
+}
+
+/* NB: caller must free the array, but NOT the strings */
+static char **
+get_string_list (lua_State *L, int index)
+{
+ size_t len = lua_objlen (L, index);
+ size_t i;
+ char **strs;
+
+ strs = malloc ((len+1) * sizeof (char *));
+ if (strs == NULL) {
+ luaL_error (L, \"get_string_list: malloc failed: %%m\");
+ /*NOTREACHED*/
+ return NULL;
+ }
+
+ for (i = 0; i < len; ++i) {
+ lua_pushinteger (L, i+1 /* because of base 1 arrays */);
+ lua_gettable (L, index);
+ strs[i] = (char *) luaL_checkstring (L, -1);
+ lua_pop (L, 1);
+ }
+ strs[len] = NULL;
+
+ return strs;
+}
+
+static void
+push_string_list (lua_State *L, char **strs)
+{
+ size_t i;
+
+ lua_newtable (L);
+ for (i = 0; strs[i] != NULL; ++i) {
+ lua_pushinteger (L, i+1 /* because of base 1 arrays */);
+ lua_pushstring (L, strs[i]);
+ lua_settable (L, -3);
+ }
+}
+
+static void
+push_table (lua_State *L, char **table)
+{
+ size_t i;
+
+ lua_newtable (L);
+ for (i = 0; table[i] != NULL; i += 2) {
+ lua_pushstring (L, table[i]);
+ lua_pushstring (L, table[i+1]);
+ lua_settable (L, -3);
+ }
+}
+
+/* Because Lua doesn't have real 64 bit ints (eg. on 32 bit), which
+ * sucks, we implement these as strings. It's left as an exercise to
+ * the caller to turn strings to/from integers.
+ */
+static int64_t
+get_int64 (lua_State *L, int index)
+{
+ int64_t r;
+ const char *s;
+
+ s = luaL_checkstring (L, index);
+ if (sscanf (s, \"%%\" SCNi64, &r) != 1)
+ return luaL_error (L, \"int64 parameter expected\");
+ return r;
+}
+
+static void
+push_int64 (lua_State *L, int64_t i64)
+{
+ char s[64];
+
+ snprintf (s, sizeof s, \"%%\" PRIi64, i64);
+ lua_pushstring (L, s);
+}
+
+";
+
+ let generate_push_struct typ =
+ pr "static void\n";
+ pr "push_%s (lua_State *L, struct guestfs_%s *v)\n" typ typ;
+ pr "{\n";
+ pr " lua_newtable (L);\n";
+ List.iter (
+ fun (n, field) ->
+ pr " lua_pushliteral (L, \"%s\");\n" n;
+ (match field with
+ | FChar ->
+ pr " lua_pushlstring (L, &v->%s, 1);\n" n
+ | FString ->
+ pr " lua_pushstring (L, v->%s);\n" n
+ | FBuffer ->
+ pr " lua_pushlstring (L, v->%s, v->%s_len);\n" n n
+ | FUInt32
+ | FInt32 ->
+ pr " lua_pushinteger (L, v->%s);\n" n
+ | FUInt64
+ | FInt64
+ | FBytes ->
+ pr " push_int64 (L, (int64_t) v->%s);\n" n
+ | FUUID ->
+ pr " lua_pushlstring (L, v->%s, 32);\n" n
+ | FOptPercent ->
+ pr " lua_pushnumber (L, v->%s);\n" n
+ );
+ pr " lua_settable (L, -3);\n"
+ ) (lookup_struct typ).s_cols;
+ pr "}\n";
+ pr "\n";
+
+ and generate_push_struct_list typ =
+ pr "static void\n";
+ pr "push_%s_list (lua_State *L, struct guestfs_%s_list *v)\n" typ typ;
+ pr "{\n";
+ pr " size_t i;\n";
+ pr "\n";
+ pr " lua_newtable (L);\n";
+ pr " for (i = 0; i < v->len; ++i) {\n";
+ pr " lua_pushinteger (L, i+1 /* because of base 1 arrays */);\n";
+ pr " push_%s (L, &v->val[i]);\n" typ;
+ pr " lua_settable (L, -3);\n";
+ pr " }\n";
+ pr "}\n";
+ pr "\n"
+ in
+
+ List.iter (
+ function
+ | typ, RStructOnly ->
+ generate_push_struct typ
+ | typ, (RStructListOnly | RStructAndList) ->
+ generate_push_struct typ;
+ generate_push_struct_list typ
+ ) (rstructs_used_by all_functions);
+
+ pr "\
+
+static luaL_Reg handle_methods[] = {
+ { \"__gc\", lua_guestfs_finalizer },
+ { \"create\", lua_guestfs_create },
+ { \"close\", lua_guestfs_close },
+
+";
+
+ List.iter (
+ fun { name = name } -> pr " { \"%s\", lua_guestfs_%s },\n" name name
+ ) all_functions_sorted;
+
+ pr "\
+
+ { NULL, NULL }
+};
+
+static void
+make_version_string (char *version, size_t size)
+{
+ guestfs_h *g;
+ struct guestfs_version *v;
+
+ g = guestfs_create ();
+ v = guestfs_version (g);
+ snprintf (version, size,
+ \"libguestfs %%\" PRIi64 \".%%\" PRIi64 \".%%\" PRIi64 \"%%s\",
+ v->major, v->minor, v->release, v->extra);
+ free (v);
+ guestfs_close (g);
+}
+
+extern int luaopen_guestfs (lua_State *L);
+
+int
+luaopen_guestfs (lua_State *L)
+{
+ char v[256];
+
+ /* Create metatable and register methods into it. */
+ luaL_newmetatable (L, LUA_GUESTFS_HANDLE);
+ luaL_register (L, NULL /* \"guestfs\" ? XXX */, handle_methods);
+
+ /* Set __index field of metatable to point to itself. */
+ lua_pushvalue (L, -1);
+ lua_setfield (L, -1, \"__index\");
+
+ /* Add _COPYRIGHT, etc. fields to the metatable. */
+ lua_pushliteral (L, \"_COPYRIGHT\");
+ lua_pushliteral (L, \"Copyright (C) %s Red Hat Inc.\");
+ lua_settable (L, -3);
+
+ lua_pushliteral (L, \"_DESCRIPTION\");
+ lua_pushliteral (L, \"Lua binding to libguestfs\");
+ lua_settable (L, -3);
+
+ lua_pushliteral (L, \"_VERSION\");
+ make_version_string (v, sizeof v);
+ lua_pushlstring (L, v, strlen (v));
+ lua_settable (L, -3);
+
+ /* Expose metatable to lua as \"Guestfs\". */
+ lua_setglobal (L, \"Guestfs\");
+
+ return 1;
+}
+
+" copyright_years;
diff --git a/generator/main.ml b/generator/main.ml
index f8df1295..55867474 100644
--- a/generator/main.ml
+++ b/generator/main.ml
@@ -41,6 +41,7 @@ open Haskell
open Csharp
open Php
open Erlang
+open Lua
open Gobject
open Bindtests
open Errnostring
@@ -152,6 +153,7 @@ Run it from the top source directory using the command
output_to "erlang/guestfs.erl" generate_erlang_erl;
output_to "erlang/erl-guestfs.c" generate_erlang_c;
output_to ~perm:0o555 "erlang/bindtests.erl" generate_erlang_bindtests;
+ output_to "lua/lua-guestfs.c" generate_lua_c;
output_to "gobject/bindtests.js" generate_gobject_js_bindtests;
output_to "gobject/Makefile.inc" generate_gobject_makefile;
diff --git a/java/examples/guestfs-java.pod b/java/examples/guestfs-java.pod
index 23da8ad4..0795c86b 100644
--- a/java/examples/guestfs-java.pod
+++ b/java/examples/guestfs-java.pod
@@ -47,6 +47,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-lua(3)>,
L<guestfs-ocaml(3)>,
L<guestfs-perl(3)>,
L<guestfs-python(3)>,
diff --git a/lua/Makefile.am b/lua/Makefile.am
new file mode 100644
index 00000000..e642a130
--- /dev/null
+++ b/lua/Makefile.am
@@ -0,0 +1,79 @@
+# libguestfs Lua bindings
+# Copyright (C) 2012 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 $(top_srcdir)/subdir-rules.mk
+
+lualibdir = $(libdir)/lua/$(LUA_VERSION)
+
+generator_built = \
+ lua-guestfs.c
+
+EXTRA_DIST = \
+ $(generator_built)
+
+CLEANFILES = *~ guestfs.so
+
+if HAVE_LUA
+
+# Libtool forces us to use 'libluaguestfs.so' instead of the desired
+# name 'guestfs.so'. However we'll rename it in the install hook.
+# Cannot use 'noinst' here as that prevents the shared library from
+# being built at all.
+lualib_LTLIBRARIES = libluaguestfs.la
+libluaguestfs_la_SOURCES = lua-guestfs.c
+
+libluaguestfs_la_CFLAGS = \
+ $(WARN_CFLAGS) $(WERROR_CFLAGS) \
+ -I$(top_srcdir)/src -I$(top_builddir)/src
+libluaguestfs_la_LIBADD = $(top_builddir)/src/libguestfs.la
+libluaguestfs_la_LDFLAGS = -avoid-version -shared
+
+# Hack so we can run without installing.
+noinst_DATA = guestfs.so
+guestfs.so: libluaguestfs.la
+ ln -sf .libs/libluaguestfs.so $@
+
+# Tests.
+TESTS_ENVIRONMENT = $(top_builddir)/run --test
+TESTS = \
+ tests/010-load.lua \
+ tests/020-create.lua \
+ tests/025-create-flags.lua \
+ tests/030-config.lua \
+ tests/070-optargs.lua
+
+if ENABLE_APPLIANCE
+TESTS += \
+ tests/050-lvcreate.lua \
+ tests/060-readdir.lua
+endif
+
+EXTRA_DIST += \
+ tests/010-load.lua \
+ tests/020-create.lua \
+ tests/025-create-flags.lua \
+ tests/030-config.lua \
+ tests/050-lvcreate.lua \
+ tests/060-readdir.lua \
+ tests/070-optargs.lua
+
+# Custom install rule.
+install-data-hook:
+ mkdir -p $(DESTDIR)$(lualibdir)
+ mv $(DESTDIR)$(lualibdir)/libluaguestfs.so $(DESTDIR)$(lualibdir)/guestfs.so
+
+endif
diff --git a/lua/examples/LICENSE b/lua/examples/LICENSE
new file mode 100644
index 00000000..c5976b51
--- /dev/null
+++ b/lua/examples/LICENSE
@@ -0,0 +1,2 @@
+All the examples in the lua/examples/ subdirectory may be freely
+copied without any restrictions.
diff --git a/lua/examples/Makefile.am b/lua/examples/Makefile.am
new file mode 100644
index 00000000..8dc4650c
--- /dev/null
+++ b/lua/examples/Makefile.am
@@ -0,0 +1,40 @@
+# libguestfs Lua examples
+# Copyright (C) 2012 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.
+
+EXTRA_DIST = \
+ LICENSE \
+ create_disk.lua \
+ inspect_vm.lua \
+ guestfs-lua.pod
+
+CLEANFILES = stamp-guestfs-lua.pod
+
+man_MANS = guestfs-lua.3
+noinst_DATA = $(top_builddir)/html/guestfs-lua.3.html
+
+guestfs-lua.3 $(top_builddir)/html/guestfs-lua.3.html: stamp-guestfs-lua.pod
+
+stamp-guestfs-lua.pod: guestfs-lua.pod create_disk.lua inspect_vm.lua
+ $(PODWRAPPER) \
+ --section 3 \
+ --man guestfs-lua.3 \
+ --html $(top_builddir)/html/guestfs-lua.3.html \
+ --verbatim $(srcdir)/create_disk.lua:@EXAMPLE1@ \
+ --verbatim $(srcdir)/inspect_vm.lua:@EXAMPLE2@ \
+ --license examples \
+ $<
+ touch $@
diff --git a/lua/examples/create_disk.lua b/lua/examples/create_disk.lua
new file mode 100644
index 00000000..2dfc1fc2
--- /dev/null
+++ b/lua/examples/create_disk.lua
@@ -0,0 +1,66 @@
+-- Example showing how to create a disk image.
+
+require "guestfs"
+
+output = "disk.img"
+
+g = Guestfs.create ()
+
+-- Create a raw-format sparse disk image, 512 MB in size.
+file = io.open (output, "w")
+file:seek ("set", 512 * 1024 * 1024)
+file:write (' ')
+file:close ()
+
+-- Set the trace flag so that we can see each libguestfs call.
+g:set_trace (true)
+
+-- Attach the disk image to libguestfs.
+g:add_drive (output, { format = "raw", readonly = false })
+
+-- Run the libguestfs back-end.
+g:launch ()
+
+-- Get the list of devices. Because we only added one drive
+-- above, we expect that this list should contain a single
+-- element.
+devices = g:list_devices ()
+if table.getn (devices) ~= 1 then
+ error "expected a single device from list-devices"
+end
+
+-- Partition the disk as one single MBR partition.
+g:part_disk (devices[1], "mbr")
+
+-- Get the list of partitions. We expect a single element, which
+-- is the partition we have just created.
+partitions = g:list_partitions ()
+if table.getn (partitions) ~= 1 then
+ error "expected a single partition from list-partitions"
+end
+
+-- Create a filesystem on the partition.
+g:mkfs ("ext4", partitions[1])
+
+-- Now mount the filesystem so that we can add files.
+g:mount (partitions[1], "/")
+
+-- Create some files and directories.
+g:touch ("/empty")
+message = "Hello, world\n"
+g:write ("/hello", message)
+g:mkdir ("/foo")
+
+-- This one uploads the local file /etc/resolv.conf into
+-- the disk image.
+g:upload ("/etc/resolv.conf", "/foo/resolv.conf")
+
+-- Because we wrote to the disk and we want to detect write
+-- errors, call g:shutdown. You don't need to do this:
+-- g:close will do it implicitly.
+g:shutdown ()
+
+-- 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.
+g:close ()
diff --git a/lua/examples/guestfs-lua.pod b/lua/examples/guestfs-lua.pod
new file mode 100644
index 00000000..33c9b811
--- /dev/null
+++ b/lua/examples/guestfs-lua.pod
@@ -0,0 +1,97 @@
+=encoding utf8
+
+=head1 NAME
+
+guestfs-lua - How to use libguestfs from Lua
+
+=head1 SYNOPSIS
+
+ require "guestfs"
+ g = Guestfs.create ()
+ g:add_drive ("test.img", { format = "raw", readonly = "true" })
+ g:launch ()
+ devices = g:list_devices ()
+ g:close ()
+
+=head1 DESCRIPTION
+
+This manual page documents how to call libguestfs from the Lua
+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
+
+To create a new handle, call:
+
+ g = Guestfs.create ()
+
+You can also use the optional arguments:
+
+ g = Guestfs.create { environment = 0, close_on_exit = 0 }
+
+to set the flags C<GUESTFS_CREATE_NO_ENVIRONMENT>
+and/or C<GUESTFS_CREATE_NO_CLOSE_ON_EXIT>.
+
+The handle will be closed by the garbage collector, but you can
+also close it explicitly by doing:
+
+ g:close ()
+
+=head2 CALLING METHODS
+
+Use the ordinary Lua convention for calling methods on the handle.
+For example:
+
+ g:set_verbose (true)
+
+=head2 FUNCTIONS WITH OPTIONAL ARGUMENTS
+
+For functions that take optional arguments, the first arguments are
+the non-optional ones. The optional final argument is a table
+supplying the optional arguments.
+
+ g:add_drive ("test.img")
+
+or:
+
+ g:add_drive ("test.img", { format = "raw", readonly = "true" })
+
+=head2 64 BIT VALUES
+
+Currently 64 bit values must be passed as strings, and are returned as
+strings. This is because 32 bit Lua cannot handle 64 bit integers
+properly. We hope to come up with a better solution later.
+
+=head2 ERRORS
+
+Errors are converted into exceptions. Use C<pcall> to catch these.
+
+=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) 2012 Red Hat Inc.
diff --git a/lua/examples/inspect_vm.lua b/lua/examples/inspect_vm.lua
new file mode 100644
index 00000000..1bee10b3
--- /dev/null
+++ b/lua/examples/inspect_vm.lua
@@ -0,0 +1,62 @@
+-- Example showing how to inspect a virtual machine disk.
+
+require "guestfs"
+
+if table.getn (arg) == 1 then
+ disk = arg[1]
+else
+ error ("usage: inspect_vm disk.img")
+end
+
+g = Guestfs.create ()
+
+-- Attach the disk image read-only to libguestfs.
+g:add_drive (disk, { -- format:"raw"
+ readonly = true })
+
+-- Run the libguestfs back-end.
+g:launch ()
+
+-- Ask libguestfs to inspect for operating systems.
+roots = g:inspect_os ()
+if table.getn (roots) == 0 then
+ error ("inspect_vm: no operating systems found")
+end
+
+for _, root in ipairs (roots) do
+ print ("Root device: ", root)
+
+ -- Print basic information about the operating system.
+ print (" Product name: ", g:inspect_get_product_name (root))
+ print (" Version: ",
+ g:inspect_get_major_version (root),
+ g:inspect_get_minor_version (root))
+ print (" Type: ", g:inspect_get_type (root))
+ print (" Distro: ", g:inspect_get_distro (root))
+
+ -- Mount up the disks, like guestfish -i.
+ --
+ -- Sort keys by length, shortest first, so that we end up
+ -- mounting the filesystems in the correct order.
+ mps = g:inspect_get_mountpoints (root)
+ table.sort (mps,
+ function (a, b)
+ return string.len (a) < string.len (b)
+ end)
+ for mp,dev in pairs (mps) do
+ pcall (function () g:mount_ro (dev, mp) end)
+ end
+
+ -- If /etc/issue.net file exists, print up to 3 lines.
+ filename = "/etc/issue.net"
+ if g:is_file (filename) then
+ print ("--- ", filename, " ---")
+ lines = g:head_n (3, filename)
+ for _, line in ipairs (lines) do
+ print (line)
+ end
+ end
+
+ -- Unmount everything.
+ g:umount_all ()
+end
diff --git a/lua/tests/010-load.lua b/lua/tests/010-load.lua
new file mode 100755
index 00000000..4443e322
--- /dev/null
+++ b/lua/tests/010-load.lua
@@ -0,0 +1,19 @@
+#!/usr/bin/lua
+-- libguestfs Lua bindings -*- lua -*-
+-- Copyright (C) 2012 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.
+
+require "guestfs"
diff --git a/lua/tests/020-create.lua b/lua/tests/020-create.lua
new file mode 100755
index 00000000..527aa0bc
--- /dev/null
+++ b/lua/tests/020-create.lua
@@ -0,0 +1,21 @@
+#!/usr/bin/lua
+-- libguestfs Lua bindings -*- lua -*-
+-- Copyright (C) 2012 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.
+
+require "guestfs"
+
+local g = Guestfs.create ()
diff --git a/lua/tests/025-create-flags.lua b/lua/tests/025-create-flags.lua
new file mode 100755
index 00000000..881b1834
--- /dev/null
+++ b/lua/tests/025-create-flags.lua
@@ -0,0 +1,21 @@
+#!/usr/bin/lua
+-- libguestfs Lua bindings -*- lua -*-
+-- Copyright (C) 2012 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.
+
+require "guestfs"
+
+local g = Guestfs.create { environment = 0 }
diff --git a/lua/tests/030-config.lua b/lua/tests/030-config.lua
new file mode 100755
index 00000000..a1325584
--- /dev/null
+++ b/lua/tests/030-config.lua
@@ -0,0 +1,42 @@
+#!/usr/bin/lua
+-- libguestfs Lua bindings -*- lua -*-
+-- Copyright (C) 2012 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.
+
+require "guestfs"
+
+local g = Guestfs.create ()
+
+local verbose = g:get_verbose ()
+g:set_verbose (true)
+g:set_verbose (verbose)
+
+g:set_autosync (false)
+g:set_autosync (true)
+
+g:set_path (".")
+if g:get_path () ~= "." then
+ error ()
+end
+
+g:add_drive ("/dev/null")
+
+local version = g:version ()
+for k,v in pairs (version) do
+ print(k,v)
+end
+
+g:close ()
diff --git a/lua/tests/050-lvcreate.lua b/lua/tests/050-lvcreate.lua
new file mode 100755
index 00000000..a9d9920e
--- /dev/null
+++ b/lua/tests/050-lvcreate.lua
@@ -0,0 +1,47 @@
+#!/usr/bin/lua
+-- libguestfs Lua bindings -*- lua -*-
+-- Copyright (C) 2012 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.
+
+require "guestfs"
+
+local g = Guestfs.create ()
+
+file = io.open ("test.img", "w")
+file:seek ("set", 500 * 1024 * 1024)
+file:write (' ')
+file:close ()
+
+g:add_drive ("test.img")
+
+g:launch ()
+
+g:pvcreate ("/dev/sda")
+g:vgcreate ("VG", {"/dev/sda"})
+g:lvcreate ("LV1", "VG", 200)
+g:lvcreate ("LV2", "VG", 200)
+
+local lvs = g:lvs ()
+if table.getn (lvs) ~= 2 or lvs[1] ~= "/dev/VG/LV1" or lvs[2] ~= "/dev/VG/LV2"
+then
+ error ("g:lvs returned incorrect result")
+end
+
+g:shutdown ()
+
+g:close ()
+
+os.remove ("test.img")
diff --git a/lua/tests/060-readdir.lua b/lua/tests/060-readdir.lua
new file mode 100755
index 00000000..dd060840
--- /dev/null
+++ b/lua/tests/060-readdir.lua
@@ -0,0 +1,65 @@
+#!/usr/bin/lua
+-- libguestfs Lua bindings -*- lua -*-
+-- Copyright (C) 2012 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.
+
+require "guestfs"
+
+local g = Guestfs.create ()
+
+file = io.open ("test.img", "w")
+file:seek ("set", 10 * 1024 * 1024)
+file:write (' ')
+file:close ()
+
+g:add_drive ("test.img")
+
+g:launch ()
+
+g:part_disk ("/dev/sda", "mbr")
+g:mkfs ("ext2", "/dev/sda1")
+g:mount ("/dev/sda1", "/")
+g:mkdir ("/p")
+g:touch ("/q")
+
+local dirs = g:readdir ("/")
+
+function print_dirs(dirs)
+ for i,dentry in ipairs (dirs) do
+ for k,v in pairs (dentry) do
+ print(i, k, v)
+ end
+ end
+end
+
+print_dirs (dirs)
+table.sort (dirs, function (a,b) return a["name"] < b["name"] end)
+print_dirs (dirs)
+
+-- Slots 1, 2, 3 contain "." and ".." and "lost+found" respectively.
+
+if (dirs[4]["name"] ~= "p") then
+ error "incorrect name in slot 4"
+end
+if (dirs[5]["name"] ~= "q") then
+ error "incorrect name in slot 5"
+end
+
+g:shutdown ()
+
+g:close ()
+
+os.remove ("test.img")
diff --git a/lua/tests/070-optargs.lua b/lua/tests/070-optargs.lua
new file mode 100755
index 00000000..fe0ec753
--- /dev/null
+++ b/lua/tests/070-optargs.lua
@@ -0,0 +1,25 @@
+#!/usr/bin/lua
+-- libguestfs Lua bindings -*- lua -*-
+-- Copyright (C) 2012 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.
+
+require "guestfs"
+
+local g = Guestfs.create ()
+
+g:add_drive ("/dev/null")
+g:add_drive ("/dev/null", { readonly = true })
+g:add_drive ("/dev/null", { format = "raw", readonly = false })
diff --git a/ocaml/examples/guestfs-ocaml.pod b/ocaml/examples/guestfs-ocaml.pod
index 8a4c94fd..f91d6d2c 100644
--- a/ocaml/examples/guestfs-ocaml.pod
+++ b/ocaml/examples/guestfs-ocaml.pod
@@ -81,6 +81,7 @@ L<guestfs(3)>,
L<guestfs-examples(3)>,
L<guestfs-erlang(3)>,
L<guestfs-java(3)>,
+L<guestfs-lua(3)>,
L<guestfs-perl(3)>,
L<guestfs-python(3)>,
L<guestfs-recipes(1)>,
diff --git a/perl/examples/guestfs-perl.pod b/perl/examples/guestfs-perl.pod
index ab3f0359..95a1b604 100644
--- a/perl/examples/guestfs-perl.pod
+++ b/perl/examples/guestfs-perl.pod
@@ -44,6 +44,7 @@ L<guestfs(3)>,
L<guestfs-examples(3)>,
L<guestfs-erlang(3)>,
L<guestfs-java(3)>,
+L<guestfs-lua(3)>,
L<guestfs-ocaml(3)>,
L<guestfs-python(3)>,
L<guestfs-recipes(1)>,
diff --git a/po-docs/ja/Makefile.am b/po-docs/ja/Makefile.am
index 27432f03..d3413ec1 100644
--- a/po-docs/ja/Makefile.am
+++ b/po-docs/ja/Makefile.am
@@ -30,6 +30,7 @@ MANPAGES = \
guestfs-examples.3 \
guestfs-faq.1 \
guestfs-java.3 \
+ guestfs-lua.3 \
guestfs-ocaml.3 \
guestfs-performance.1 \
guestfs-perl.3 \
diff --git a/po-docs/podfiles b/po-docs/podfiles
index 5e23ea15..74f722d4 100644
--- a/po-docs/podfiles
+++ b/po-docs/podfiles
@@ -24,6 +24,7 @@
../guestfs-release-notes.pod
../inspector/virt-inspector.pod
../java/examples/guestfs-java.pod
+../lua/examples/guestfs-lua.pod
../ocaml/examples/guestfs-ocaml.pod
../perl/examples/guestfs-perl.pod
../python/examples/guestfs-python.pod
diff --git a/po-docs/uk/Makefile.am b/po-docs/uk/Makefile.am
index 27432f03..d3413ec1 100644
--- a/po-docs/uk/Makefile.am
+++ b/po-docs/uk/Makefile.am
@@ -30,6 +30,7 @@ MANPAGES = \
guestfs-examples.3 \
guestfs-faq.1 \
guestfs-java.3 \
+ guestfs-lua.3 \
guestfs-ocaml.3 \
guestfs-performance.1 \
guestfs-perl.3 \
diff --git a/po/POTFILES b/po/POTFILES
index 622c0158..ece787a8 100644
--- a/po/POTFILES
+++ b/po/POTFILES
@@ -203,6 +203,7 @@ gobject/src/struct-xfsinfo.c
gobject/src/tristate.c
inspector/virt-inspector.c
java/com_redhat_et_libguestfs_GuestFS.c
+lua/lua-guestfs.c
ocaml/guestfs-c-actions.c
ocaml/guestfs-c.c
perl/Guestfs.c
diff --git a/python/examples/guestfs-python.pod b/python/examples/guestfs-python.pod
index 52010155..b17cf078 100644
--- a/python/examples/guestfs-python.pod
+++ b/python/examples/guestfs-python.pod
@@ -45,6 +45,7 @@ L<guestfs(3)>,
L<guestfs-examples(3)>,
L<guestfs-erlang(3)>,
L<guestfs-java(3)>,
+L<guestfs-lua(3)>,
L<guestfs-ocaml(3)>,
L<guestfs-perl(3)>,
L<guestfs-recipes(1)>,
diff --git a/ruby/examples/guestfs-ruby.pod b/ruby/examples/guestfs-ruby.pod
index e3b86481..82b1325a 100644
--- a/ruby/examples/guestfs-ruby.pod
+++ b/ruby/examples/guestfs-ruby.pod
@@ -39,6 +39,7 @@ L<guestfs(3)>,
L<guestfs-examples(3)>,
L<guestfs-erlang(3)>,
L<guestfs-java(3)>,
+L<guestfs-lua(3)>,
L<guestfs-ocaml(3)>,
L<guestfs-perl(3)>,
L<guestfs-python(3)>,
diff --git a/run.in b/run.in
index f0357b2e..06f1d5aa 100755
--- a/run.in
+++ b/run.in
@@ -93,6 +93,14 @@ export CAML_LD_LIBRARY_PATH="$b/ocaml"
export JAVA_EXE=@JAVA_EXE@
export CLASSPATH="$b/java:$b/java/t:$b/java/libguestfs-@VERSION@.jar"
+# For Lua.
+if [ -z "$LUA_CPATH" ]; then
+ LUA_CPATH="$b/lua/?.so"
+else
+ LUA_CPATH="$b/lua/?.so;$LUA_CPATH"
+fi
+export LUA_CPATH
+
# For GObject, Javascript and friends.
export GJS=@GJS@
if [ -z "$GI_TYPELIB_PATH" ]; then
diff --git a/src/guestfs.pod b/src/guestfs.pod
index 1e1fe416..cc6b27b8 100644
--- a/src/guestfs.pod
+++ b/src/guestfs.pod
@@ -880,6 +880,10 @@ and we are looking for help to complete this binding.
Full documentation is contained in the Javadoc which is distributed
with libguestfs. For examples, see L<guestfs-java(3)>.
+=item B<Lua>
+
+See L<guestfs-lua(3)>.
+
=item B<OCaml>
See L<guestfs-ocaml(3)>.
@@ -3751,6 +3755,8 @@ Command line tools written in Perl (L<virt-win-reg(1)> and many others).
=item C<java>
+=item C<lua>
+
=item C<ocaml>
=item C<php>
@@ -4027,6 +4033,7 @@ See L</LIBGUESTFS_CACHEDIR>, L</LIBGUESTFS_TMPDIR>.
L<guestfs-examples(3)>,
L<guestfs-erlang(3)>,
L<guestfs-java(3)>,
+L<guestfs-lua(3)>,
L<guestfs-ocaml(3)>,
L<guestfs-perl(3)>,
L<guestfs-python(3)>,