fish: Allow events to be processed in guestfish.
[libguestfs.git] / fish / events.c
1 /* guestfish - the filesystem interactive shell
2  * Copyright (C) 2011 Red Hat Inc.
3  *
4  * This program is free software; you can redistribute it and/or modify
5  * it under the terms of the GNU General Public License as published by
6  * the Free Software Foundation; either version 2 of the License, or
7  * (at your option) any later version.
8  *
9  * This program is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  * GNU General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public License
15  * along with this program; if not, write to the Free Software
16  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
17  */
18
19 #include <config.h>
20
21 #include <stdio.h>
22 #include <stdlib.h>
23 #include <string.h>
24 #include <unistd.h>
25 #include <inttypes.h>
26 #include <assert.h>
27 #include <sys/types.h>
28 #include <sys/wait.h>
29
30 #include <guestfs.h>
31
32 #include "hash.h"
33 #include "hash-pjw.h"
34
35 #include "fish.h"
36
37 /* The hash table maps names to multiple (linked list of) event handlers. */
38 static Hash_table *event_handlers;
39
40 struct entry {
41   struct entry *next;           /* Next entry in linked list. */
42   char *name;                   /* Event name. */
43   char *command;                /* Shell script / command that runs. */
44   uint64_t event_bitmask;       /* Events this is registered for. */
45   int eh;        /* Event handle (from guestfs_set_event_callback). */
46 };
47
48 static void
49 entry_free (void *x)
50 {
51   if (x) {
52     struct entry *p = x;
53     entry_free (p->next);
54     free (p->name);
55     free (p->command);
56     free (p);
57   }
58 }
59
60 static size_t
61 entry_hash (void const *x, size_t table_size)
62 {
63   struct entry const *p = x;
64   return hash_pjw (p->name, table_size);
65 }
66
67 static bool
68 entry_compare (void const *x, void const *y)
69 {
70   struct entry const *p = x;
71   struct entry const *q = y;
72   return STREQ (p->name, q->name);
73 }
74
75 void
76 init_event_handlers (void)
77 {
78   assert (event_handlers == NULL);
79   event_handlers =
80     hash_initialize (64, NULL, entry_hash, entry_compare, entry_free);
81 }
82
83 void
84 free_event_handlers (void)
85 {
86   assert (event_handlers != NULL);
87   hash_free (event_handlers);
88   event_handlers = NULL;
89 }
90
91 static void
92 do_event_handler (guestfs_h *g,
93                   void *opaque,
94                   uint64_t event,
95                   int event_handle,
96                   int flags,
97                   const char *buf, size_t buf_len,
98                   const uint64_t *array, size_t array_len)
99 {
100   pid_t pid;
101   const char *argv[8 + array_len];
102   const char *shell;
103   struct entry *entry = opaque;
104   size_t i, j;
105   char *s;
106
107   pid = fork ();
108   if (pid == -1) {
109     perror ("event handler: fork");
110     return;
111   }
112
113   if (pid == 0) {               /* Child process. */
114     shell = getenv ("SHELL");
115     if (!shell)
116       shell = "/bin/sh";
117
118     setenv ("EVENT", event_name_of_event_bitmask (event), 1);
119
120     /* Construct the command and arguments. */
121     i = 0;
122     argv[i++] = shell;
123     argv[i++] = "-c";
124     argv[i++] = entry->command;
125     argv[i++] = ""; /* $0 */
126
127     if (buf != NULL)
128       /* XXX: So far, buf is always ASCII NUL-terminated.  There is no
129        * way to pass arbitrary 8 bit buffers.
130        */
131       argv[i++] = buf;
132
133     for (j = 0; j < array_len; ++j) {
134       if (asprintf (&s, "%" PRIu64, array[j]) == -1) {
135         perror ("event handler: asprintf");
136         _exit (EXIT_FAILURE);
137       }
138       argv[i++] = s;
139     }
140
141     argv[i++] = NULL;
142
143     execvp (argv[0], (void *) argv);
144     perror (argv[0]);
145
146     _exit (EXIT_FAILURE);
147   }
148
149   if (waitpid (pid, NULL, 0) == -1)
150     perror ("event handler: waitpid");
151 }
152
153 int
154 run_event (const char *cmd, size_t argc, char *argv[])
155 {
156   int r;
157   struct entry *entry = NULL, *old_entry;
158
159   if (argc != 3) {
160     fprintf (stderr,
161              _("use 'event <name> <eventset> <script>' to register an event handler\n"));
162     goto error;
163   }
164
165   entry = calloc (1, sizeof *entry);
166   if (entry == NULL) {
167     perror ("calloc");
168     goto error;
169   }
170   entry->eh = -1;
171
172   r = event_bitmask_of_event_set (argv[1], &entry->event_bitmask);
173   if (r == -1)
174     goto error;
175
176   entry->name = strdup (argv[0]);
177   if (entry->name == NULL) {
178     perror ("strdup");
179     goto error;
180   }
181   entry->command = strdup (argv[2]);
182   if (entry->command == NULL) {
183     perror ("strdup");
184     goto error;
185   }
186
187   entry->eh =
188     guestfs_set_event_callback (g, do_event_handler,
189                                 entry->event_bitmask, 0, entry);
190   if (entry->eh == -1)
191     goto error;
192
193   r = hash_insert_if_absent (event_handlers, entry, (const void **) &old_entry);
194   if (r == -1)
195     goto error;
196   if (r == 0) {                 /* old_entry set to existing entry */
197     entry->next = old_entry->next;
198     /* XXX are we allowed to update the old entry? */
199     old_entry->next = entry;
200   }
201
202   return 0;
203
204  error:
205   if (entry) {
206     if (entry->eh >= 0)
207       guestfs_delete_event_callback (g, entry->eh);
208     free (entry->name);
209     free (entry->command);
210     free (entry);
211   }
212   return -1;
213 }
214
215 int
216 run_delete_event (const char *cmd, size_t argc, char *argv[])
217 {
218   if (argc != 1) {
219     fprintf (stderr,
220              _("use 'delete-event <name>' to delete an event handler\n"));
221     return -1;
222   }
223
224   const struct entry key = { .name = bad_cast (argv[0]) };
225   struct entry *entry, *p;
226
227   entry = hash_delete (event_handlers, &key);
228   if (!entry) {
229     fprintf (stderr, _("delete-event: %s: no such event handler\n"), argv[0]);
230     return -1;
231   }
232
233   /* Delete them from the handle. */
234   p = entry;
235   while (p) {
236     guestfs_delete_event_callback (g, p->eh);
237     p = p->next;
238   }
239
240   /* Free the structures. */
241   entry_free (entry);
242
243   return 0;
244 }
245
246 static bool
247 list_event (void *x, void *data)
248 {
249   struct entry *entry = x;
250
251   while (entry) {
252     printf ("\"%s\" (%d): ", entry->name, entry->eh);
253     print_event_set (entry->event_bitmask, stdout);
254     printf (": %s\n", entry->command);
255     entry = entry->next;
256   }
257
258   return 1;
259 }
260
261 int
262 run_list_events (const char *cmd, size_t argc, char *argv[])
263 {
264   if (argc != 0) {
265     fprintf (stderr,
266              _("use 'list-events' to list event handlers\n"));
267     return -1;
268   }
269
270   hash_do_for_each (event_handlers, list_event, NULL);
271   return 0;
272 }