mirror of
https://github.com/libguestfs/libguestfs.git
synced 2026-03-22 07:03:38 +00:00
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.
400 lines
12 KiB
C
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);
|
|
}
|