summaryrefslogtreecommitdiffstats
path: root/src/events.c
blob: 9474228342423790bdad2f1ed7cd0f9860a22672 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
/* libguestfs
 * Copyright (C) 2011 Red Hat Inc.
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This library 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
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
 */

#include <config.h>

#define _BSD_SOURCE /* for mkdtemp, usleep */

#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <inttypes.h>
#include <unistd.h>
#include <assert.h>
#include <string.h>

#include "c-ctype.h"

#include "guestfs.h"
#include "guestfs-internal.h"

int
guestfs_set_event_callback (guestfs_h *g,
                            guestfs_event_callback cb,
                            uint64_t event_bitmask,
                            int flags,
                            void *opaque)
{
  if (flags != 0) {
    error (g, "flags parameter should be passed as 0 to this function");
    return -1;
  }

  /* We cast size_t to int which is not always safe for large numbers,
   * and in any case if a program is registering a huge number of
   * callbacks then we'd want to look at using an alternate data
   * structure in place of a linear list.
   */
  if (g->nr_events >= 1000) {
    error (g, "too many event callbacks registered");
    return -1;
  }

  int event_handle = (int) g->nr_events;
  g->events =
    guestfs_safe_realloc (g, g->events,
                          (g->nr_events+1) * sizeof (struct event));
  g->nr_events++;

  g->events[event_handle].event_bitmask = event_bitmask;
  g->events[event_handle].cb = cb;
  g->events[event_handle].opaque = opaque;
  g->events[event_handle].opaque2 = NULL;

  return event_handle;
}

void
guestfs_delete_event_callback (guestfs_h *g, int event_handle)
{
  if (event_handle < 0 || event_handle >= (int) g->nr_events)
    return;

  /* Set the event_bitmask to 0, which will ensure that this callback
   * cannot match any event and therefore cannot be called.
   */
  g->events[event_handle].event_bitmask = 0;
}

/* Functions to generate an event with various payloads. */

void
guestfs___call_callbacks_void (guestfs_h *g, uint64_t event)
{
  size_t i;

  for (i = 0; i < g->nr_events; ++i)
    if ((g->events[i].event_bitmask & event) != 0)
      g->events[i].cb (g, g->events[i].opaque, event, i, 0, NULL, 0, NULL, 0);

  /* All events with payload type void are discarded if no callback
   * was registered.
   */
}

void
guestfs___call_callbacks_message (guestfs_h *g, uint64_t event,
                                  const char *buf, size_t buf_len)
{
  size_t i, count = 0;

  for (i = 0; i < g->nr_events; ++i)
    if ((g->events[i].event_bitmask & event) != 0) {
      g->events[i].cb (g, g->events[i].opaque, event, i, 0,
                       buf, buf_len, NULL, 0);
      count++;
    }

  /* Emulate the old-style handlers.  Callers can override
   * print-on-stderr simply by registering a callback.
   */
  if (count == 0 &&
      (event == GUESTFS_EVENT_APPLIANCE ||
       event == GUESTFS_EVENT_LIBRARY ||
       event == GUESTFS_EVENT_TRACE) &&
      (g->verbose || event == GUESTFS_EVENT_TRACE)) {
    int from_appliance = event == GUESTFS_EVENT_APPLIANCE;
    size_t i, i0;

    /* APPLIANCE =>  <buf>
     * LIBRARY =>    libguestfs: <buf>\n
     * TRACE =>      libguestfs: trace: <buf>\n  (RHBZ#673479)
     */

    if (event != GUESTFS_EVENT_APPLIANCE)
      fputs ("libguestfs: ", stderr);

    if (event == GUESTFS_EVENT_TRACE)
      fputs ("trace: ", stderr);

    /* Special or non-printing characters in the buffer must be
     * escaped (RHBZ#731744).  The buffer can contain any 8 bit
     * character, even \0.
     *
     * Handling of \n and \r characters is complex:
     *
     * Case 1: Messages from the appliance: These messages already
     * contain \n and \r characters at logical positions, so we just
     * echo those out directly.
     *
     * Case 2: Messages from other sources: These messages should NOT
     * contain \n or \r.  If they do, it is escaped.  However we also
     * need to print a real end of line after these messages.
     *
     * RHBZ#802109: Because stderr is usually not buffered, avoid
     * single 'putc' calls (which translate to a 1 byte write), and
     * try to send longest possible strings in single fwrite calls
     * (thanks to Jim Meyering for the basic approach).
     */
#define NO_ESCAPING(c) \
      (c_isprint ((c)) || (from_appliance && ((c) == '\n' || (c) == '\r')))

    for (i = 0; i < buf_len; ++i) {
      if (NO_ESCAPING (buf[i])) {
        i0 = i;
        while (i < buf_len && NO_ESCAPING (buf[i]))
          ++i;
        fwrite (&buf[i0], 1, i-i0, stderr);
        /* Adjust i so that next time around the loop, the next
         * non-printing character will be displayed.
         */
        if (i < buf_len)
          --i;
      } else {
        switch (buf[i]) {
        case '\0': fputs ("\\0", stderr); break;
        case '\a': fputs ("\\a", stderr); break;
        case '\b': fputs ("\\b", stderr); break;
        case '\f': fputs ("\\f", stderr); break;
        case '\n': fputs ("\\n", stderr); break;
        case '\r': fputs ("\\r", stderr); break;
        case '\t': fputs ("\\t", stderr); break;
        case '\v': fputs ("\\v", stderr); break;
        default:
          fprintf (stderr, "\\x%x", (unsigned char) buf[i]);
        }
      }
    }

    if (!from_appliance)
      putc ('\n', stderr);
  }
}

void
guestfs___call_callbacks_array (guestfs_h *g, uint64_t event,
                                const uint64_t *array, size_t array_len)
{
  size_t i;

  for (i = 0; i < g->nr_events; ++i)
    if ((g->events[i].event_bitmask & event) != 0)
      g->events[i].cb (g, g->events[i].opaque, event, i, 0,
                       NULL, 0, array, array_len);

  /* All events with payload type array are discarded if no callback
   * was registered.
   */
}

/* Emulate old-style callback API.
 *
 * There were no event handles, so multiple callbacks per event were
 * not supported.  Calling the same 'guestfs_set_*_callback' function
 * would replace the existing event.  Calling it with cb == NULL meant
 * that the caller wanted to remove the callback.
 */

static void
replace_old_style_event_callback (guestfs_h *g,
                                  guestfs_event_callback cb,
                                  uint64_t event_bitmask,
                                  void *opaque,
                                  void *opaque2)
{
  size_t i;

  /* Use 'cb' pointer as a sentinel to replace the existing callback
   * for this event if one was registered previously.  Else append a
   * new event.
   */

  for (i = 0; i < g->nr_events; ++i)
    if (g->events[i].cb == cb) {
      if (opaque2 == NULL) {
        /* opaque2 (the original callback) is NULL, which in the
         * old-style API meant remove the callback.
         */
        guestfs_delete_event_callback (g, i);
        return;
      }

      goto replace;
    }

  if (opaque2 == NULL)
    return; /* see above */

  /* i == g->nr_events */
  g->events =
    guestfs_safe_realloc (g, g->events,
                          (g->nr_events+1) * sizeof (struct event));
  g->nr_events++;

 replace:
  g->events[i].event_bitmask = event_bitmask;
  g->events[i].cb = cb;
  g->events[i].opaque = opaque;
  g->events[i].opaque2 = opaque2;
}

static void
log_message_callback_wrapper (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)
{
  guestfs_log_message_cb cb = g->events[event_handle].opaque2;
  /* Note that the old callback declared the message buffer as
   * (char *, int).  I sure hope message buffers aren't too large
   * and that callers aren't writing to them. XXX
   */
  cb (g, opaque, (char *) buf, (int) buf_len);
}

void
guestfs_set_log_message_callback (guestfs_h *g,
                                  guestfs_log_message_cb cb, void *opaque)
{
  replace_old_style_event_callback (g, log_message_callback_wrapper,
                                    GUESTFS_EVENT_APPLIANCE,
                                    opaque, cb);
}

static void
subprocess_quit_callback_wrapper (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)
{
  guestfs_subprocess_quit_cb cb = g->events[event_handle].opaque2;
  cb (g, opaque);
}

void
guestfs_set_subprocess_quit_callback (guestfs_h *g,
                                      guestfs_subprocess_quit_cb cb, void *opaque)
{
  replace_old_style_event_callback (g, subprocess_quit_callback_wrapper,
                                    GUESTFS_EVENT_SUBPROCESS_QUIT,
                                    opaque, cb);
}

static void
launch_done_callback_wrapper (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)
{
  guestfs_launch_done_cb cb = g->events[event_handle].opaque2;
  cb (g, opaque);
}

void
guestfs_set_launch_done_callback (guestfs_h *g,
                                  guestfs_launch_done_cb cb, void *opaque)
{
  replace_old_style_event_callback (g, launch_done_callback_wrapper,
                                    GUESTFS_EVENT_LAUNCH_DONE,
                                    opaque, cb);
}

static void
close_callback_wrapper (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)
{
  guestfs_close_cb cb = g->events[event_handle].opaque2;
  cb (g, opaque);
}

void
guestfs_set_close_callback (guestfs_h *g,
                            guestfs_close_cb cb, void *opaque)
{
  replace_old_style_event_callback (g, close_callback_wrapper,
                                    GUESTFS_EVENT_CLOSE,
                                    opaque, cb);
}

static void
progress_callback_wrapper (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)
{
  guestfs_progress_cb cb = g->events[event_handle].opaque2;
  assert (array_len >= 4);
  cb (g, opaque, (int) array[0], (int) array[1], array[2], array[3]);
}

void
guestfs_set_progress_callback (guestfs_h *g,
                               guestfs_progress_cb cb, void *opaque)
{
  replace_old_style_event_callback (g, progress_callback_wrapper,
                                    GUESTFS_EVENT_PROGRESS,
                                    opaque, cb);
}