Files
libguestfs/lib/events.c
Richard W.M. Jones 9cfa1c410f Remove use of gnulib glthread.
This gnulib feature abstracts away threads, locks and TLS, and also
allowed libguestfs to be linked with or without pthread.  However
since pthread these days is part of glibc and so every program is
using pthread, and we want to get rid of gnulib as a dependency, just
use pthread directly.
2021-04-08 11:12:17 +01:00

400 lines
12 KiB
C

/* 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>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.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)
{
ACQUIRE_LOCK_FOR_CURRENT_SCOPE (&g->lock);
int event_handle;
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;
}
event_handle = (int) g->nr_events;
g->events =
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)
{
ACQUIRE_LOCK_FOR_CURRENT_SCOPE (&g->lock);
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;
/* If the program continually allocated and then deallocated event
* handlers, it would eventually run into the limit (of 1000) in the
* function above. Avoid that here. However this doesn't "fix" the
* problem that this structure is not well-suited to handling large
* numbers of event handlers.
*/
if ((unsigned) event_handle == g->nr_events-1)
g->nr_events--;
}
/* Functions to generate an event with various payloads. */
void
guestfs_int_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_int_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 (so count > 0).
*/
if (count > 0)
return;
if ((event == GUESTFS_EVENT_APPLIANCE ||
event == GUESTFS_EVENT_LIBRARY ||
event == GUESTFS_EVENT_WARNING ||
event == GUESTFS_EVENT_TRACE) &&
(g->verbose ||
event == GUESTFS_EVENT_WARNING ||
event == GUESTFS_EVENT_TRACE)) {
bool from_appliance = event == GUESTFS_EVENT_APPLIANCE;
size_t i0;
/* APPLIANCE => <buf>
* LIBRARY => libguestfs: <buf>\n
* WARNING => libguestfs: warning: <buf>\n
* TRACE => libguestfs: trace: [<ID>:] <buf>\n (RHBZ#673479)
*/
if (event != GUESTFS_EVENT_APPLIANCE)
fputs ("libguestfs: ", stderr);
if (event == GUESTFS_EVENT_WARNING)
fputs ("warning: ", stderr);
if (event == GUESTFS_EVENT_TRACE) {
fputs ("trace: ", stderr);
if (STRNEQ (g->identifier, "")) {
fputs (g->identifier, stderr);
fputs (": ", 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_int_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 C<guestfs_set_*_callback> function
* would replace the existing event. Calling it with C<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 =
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)
{
ACQUIRE_LOCK_FOR_CURRENT_SCOPE (&g->lock);
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)
{
ACQUIRE_LOCK_FOR_CURRENT_SCOPE (&g->lock);
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)
{
ACQUIRE_LOCK_FOR_CURRENT_SCOPE (&g->lock);
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)
{
ACQUIRE_LOCK_FOR_CURRENT_SCOPE (&g->lock);
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)
{
ACQUIRE_LOCK_FOR_CURRENT_SCOPE (&g->lock);
replace_old_style_event_callback (g, progress_callback_wrapper,
GUESTFS_EVENT_PROGRESS,
opaque, cb);
}