diff options
author | Richard W.M. Jones <rjones@redhat.com> | 2012-11-19 13:00:52 +0000 |
---|---|---|
committer | Richard W.M. Jones <rjones@redhat.com> | 2012-11-19 14:01:40 +0000 |
commit | f77ddb9e114e560724d6548499047ae6894ab59c (patch) | |
tree | 6341c27ca32fb5e1262f1263974991a1d6357b22 | |
parent | d14557d434de5cb1c9754ae0f3e8e820e0a46694 (diff) | |
download | libguestfs-f77ddb9e114e560724d6548499047ae6894ab59c.tar.gz libguestfs-f77ddb9e114e560724d6548499047ae6894ab59c.tar.xz libguestfs-f77ddb9e114e560724d6548499047ae6894ab59c.zip |
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'
-rw-r--r-- | generator/lua.ml | 326 | ||||
-rw-r--r-- | lua/Makefile.am | 10 | ||||
-rw-r--r-- | lua/examples/guestfs-lua.pod | 27 | ||||
-rwxr-xr-x | lua/tests/027-create-multiple.lua | 15 | ||||
-rwxr-xr-x | lua/tests/030-config.lua | 4 | ||||
-rwxr-xr-x | lua/tests/050-lvcreate.lua | 7 | ||||
-rwxr-xr-x | lua/tests/060-readdir.lua | 8 | ||||
-rwxr-xr-x | lua/tests/400-events.lua | 49 | ||||
-rwxr-xr-x | lua/tests/400-progress.lua | 44 |
9 files changed, 435 insertions, 55 deletions
diff --git a/generator/lua.ml b/generator/lua.ml index 874681c3..fbb4e93a 100644 --- a/generator/lua.ml +++ b/generator/lua.ml @@ -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 }, "; diff --git a/lua/Makefile.am b/lua/Makefile.am index e2daceee..7fff50b8 100644 --- a/lua/Makefile.am +++ b/lua/Makefile.am @@ -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: diff --git a/lua/examples/guestfs-lua.pod b/lua/examples/guestfs-lua.pod index c8afb887..83900cae 100644 --- a/lua/examples/guestfs-lua.pod +++ b/lua/examples/guestfs-lua.pod @@ -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@ diff --git a/lua/tests/027-create-multiple.lua b/lua/tests/027-create-multiple.lua index 30ce6155..bd6cae56 100755 --- a/lua/tests/027-create-multiple.lua +++ b/lua/tests/027-create-multiple.lua @@ -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'") diff --git a/lua/tests/030-config.lua b/lua/tests/030-config.lua index a1325584..53e47fc8 100755 --- a/lua/tests/030-config.lua +++ b/lua/tests/030-config.lua @@ -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") diff --git a/lua/tests/050-lvcreate.lua b/lua/tests/050-lvcreate.lua index a9d9920e..3bd95c23 100755 --- a/lua/tests/050-lvcreate.lua +++ b/lua/tests/050-lvcreate.lua @@ -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 () diff --git a/lua/tests/060-readdir.lua b/lua/tests/060-readdir.lua index dd060840..07e8e3ba 100755 --- a/lua/tests/060-readdir.lua +++ b/lua/tests/060-readdir.lua @@ -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 () diff --git a/lua/tests/400-events.lua b/lua/tests/400-events.lua new file mode 100755 index 00000000..c29cc62d --- /dev/null +++ b/lua/tests/400-events.lua @@ -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") diff --git a/lua/tests/400-progress.lua b/lua/tests/400-progress.lua new file mode 100755 index 00000000..e0e17ac8 --- /dev/null +++ b/lua/tests/400-progress.lua @@ -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 () |