fish: Allow events to be processed in guestfish.

Add 'event', 'list-events' and 'delete-event' commands so that event
handlers can be registered, listed and deleted in guestfish.  The
event handler is a shell script snippet or host command.

Cc: Pádraig Brady <P@draigBrady.com>
This commit is contained in:
Richard W.M. Jones
2011-12-14 08:38:27 +00:00
parent 3c9dfd1e95
commit 7123f0cab1
13 changed files with 530 additions and 4 deletions

1
.gitignore vendored
View File

@@ -99,6 +99,7 @@ fish/cmds.c
fish/cmds_gperf.c
fish/cmds_gperf.gperf
fish/completion.c
fish/event-names.c
fish/fish-cmds.h
fish/guestfish
fish/guestfish.1

View File

@@ -30,6 +30,7 @@ generator_built = \
cmds.c \
cmds_gperf.gperf \
completion.c \
event-names.c \
fish-cmds.h \
guestfish-actions.pod \
guestfish-commands.pod \
@@ -81,6 +82,7 @@ guestfish_SOURCES = \
display.c \
echo.c \
edit.c \
events.c \
fish.c \
fish.h \
glob.c \

272
fish/events.c Normal file
View File

@@ -0,0 +1,272 @@
/* guestfish - the filesystem interactive shell
* 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 <config.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <inttypes.h>
#include <assert.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <guestfs.h>
#include "hash.h"
#include "hash-pjw.h"
#include "fish.h"
/* The hash table maps names to multiple (linked list of) event handlers. */
static Hash_table *event_handlers;
struct entry {
struct entry *next; /* Next entry in linked list. */
char *name; /* Event name. */
char *command; /* Shell script / command that runs. */
uint64_t event_bitmask; /* Events this is registered for. */
int eh; /* Event handle (from guestfs_set_event_callback). */
};
static void
entry_free (void *x)
{
if (x) {
struct entry *p = x;
entry_free (p->next);
free (p->name);
free (p->command);
free (p);
}
}
static size_t
entry_hash (void const *x, size_t table_size)
{
struct entry const *p = x;
return hash_pjw (p->name, table_size);
}
static bool
entry_compare (void const *x, void const *y)
{
struct entry const *p = x;
struct entry const *q = y;
return STREQ (p->name, q->name);
}
void
init_event_handlers (void)
{
assert (event_handlers == NULL);
event_handlers =
hash_initialize (64, NULL, entry_hash, entry_compare, entry_free);
}
void
free_event_handlers (void)
{
assert (event_handlers != NULL);
hash_free (event_handlers);
event_handlers = NULL;
}
static void
do_event_handler (guestfs_h *g,
void *opaque,
uint64_t event,
int event_handle,
int flags,
const char *buf, size_t buf_len,
const uint64_t *array, size_t array_len)
{
pid_t pid;
const char *argv[8 + array_len];
const char *shell;
struct entry *entry = opaque;
size_t i, j;
char *s;
pid = fork ();
if (pid == -1) {
perror ("event handler: fork");
return;
}
if (pid == 0) { /* Child process. */
shell = getenv ("SHELL");
if (!shell)
shell = "/bin/sh";
setenv ("EVENT", event_name_of_event_bitmask (event), 1);
/* Construct the command and arguments. */
i = 0;
argv[i++] = shell;
argv[i++] = "-c";
argv[i++] = entry->command;
argv[i++] = ""; /* $0 */
if (buf != NULL)
/* XXX: So far, buf is always ASCII NUL-terminated. There is no
* way to pass arbitrary 8 bit buffers.
*/
argv[i++] = buf;
for (j = 0; j < array_len; ++j) {
if (asprintf (&s, "%" PRIu64, array[j]) == -1) {
perror ("event handler: asprintf");
_exit (EXIT_FAILURE);
}
argv[i++] = s;
}
argv[i++] = NULL;
execvp (argv[0], (void *) argv);
perror (argv[0]);
_exit (EXIT_FAILURE);
}
if (waitpid (pid, NULL, 0) == -1)
perror ("event handler: waitpid");
}
int
run_event (const char *cmd, size_t argc, char *argv[])
{
int r;
struct entry *entry = NULL, *old_entry;
if (argc != 3) {
fprintf (stderr,
_("use 'event <name> <eventset> <script>' to register an event handler\n"));
goto error;
}
entry = calloc (1, sizeof *entry);
if (entry == NULL) {
perror ("calloc");
goto error;
}
entry->eh = -1;
r = event_bitmask_of_event_set (argv[1], &entry->event_bitmask);
if (r == -1)
goto error;
entry->name = strdup (argv[0]);
if (entry->name == NULL) {
perror ("strdup");
goto error;
}
entry->command = strdup (argv[2]);
if (entry->command == NULL) {
perror ("strdup");
goto error;
}
entry->eh =
guestfs_set_event_callback (g, do_event_handler,
entry->event_bitmask, 0, entry);
if (entry->eh == -1)
goto error;
r = hash_insert_if_absent (event_handlers, entry, (const void **) &old_entry);
if (r == -1)
goto error;
if (r == 0) { /* old_entry set to existing entry */
entry->next = old_entry->next;
/* XXX are we allowed to update the old entry? */
old_entry->next = entry;
}
return 0;
error:
if (entry) {
if (entry->eh >= 0)
guestfs_delete_event_callback (g, entry->eh);
free (entry->name);
free (entry->command);
free (entry);
}
return -1;
}
int
run_delete_event (const char *cmd, size_t argc, char *argv[])
{
if (argc != 1) {
fprintf (stderr,
_("use 'delete-event <name>' to delete an event handler\n"));
return -1;
}
const struct entry key = { .name = bad_cast (argv[0]) };
struct entry *entry, *p;
entry = hash_delete (event_handlers, &key);
if (!entry) {
fprintf (stderr, _("delete-event: %s: no such event handler\n"), argv[0]);
return -1;
}
/* Delete them from the handle. */
p = entry;
while (p) {
guestfs_delete_event_callback (g, p->eh);
p = p->next;
}
/* Free the structures. */
entry_free (entry);
return 0;
}
static bool
list_event (void *x, void *data)
{
struct entry *entry = x;
while (entry) {
printf ("\"%s\" (%d): ", entry->name, entry->eh);
print_event_set (entry->event_bitmask, stdout);
printf (": %s\n", entry->command);
entry = entry->next;
}
return 1;
}
int
run_list_events (const char *cmd, size_t argc, char *argv[])
{
if (argc != 0) {
fprintf (stderr,
_("use 'list-events' to list event handlers\n"));
return -1;
}
hash_do_for_each (event_handlers, list_event, NULL);
return 0;
}

View File

@@ -208,6 +208,7 @@ main (int argc, char *argv[])
int next_prepared_drive = 1;
initialize_readline ();
init_event_handlers ();
memset (&sa, 0, sizeof sa);
sa.sa_handler = SIG_IGN;
@@ -548,6 +549,7 @@ main (int argc, char *argv[])
progress_bar_free (bar);
guestfs_close (g);
free_event_handlers ();
exit (EXIT_SUCCESS);
}

View File

@@ -94,6 +94,15 @@ extern char **do_completion (const char *text, int start, int end);
extern int complete_dest_paths;
extern char *complete_dest_paths_generator (const char *text, int state);
/* in events.c */
extern void init_event_handlers (void);
extern void free_event_handlers (void);
/* in event-names.c (auto-generated) */
extern const char *event_name_of_event_bitmask (uint64_t);
extern void print_event_set (uint64_t, FILE *);
extern int event_bitmask_of_event_set (const char *arg, uint64_t *);
/* in alloc.c */
extern int alloc_disk (const char *filename, const char *size,
int add, int sparse);

View File

@@ -83,5 +83,12 @@ run_reopen (const char *cmd, size_t argc, char *argv[])
guestfs_close (g);
g = g2;
/* We don't bother copying event handlers over to the new handle,
* but we have to reset the list because they were registered
* against the old handle.
*/
free_event_handlers ();
init_event_handlers ();
return 0;
}

View File

@@ -62,12 +62,12 @@ generator_capitests.cmx: generator_utils.cmx generator_types.cmx \
generator_docstrings.cmx generator_actions.cmx
generator_fish.cmo: generator_utils.cmi generator_types.cmo \
generator_structs.cmi generator_prepopts.cmi generator_pr.cmi \
generator_optgroups.cmo generator_docstrings.cmo generator_c.cmo \
generator_actions.cmi
generator_optgroups.cmo generator_events.cmo generator_docstrings.cmo \
generator_c.cmo generator_actions.cmi
generator_fish.cmx: generator_utils.cmx generator_types.cmx \
generator_structs.cmx generator_prepopts.cmx generator_pr.cmx \
generator_optgroups.cmx generator_docstrings.cmx generator_c.cmx \
generator_actions.cmx
generator_optgroups.cmx generator_events.cmx generator_docstrings.cmx \
generator_c.cmx generator_actions.cmx
generator_ocaml.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 \

View File

@@ -6675,6 +6675,16 @@ them with the help of L</glob> like this:
glob copy-out /home/* .");
("delete_event", (RErr,[], []), -1, [], [],
"delete a previously registered event handler",
" delete-event name
Delete the event handler which was previously registered as C<name>.
If multiple event handlers were registered with the same name, they
are all deleted.
See also the guestfish commands C<event> and C<list-events>.");
("display", (RErr,[], []), -1, [], [],
"display an image",
" display filename
@@ -6706,6 +6716,39 @@ The editor is C<$EDITOR>. However if you use the alternate
commands C<vi> or C<emacs> you will get those corresponding
editors.");
("event", (RErr,[], []), -1, [], [],
"register a handler for an event or events",
" event name eventset \"shell script ...\"
Register a shell script fragment which is executed when an
event is raised. See L<guestfs(3)/guestfs_set_event_callback>
for a discussion of the event API in libguestfs.
The C<name> parameter is a name that you give to this event
handler. It can be any string (even the empty string) and is
simply there so you can delete the handler using the guestfish
C<delete-event> command.
The C<eventset> parameter is a comma-separated list of one
or more events, for example C<close> or C<close,trace>. The
special value C<*> means all events.
The third and final parameter is the shell script fragment
(or any external command) that is executed when any of the
events in the eventset occurs. It is executed using
C<$SHELL -c>, or if C<$SHELL> is not set then C</bin/sh -c>.
The shell script fragment receives callback parameters as
arguments C<$1>, C<$2> etc. The actual event that was
called is available in the environment variable C<$EVENT>.
event \"\" close \"echo closed\"
event messages appliance,library,trace \"echo $@\"
event \"\" progress \"echo progress: $3/$4\"
event \"\" * \"echo $EVENT $@\"
See also the guestfish commands C<delete-event> and C<list-events>.");
("glob", (RErr,[], []), -1, [], [],
"expand wildcards in command",
" glob command args...
@@ -6760,6 +6803,13 @@ itself.
Note that C<!cd> won't do what you might expect.");
("list_events", (RErr,[], []), -1, [], [],
"list event handlers",
" list-events
List the event handlers registered using the guestfish
C<event> command.");
("man", (RErr,[], []), -1, [FishAlias "manual"], [],
"open the manual",
" man

View File

@@ -29,6 +29,7 @@ open Generator_actions
open Generator_structs
open Generator_prepopts
open Generator_c
open Generator_events
let doc_opttype_of = function
| Bool n -> "true|false"
@@ -971,3 +972,93 @@ and generate_fish_prep_options_c () =
name name;
) prepopts;
pr "};\n"
and generate_fish_event_names () =
generate_header CStyle GPLv2plus;
pr "\
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include \"fish.h\"
const char *
event_name_of_event_bitmask (uint64_t ev)
{
switch (ev) {
";
List.iter (
fun (name, _) ->
pr " case GUESTFS_EVENT_%s:\n" (String.uppercase name);
pr " return \"%s\";\n" name
) events;
pr " default:
abort (); /* should not happen */
}
}
void
print_event_set (uint64_t event_bitmask, FILE *fp)
{
int comma = 0;
if (event_bitmask == GUESTFS_EVENT_ALL) {
fputs (\"*\", fp);
return;
}
";
List.iter (
fun (name, _) ->
pr " if (event_bitmask & GUESTFS_EVENT_%s) {\n" (String.uppercase name);
pr " if (comma) fputc (',', fp);\n";
pr " comma = 1;\n";
pr " fputs (\"%s\", fp);\n" name;
pr " }\n"
) events;
pr "\
}
int
event_bitmask_of_event_set (const char *arg, uint64_t *eventset_r)
{
size_t n;
if (STREQ (arg, \"*\")) {
*eventset_r = GUESTFS_EVENT_ALL;
return 0;
}
*eventset_r = 0;
while (*arg) {
n = strcspn (arg, \",\");
";
List.iter (
fun (name, _) ->
pr "if (STREQLEN (arg, \"%s\", n))\n" name;
pr " *eventset_r |= GUESTFS_EVENT_%s;\n" (String.uppercase name);
pr " else ";
) events;
pr "\
{
fprintf (stderr, _(\"unknown event name: %%s\\n\"), arg);
return -1;
}
arg += n;
if (*arg == ',')
arg++;
}
return 0;
}
"

View File

@@ -99,6 +99,7 @@ Run it from the top source directory using the command
output_to "fish/cmds_gperf.gperf" generate_fish_cmds_gperf;
output_to "fish/cmds.c" generate_fish_cmds;
output_to "fish/completion.c" generate_fish_completion;
output_to "fish/event-names.c" generate_fish_event_names;
output_to "fish/fish-cmds.h" generate_fish_cmds_h;
output_to "fish/guestfish-commands.pod" generate_fish_commands_pod;
output_to "fish/guestfish-actions.pod" generate_fish_actions_pod;

View File

@@ -97,6 +97,8 @@ fish/destpaths.c
fish/display.c
fish/echo.c
fish/edit.c
fish/event-names.c
fish/events.c
fish/fish.c
fish/glob.c
fish/help.c

View File

@@ -42,6 +42,7 @@ TESTS = \
test-guestfish-a.sh \
test-guestfish-d.sh \
test-guestfish-escapes.sh \
test-guestfish-events.sh \
test-guestfish-tilde.sh \
test-inspect-fstab.sh \
test-inspect-fstab-md.sh \

View File

@@ -0,0 +1,88 @@
#!/bin/bash -
# 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.
# Test guestfish events.
set -e
rm -f test.out
../fish/guestfish -a /dev/null <<'EOF' > test.out
trace true
event ev1 * "echo $EVENT $@"
event ev1 * "echo $EVENT $@"
event ev2 * "echo $EVENT $@"
list-events
delete-event ev1
list-events
reopen
list-events
event ev1 close,subprocess_quit "echo $EVENT $@"
event ev2 close,subprocess_quit "echo $EVENT $@"
event ev3 launch "echo $EVENT $@"
list-events
-delete-event ev4
list-events
delete-event ev1
list-events
delete-event ev3
list-events
EOF
if [ "$(cat test.out)" != '"ev1" (0): *: echo $EVENT $@
"ev1" (1): *: echo $EVENT $@
"ev2" (2): *: echo $EVENT $@
"ev2" (2): *: echo $EVENT $@
enter get_verbose
trace get_verbose
trace get_verbose = 0
enter get_trace
trace get_trace
trace get_trace = 1
enter get_autosync
trace get_autosync
trace get_autosync = 1
enter get_path
trace get_path
trace get_path = "../appliance"
enter get_pgroup
trace get_pgroup
trace get_pgroup = 0
trace close
close
"ev1" (0): close,subprocess_quit: echo $EVENT $@
"ev2" (1): close,subprocess_quit: echo $EVENT $@
"ev3" (2): launch_done: echo $EVENT $@
"ev1" (0): close,subprocess_quit: echo $EVENT $@
"ev2" (1): close,subprocess_quit: echo $EVENT $@
"ev3" (2): launch_done: echo $EVENT $@
"ev2" (1): close,subprocess_quit: echo $EVENT $@
"ev3" (2): launch_done: echo $EVENT $@
"ev2" (1): close,subprocess_quit: echo $EVENT $@
close' ]; then
echo "$0: unexpected output from guestfish events"
cat test.out
exit 1
fi
rm test.out