lua: Various fixes and enhancements:

- add support for events (with test)
- test progress messages
- update documentation to describe events
- refactor handle closing code
- refactor error code
- use 'assert' in test code instead of 'if ... then error end'
This commit is contained in:
Richard W.M. Jones
2012-11-19 13:00:52 +00:00
parent d14557d434
commit f77ddb9e11
9 changed files with 435 additions and 55 deletions

View File

@@ -53,19 +53,41 @@ let generate_lua_c () =
/* 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.
* lua_guestfs_finalizer.
*
* There is also an entry in the Lua registry, indexed by 'g'
* (allocated on demand) which stores per-handle Lua data. See
* functions 'get_per_handle_table', 'free_per_handle_table'.
*/
struct userdata {
guestfs_h *g; /* Libguestfs handle, NULL if closed. */
struct event_state *es;
};
/* Structure passed to event_callback_wrapper. */
struct event_state {
struct event_state *next; /* Stored in a linked list. */
lua_State *L;
struct userdata *u;
int ref; /* Reference to closure. */
};
static struct userdata *get_handle (lua_State *L, int index);
static void get_per_handle_table (lua_State *L, guestfs_h *g);
static void free_per_handle_table (lua_State *L, guestfs_h *g);
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);
static void push_int64_array (lua_State *L, const int64_t *array, size_t len);
static void event_callback_wrapper (guestfs_h *g, void *esvp, uint64_t event, int eh, int flags, const char *buf, size_t buf_len, const uint64_t *array, size_t array_len);
static uint64_t get_event (lua_State *L, int index);
static uint64_t get_event_bitmask (lua_State *L, int index);
static void push_event (lua_State *L, uint64_t event);
";
@@ -80,6 +102,10 @@ static void push_int64 (lua_State *L, int64_t i64);
pr "\
/* On the stack at 'index' should be a table. Check if 'name' (string)
* is a key in this table, and if so execute 'code'. While 'code' is
* executing, the top of stack (ie. index == -1) is the value of 'name'.
*/
#define OPTARG_IF_SET(index, name, code) \\
do { \\
lua_pushliteral (L, name); \\
@@ -123,18 +149,32 @@ lua_guestfs_create (lua_State *L)
lua_setmetatable (L, -2);
u->g = g;
u->es = NULL;
return 1;
}
static void
close_handle (lua_State *L, guestfs_h *g)
{
guestfs_close (g);
free_per_handle_table (L, g);
}
/* Finalizer. */
static int
lua_guestfs_finalizer (lua_State *L)
{
struct userdata *u = get_handle (L, 1);
struct event_state *es, *es_next;
if (u->g)
guestfs_close (u->g);
close_handle (L, u->g);
for (es = u->es; es != NULL; es = es_next) {
es_next = es->next;
free (es);
}
/* u will be freed by Lua when we return. */
@@ -148,13 +188,67 @@ lua_guestfs_close (lua_State *L)
struct userdata *u = get_handle (L, 1);
if (u->g) {
guestfs_close (u->g);
close_handle (L, u->g);
u->g = NULL;
}
return 0;
}
/* Return the last error in the handle. */
static int
last_error (lua_State *L, guestfs_h *g)
{
/* Construct an error object on the stack containing 'msg'
* and 'code' fields.
*/
lua_newtable (L);
lua_pushliteral (L, \"msg\");
lua_pushstring (L, guestfs_last_error (g));
lua_settable (L, -3);
lua_pushliteral (L, \"code\");
lua_pushinteger (L, guestfs_last_errno (g));
lua_settable (L, -3);
/* Raise an exception with the error object. */
return lua_error (L);
}
/* Push the per-handle Lua table onto the stack. This is stored
* in the global Lua registry. It is allocated on demand the first
* time you call this function. Use luaL_ref to allocate new
* entries in this table.
* See also: http://www.lua.org/pil/27.3.1.html
*/
static void
get_per_handle_table (lua_State *L, guestfs_h *g)
{
again:
lua_pushlightuserdata (L, g);
lua_gettable (L, LUA_REGISTRYINDEX);
if (lua_isnil (L, -1)) {
lua_pop (L, 1);
/* registry[g] = {} */
lua_pushlightuserdata (L, g);
lua_newtable (L);
lua_settable (L, LUA_REGISTRYINDEX);
goto again;
}
}
/* Free the per-handle Lua table. It doesn't literally \"free\"
* anything since the GC will do that. It just removes the entry
* from the global registry.
*/
static void
free_per_handle_table (lua_State *L, guestfs_h *g)
{
/* registry[g] = nil */
lua_pushlightuserdata (L, g);
lua_pushnil (L);
lua_settable (L, LUA_REGISTRYINDEX);
}
/* User cancel. */
static int
lua_guestfs_user_cancel (lua_State *L)
@@ -167,8 +261,129 @@ lua_guestfs_user_cancel (lua_State *L)
return 0;
}
/* Set an event callback. */
static int
lua_guestfs_set_event_callback (lua_State *L)
{
struct userdata *u = get_handle (L, 1);
guestfs_h *g = u->g;
uint64_t event_bitmask;
int eh;
int ref;
struct event_state *es;
if (g == NULL)
return luaL_error (L, \"Guestfs.%%s: handle is closed\",
\"set_event_callback\");
event_bitmask = get_event_bitmask (L, 3);
/* Save the function in the per-handle table, so that the GC doesn't
* clean it up before the event fires.
*/
luaL_checktype (L, 2, LUA_TFUNCTION);
get_per_handle_table (L, g);
lua_pushvalue (L, 2);
ref = luaL_ref (L, -2);
lua_pop (L, 1);
es = malloc (sizeof *es);
if (!es)
return luaL_error (L, \"failed to allocate event_state\");
es->next = u->es;
es->L = L;
es->u = u;
es->ref = ref;
u->es = es;
eh = guestfs_set_event_callback (g, event_callback_wrapper,
event_bitmask, 0, es);
if (eh == -1)
return last_error (L, g);
/* Return the event handle. */
lua_pushinteger (L, eh);
return 1;
}
static void
event_callback_wrapper (guestfs_h *g,
void *esvp,
uint64_t event,
int eh,
int flags,
const char *buf, size_t buf_len,
const uint64_t *array, size_t array_len)
{
struct event_state *es = esvp;
lua_State *L = es->L;
struct userdata *u = es->u;
/* Look up the closure to call in the per-handle table. */
get_per_handle_table (L, g);
lua_rawgeti (L, -1, es->ref);
if (!lua_isfunction (L, -1)) {
fprintf (stderr, \"lua-guestfs: %%s: internal error: no closure found for g = %%p, eh = %%d\\n\",
__func__, g, eh);
goto out;
}
/* Call the event handler: event_handler (g, event, eh, flags, buf, array) */
lua_pushlightuserdata (L, u); /* XXX correct? */
push_event (L, event);
lua_pushinteger (L, eh);
lua_pushinteger (L, flags);
lua_pushlstring (L, buf, buf_len);
push_int64_array (L, (const int64_t *) array, array_len);
switch (lua_pcall (L, 6, 0, 0)) {
case 0: /* call ok - do nothing */
break;
case LUA_ERRRUN:
fprintf (stderr, \"lua-guestfs: %%s: unexpected error in event handler\\n\",
__func__);
/* XXX could print the error instead of throwing it away */
lua_pop (L, 1);
break;
case LUA_ERRERR: /* can probably never happen */
fprintf (stderr, \"lua-guestfs: %%s: error calling error handler\\n\",
__func__);
break;
case LUA_ERRMEM:
fprintf (stderr, \"lua-guestfs: %%s: memory allocation failed\\n\", __func__);
break;
default:
fprintf (stderr, \"lua-guestfs: %%s: unknown error\\n\", __func__);
}
/* Pop the per-handle table. */
out:
lua_pop (L, 1);
}
/* Delete an event callback. */
static int
lua_guestfs_delete_event_callback (lua_State *L)
{
struct userdata *u = get_handle (L, 1);
guestfs_h *g = u->g;
int eh;
if (g == NULL)
return luaL_error (L, \"Guestfs.%%s: handle is closed\",
\"delete_event_callback\");
eh = luaL_checkint (L, 2);
guestfs_delete_event_callback (g, eh);
return 0;
}
";
(* Actions. *)
List.iter (
fun { name = name; style = (ret, args, optargs as style);
c_function = c_function; c_optarg_prefix = c_optarg_prefix } ->
@@ -249,7 +464,7 @@ lua_guestfs_user_cancel (lua_State *L)
| Bool n ->
pr " %s = lua_toboolean (L, %d);\n" n i
| Int n ->
pr " %s = lua_tointeger (L, %d);\n" n i
pr " %s = luaL_checkint (L, %d);\n" n i
| Int64 n ->
pr " %s = get_int64 (L, %d);\n" n i
| Pointer (t, n) -> assert false
@@ -274,7 +489,7 @@ lua_guestfs_user_cancel (lua_State *L)
| OBool n ->
pr " optargs_s.%s = lua_toboolean (L, -1);\n" n
| OInt n ->
pr " optargs_s.%s = lua_tointeger (L, -1);\n" n
pr " optargs_s.%s = luaL_checkint (L, -1);\n" n
| OInt64 n ->
pr " optargs_s.%s = get_int64 (L, -1);\n" n
| OString n ->
@@ -313,28 +528,15 @@ lua_guestfs_user_cancel (lua_State *L)
) optargs;
(* Handle errors. *)
let raise_error () =
pr " lua_newtable (L);\n";
pr " lua_pushliteral (L, \"msg\");\n";
pr " lua_pushstring (L, guestfs_last_error (g));\n";
pr " lua_settable (L, -3);\n";
pr " lua_pushliteral (L, \"code\");\n";
pr " lua_pushinteger (L, guestfs_last_errno (g));\n";
pr " lua_settable (L, -3);\n";
pr " return lua_error (L);\n"
in
(match errcode_of_ret ret with
| `CannotReturnError -> ()
| `ErrorIsMinusOne ->
pr " if (r == -1) {\n";
raise_error ();
pr " }\n";
pr " if (r == -1)\n";
pr " return last_error (L, g);\n";
pr "\n"
| `ErrorIsNULL ->
pr " if (r == NULL) {\n";
raise_error ();
pr " }\n";
pr " if (r == NULL)\n";
pr " return last_error (L, g);\n";
pr "\n"
);
@@ -415,9 +617,8 @@ push_string_list (lua_State *L, char **strs)
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);
lua_rawseti (L, -2, i+1 /* because of base 1 arrays */);
}
}
@@ -459,8 +660,78 @@ push_int64 (lua_State *L, int64_t i64)
lua_pushstring (L, s);
}
static void
push_int64_array (lua_State *L, const int64_t *array, size_t len)
{
size_t i;
lua_newtable (L);
for (i = 0; i < len; ++i) {
push_int64 (L, array[i]);
lua_rawseti (L, -2, i+1 /* because of base 1 arrays */);
}
}
";
(* Code to handle events. *)
pr "\
static uint64_t
get_event_bitmask (lua_State *L, int index)
{
uint64_t bitmask;
if (lua_isstring (L, index))
return get_event (L, index);
bitmask = 0;
lua_pushnil (L);
while (lua_next (L, index) != 0) {
bitmask |= get_event (L, -1);
lua_pop (L, 1); /* pop value */
}
lua_pop (L, 1); /* pop key */
return bitmask;
}
static uint64_t
get_event (lua_State *L, int index)
{
const char *s;
s = luaL_checkstring (L, index);
";
List.iter (
fun (event, i) ->
pr " if (strcmp (s, \"%s\") == 0)\n" event;
pr " return %d;\n" i
) events;
pr " return luaL_error (L, \"unknown event name '%%s'\", s);
}
static void
push_event (lua_State *L, uint64_t event)
{
";
List.iter (
fun (event, i) ->
pr " if (event == %d) {\n" i;
pr " lua_pushliteral (L, \"%s\");\n" event;
pr " return;\n";
pr " }\n";
) events;
pr " abort (); /* should never happen */
}
";
(* Code to push structs. *)
let generate_push_struct typ =
pr "static void\n";
pr "push_%s (lua_State *L, struct guestfs_%s *v)\n" typ typ;
@@ -501,9 +772,8 @@ push_int64 (lua_State *L, int64_t i64)
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 " lua_rawseti (L, -2, i+1 /* because of base 1 arrays */);\n";
pr " }\n";
pr "}\n";
pr "\n"
@@ -525,6 +795,8 @@ static luaL_Reg handle_methods[] = {
{ \"create\", lua_guestfs_create },
{ \"close\", lua_guestfs_close },
{ \"user_cancel\", lua_guestfs_user_cancel },
{ \"set_event_callback\", lua_guestfs_set_event_callback },
{ \"delete_event_callback\", lua_guestfs_delete_event_callback },
";

View File

@@ -55,12 +55,14 @@ TESTS = \
tests/025-create-flags.lua \
tests/027-create-multiple.lua \
tests/030-config.lua \
tests/070-optargs.lua
tests/070-optargs.lua \
tests/400-events.lua
if ENABLE_APPLIANCE
TESTS += \
tests/050-lvcreate.lua \
tests/060-readdir.lua
tests/060-readdir.lua \
tests/400-progress.lua
endif
EXTRA_DIST += \
@@ -71,7 +73,9 @@ EXTRA_DIST += \
tests/030-config.lua \
tests/050-lvcreate.lua \
tests/060-readdir.lua \
tests/070-optargs.lua
tests/070-optargs.lua \
tests/400-events.lua \
tests/400-progress.lua
# Custom install rule.
install-data-hook:

View File

@@ -83,6 +83,33 @@ The C<errno> (corresponding to L<guestfs(3)/guestfs_last_errno>).
Note that some errors can also be thrown as plain strings. You
need to check the type.
=head2 EVENTS
Events can be registered by calling C<set_event_callback>:
eh = g:set_event_callback (cb, "close")
or to register a single callback for multiple events make the
second argument a list:
eh = g:set_event_callback (cb, { "appliance", "library", "trace" })
The callback (C<cb>) is called with the following parameters:
function cb (g, event, eh, flags, buf, array)
-- g is the guestfs handle
-- event is a string which is the name of the event that fired
-- flags is always zero
-- buf is the data buffer (eg. log message etc)
-- array is the array of 64 bit ints (eg. progress bar status etc)
...
end
You can also remove a callback using the event handle (C<eh>) that was
returned when you registered the callback:
g:delete_event_callback (eh)
=head1 EXAMPLE 1: CREATE A DISK IMAGE
@EXAMPLE1@

View File

@@ -27,15 +27,6 @@ g1:set_path ("1")
g2:set_path ("2")
g3:set_path ("3")
if g1:get_path () ~= "1" then
error (string.format ("incorrect path in g1, expected '1', got '%s'",
g1:get_path ()))
end
if g2:get_path () ~= "2" then
error (string.format ("incorrect path in g2, expected '2', got '%s'",
g2:get_path ()))
end
if g3:get_path () ~= "3" then
error (string.format ("incorrect path in g3, expected '3', got '%s'",
g3:get_path ()))
end
assert (g1:get_path () == "1", "incorrect path in g1, expected '1'")
assert (g2:get_path () == "2", "incorrect path in g2, expected '2'")
assert (g3:get_path () == "3", "incorrect path in g3, expected '3'")

View File

@@ -28,9 +28,7 @@ g:set_autosync (false)
g:set_autosync (true)
g:set_path (".")
if g:get_path () ~= "." then
error ()
end
assert (g:get_path () == ".")
g:add_drive ("/dev/null")

View File

@@ -35,10 +35,9 @@ 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
assert (table.getn (lvs) == 2 and
lvs[1] == "/dev/VG/LV1" and lvs[2] == "/dev/VG/LV2",
"g:lvs returned incorrect result")
g:shutdown ()

View File

@@ -51,12 +51,8 @@ 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
assert (dirs[4]["name"] == "p", "incorrect name in slot 4")
assert (dirs[5]["name"] == "q", "incorrect name in slot 5")
g:shutdown ()

49
lua/tests/400-events.lua Executable file
View File

@@ -0,0 +1,49 @@
#!/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"
g = Guestfs.create ()
function log_callback (g, event, eh, flags, buf, array)
io.write (string.format ("lua event logged: event=%s eh=%d buf='%s'\n",
event, eh, buf))
end
close_invoked = 0
function close_callback (g, event, eh, flags, buf, array)
close_invoked = close_invoked+1
log_callback (g, event, eh, flags, buf, array)
end
-- Register an event callback for all log messages.
g:set_event_callback (log_callback, { "appliance", "library", "trace" })
-- Register an event callback for the close event.
g:set_event_callback (close_callback, "close")
-- Make sure we see some messages.
g:set_trace (true)
g:set_verbose (true)
-- Do some stuff.
g:add_drive_ro ("/dev/null")
-- Close the handle. The close callback should be invoked.
g:close ()
assert (close_invoked == 1, "close callback was not invoked")

44
lua/tests/400-progress.lua Executable file
View File

@@ -0,0 +1,44 @@
#!/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"
g = Guestfs.create ()
g:add_drive ("/dev/null")
g:launch ()
calls = 0
function cb ()
calls = calls+1
end
eh = g:set_event_callback (cb, "progress")
assert (g:debug ("progress", {"5"}) == "ok", "debug progress command failed")
assert (calls > 0, "progress callback was not invoked")
calls = 0
g:delete_event_callback (eh)
assert (g:debug ("progress", {"5"}) == "ok", "debug progress command failed")
assert (calls == 0, "progress callback was invoked when deleted")
g:set_event_callback (cb, "progress")
assert (g:debug ("progress", {"5"}) == "ok", "debug progress command failed")
assert (calls > 0, "progress callback was not invoked")
g:close ()