mirror of
https://github.com/libguestfs/libguestfs.git
synced 2026-03-21 22:53:37 +00:00
Otherwise string output looks quite awkward Signed-off-by: Cole Robinson <crobinso@redhat.com>
569 lines
16 KiB
C
569 lines
16 KiB
C
/* libguestfs
|
|
* Copyright (C) 2009-2025 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
|
|
*/
|
|
|
|
/**
|
|
* This file handles errors, and also debug, trace and warning messages.
|
|
*
|
|
* Errors in libguestfs API calls are handled by setting an error
|
|
* message and optional errno in the handle. The caller has the
|
|
* choice of testing API calls to find out if they failed and then
|
|
* querying the last error from the handle, and/or getting a callback.
|
|
*
|
|
* From the point of view of the library source, generally you should
|
|
* use the C<error> or C<perrorf> macros along error paths, eg:
|
|
*
|
|
* if (something_bad) {
|
|
* error (g, "something bad happened");
|
|
* return -1;
|
|
* }
|
|
*
|
|
* Make sure to call the C<error> or C<perrorf> macro exactly once
|
|
* along each error path, since the handle can only store a single
|
|
* error and the previous error will be overwritten.
|
|
*/
|
|
|
|
#include <config.h>
|
|
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <stdarg.h>
|
|
#include <string.h>
|
|
#include <errno.h>
|
|
#include <libintl.h>
|
|
|
|
#include "c-ctype.h"
|
|
#include "ignore-value.h"
|
|
|
|
#include "guestfs.h"
|
|
#include "guestfs-internal.h"
|
|
|
|
/* How errors and error handlers works in the handle:
|
|
*
|
|
* The handle has a g->error_data field which is a thread-local
|
|
* storage (TLS) key.
|
|
*
|
|
* We use TLS because we want to support the common idioms of:
|
|
* if (guestfs_foo (g) == -1)
|
|
* printf ("%s\n", guestfs_last_error (g));
|
|
* and:
|
|
* guestfs_push_error_handler (g, ...);
|
|
* guestfs_foo (g);
|
|
* guestfs_pop_error_handler (g);
|
|
* neither of which would ordinarily be safe when using the same
|
|
* handle from multiple threads.
|
|
*
|
|
* In each thread, the TLS data is either NULL or contains a pointer
|
|
* to a 'struct error_data'.
|
|
*
|
|
* When it is NULL, it means the stack is empty (in that thread) and
|
|
* the default handler (default_error_cb) is installed.
|
|
*
|
|
* As soon as the current thread calls guestfs_set_error_handler,
|
|
* guestfs_push_error_handler, or an error is set in the handle (calls
|
|
* like guestfs_int_perrorf and so on), the key is created and
|
|
* initialized with a pointer to a real 'struct error_data'.
|
|
*
|
|
* All the 'struct error_data' structures associated with one handle
|
|
* are linked together in a linked list, so that we are able to free
|
|
* them when the handle is closed. (The pthread_key* API doesn't give
|
|
* us any other way to do this, in particular pthread_key_delete
|
|
* doesn't call the destructor associated with the key).
|
|
*/
|
|
|
|
static void default_error_cb (guestfs_h *g, void *data, const char *msg);
|
|
|
|
/* Stack of old error handlers. */
|
|
struct error_cb_stack {
|
|
struct error_cb_stack *next;
|
|
guestfs_error_handler_cb error_cb;
|
|
void * error_cb_data;
|
|
};
|
|
|
|
/* Error data, stored in thread-local storage in g->error_data key. */
|
|
struct error_data {
|
|
/* Linked list of error_data structs allocated for this handle. */
|
|
struct error_data *next;
|
|
|
|
char *last_error; /* Last error on handle. */
|
|
int last_errnum; /* errno, or 0 if there was no errno */
|
|
|
|
/* Error handler and stack of old error handlers. */
|
|
guestfs_error_handler_cb error_cb;
|
|
void * error_cb_data;
|
|
struct error_cb_stack *error_cb_stack;
|
|
};
|
|
|
|
static void
|
|
free_error_data (struct error_data *error_data)
|
|
{
|
|
struct error_cb_stack *p, *next_p;
|
|
|
|
free (error_data->last_error);
|
|
for (p = error_data->error_cb_stack; p != NULL; p = next_p) {
|
|
next_p = p->next;
|
|
free (p);
|
|
}
|
|
free (error_data);
|
|
}
|
|
|
|
/* Free all the error_data structs created for a particular handle. */
|
|
void
|
|
guestfs_int_free_error_data_list (guestfs_h *g)
|
|
{
|
|
struct error_data *p, *next_p;
|
|
|
|
ACQUIRE_LOCK_FOR_CURRENT_SCOPE (&g->error_data_list_lock);
|
|
|
|
for (p = g->error_data_list; p != NULL; p = next_p) {
|
|
next_p = p->next;
|
|
free_error_data (p);
|
|
}
|
|
|
|
g->error_data_list = NULL;
|
|
}
|
|
|
|
/* Get thread-specific error_data struct. Create it if necessary. */
|
|
static struct error_data *
|
|
get_error_data (guestfs_h *g)
|
|
{
|
|
struct error_data *ret;
|
|
|
|
ret = pthread_getspecific (g->error_data);
|
|
|
|
/* Not allocated yet for this thread, so allocate one. */
|
|
if (ret == NULL) {
|
|
ret = safe_malloc (g, sizeof *ret);
|
|
ret->last_error = NULL;
|
|
ret->last_errnum = 0;
|
|
ret->error_cb = default_error_cb;
|
|
ret->error_cb_data = NULL;
|
|
ret->error_cb_stack = NULL;
|
|
|
|
/* Add it to the linked list of struct error_data that are
|
|
* associated with this handle, so we can free them when the
|
|
* handle is closed.
|
|
*/
|
|
{
|
|
ACQUIRE_LOCK_FOR_CURRENT_SCOPE (&g->error_data_list_lock);
|
|
ret->next = g->error_data_list;
|
|
g->error_data_list = ret;
|
|
}
|
|
|
|
/* Set the TLS to point to the struct. This is safe because we
|
|
* should have acquired the handle lock.
|
|
*/
|
|
pthread_setspecific (g->error_data, ret);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
const char *
|
|
guestfs_last_error (guestfs_h *g)
|
|
{
|
|
ACQUIRE_LOCK_FOR_CURRENT_SCOPE (&g->lock);
|
|
return get_error_data (g)->last_error;
|
|
}
|
|
|
|
int
|
|
guestfs_last_errno (guestfs_h *g)
|
|
{
|
|
ACQUIRE_LOCK_FOR_CURRENT_SCOPE (&g->lock);
|
|
return get_error_data (g)->last_errnum;
|
|
}
|
|
|
|
static void
|
|
set_last_error (guestfs_h *g, int errnum, const char *msg)
|
|
{
|
|
struct error_data *error_data = get_error_data (g);
|
|
|
|
free (error_data->last_error);
|
|
error_data->last_error = strdup (msg);
|
|
error_data->last_errnum = errnum;
|
|
}
|
|
|
|
/**
|
|
* Print a warning.
|
|
*
|
|
* Code should I<not> call this function directly. Use the
|
|
* S<C<warning (g, fs, ...)>> macro.
|
|
*
|
|
* Warnings are printed unconditionally. We try to make these rare:
|
|
* Generally speaking, a warning should either be an error, or if it's
|
|
* not important for end users then it should be a debug message.
|
|
*/
|
|
void
|
|
guestfs_int_warning (guestfs_h *g, const char *fs, ...)
|
|
{
|
|
va_list args;
|
|
CLEANUP_FREE char *msg = NULL;
|
|
int len;
|
|
|
|
va_start (args, fs);
|
|
len = vasprintf (&msg, fs, args);
|
|
va_end (args);
|
|
|
|
if (len < 0) return;
|
|
|
|
guestfs_int_call_callbacks_message (g, GUESTFS_EVENT_WARNING, msg, len);
|
|
}
|
|
|
|
/**
|
|
* Print a debug message.
|
|
*
|
|
* Code should I<not> call this function directly. To add debug
|
|
* messages in the library, use the S<C<debug (g, fs, ...)>> macro.
|
|
* The macro checks if C<g-E<gt>verbose> is false and avoids the
|
|
* function call, meaning the macro is more efficient.
|
|
*/
|
|
void
|
|
guestfs_int_debug (guestfs_h *g, const char *fs, ...)
|
|
{
|
|
va_list args;
|
|
CLEANUP_FREE char *msg = NULL;
|
|
int len;
|
|
|
|
/* The cpp macro "debug" has already checked that g->verbose is true
|
|
* before calling this function, but we check it again just in case
|
|
* anyone calls this function directly.
|
|
*/
|
|
if (!g->verbose)
|
|
return;
|
|
|
|
va_start (args, fs);
|
|
len = vasprintf (&msg, fs, args);
|
|
va_end (args);
|
|
|
|
if (len < 0) return;
|
|
|
|
guestfs_int_call_callbacks_message (g, GUESTFS_EVENT_LIBRARY, msg, len);
|
|
}
|
|
|
|
/**
|
|
* Print a trace message.
|
|
*
|
|
* Do not call this function. All calls are generated automatically.
|
|
*/
|
|
void
|
|
guestfs_int_trace (guestfs_h *g, const char *fs, ...)
|
|
{
|
|
va_list args;
|
|
CLEANUP_FREE char *msg = NULL;
|
|
int len;
|
|
|
|
va_start (args, fs);
|
|
len = vasprintf (&msg, fs, args);
|
|
va_end (args);
|
|
|
|
if (len < 0) return;
|
|
|
|
guestfs_int_call_callbacks_message (g, GUESTFS_EVENT_TRACE, msg, len);
|
|
}
|
|
|
|
/**
|
|
* Set the last error and errno in the handle, and optionally raise
|
|
* the error callback if one is defined.
|
|
*
|
|
* If you don't need to set errno, use the S<C<error (g, fs, ...)>>
|
|
* macro instead of calling this directly. If you need to set errno
|
|
* then there is no macro wrapper, so calling this function directly
|
|
* is fine.
|
|
*/
|
|
void
|
|
guestfs_int_error_errno (guestfs_h *g, int errnum, const char *fs, ...)
|
|
{
|
|
va_list args;
|
|
CLEANUP_FREE char *msg = NULL;
|
|
int err;
|
|
struct error_data *error_data = get_error_data (g);
|
|
|
|
va_start (args, fs);
|
|
err = vasprintf (&msg, fs, args);
|
|
va_end (args);
|
|
|
|
if (err < 0) return;
|
|
|
|
/* set_last_error first so that the callback can access the error
|
|
* message and errno through the handle if it wishes.
|
|
*/
|
|
set_last_error (g, errnum, msg);
|
|
if (error_data->error_cb)
|
|
error_data->error_cb (g, error_data->error_cb_data, msg);
|
|
}
|
|
|
|
/**
|
|
* Similar to L<perror(3)>, but it sets the last error in the handle,
|
|
* raises the error callback if one is defined, and supports format
|
|
* strings.
|
|
*
|
|
* You should probably use the S<C<perrorf (g, fs, ...)>> macro
|
|
* instead of calling this directly.
|
|
*/
|
|
void
|
|
guestfs_int_perrorf (guestfs_h *g, const char *fs, ...)
|
|
{
|
|
va_list args;
|
|
CLEANUP_FREE char *msg = NULL;
|
|
const int errnum = errno;
|
|
int err;
|
|
char buf[256];
|
|
const char *errstr;
|
|
struct error_data *error_data = get_error_data (g);
|
|
|
|
va_start (args, fs);
|
|
err = vasprintf (&msg, fs, args);
|
|
va_end (args);
|
|
|
|
if (err < 0) return;
|
|
|
|
errstr = guestfs_int_strerror (errnum, buf, sizeof buf);
|
|
|
|
msg = safe_realloc (g, msg, strlen (msg) + 2 + strlen (errstr) + 1);
|
|
strcat (msg, ": ");
|
|
strcat (msg, errstr);
|
|
|
|
/* set_last_error first so that the callback can access the error
|
|
* message and errno through the handle if it wishes.
|
|
*/
|
|
set_last_error (g, errnum, msg);
|
|
if (error_data->error_cb)
|
|
error_data->error_cb (g, error_data->error_cb_data, msg);
|
|
}
|
|
|
|
void
|
|
guestfs_set_out_of_memory_handler (guestfs_h *g, guestfs_abort_cb cb)
|
|
{
|
|
ACQUIRE_LOCK_FOR_CURRENT_SCOPE (&g->lock);
|
|
g->abort_cb = cb;
|
|
}
|
|
|
|
guestfs_abort_cb
|
|
guestfs_get_out_of_memory_handler (guestfs_h *g)
|
|
{
|
|
ACQUIRE_LOCK_FOR_CURRENT_SCOPE (&g->lock);
|
|
return g->abort_cb;
|
|
}
|
|
|
|
void
|
|
guestfs_set_error_handler (guestfs_h *g,
|
|
guestfs_error_handler_cb cb, void *data)
|
|
{
|
|
ACQUIRE_LOCK_FOR_CURRENT_SCOPE (&g->lock);
|
|
struct error_data *error_data;
|
|
|
|
error_data = get_error_data (g);
|
|
error_data->error_cb = cb;
|
|
error_data->error_cb_data = data;
|
|
}
|
|
|
|
guestfs_error_handler_cb
|
|
guestfs_get_error_handler (guestfs_h *g, void **data_rtn)
|
|
{
|
|
ACQUIRE_LOCK_FOR_CURRENT_SCOPE (&g->lock);
|
|
struct error_data *error_data = get_error_data (g);
|
|
|
|
if (data_rtn) *data_rtn = error_data->error_cb_data;
|
|
return error_data->error_cb;
|
|
}
|
|
|
|
void
|
|
guestfs_push_error_handler (guestfs_h *g,
|
|
guestfs_error_handler_cb cb, void *data)
|
|
{
|
|
ACQUIRE_LOCK_FOR_CURRENT_SCOPE (&g->lock);
|
|
struct error_data *error_data;
|
|
struct error_cb_stack *old_stack;
|
|
|
|
error_data = get_error_data (g);
|
|
old_stack = error_data->error_cb_stack;
|
|
error_data->error_cb_stack = safe_malloc (g, sizeof (struct error_cb_stack));
|
|
error_data->error_cb_stack->next = old_stack;
|
|
error_data->error_cb_stack->error_cb = error_data->error_cb;
|
|
error_data->error_cb_stack->error_cb_data = error_data->error_cb_data;
|
|
|
|
guestfs_set_error_handler (g, cb, data);
|
|
}
|
|
|
|
void
|
|
guestfs_pop_error_handler (guestfs_h *g)
|
|
{
|
|
ACQUIRE_LOCK_FOR_CURRENT_SCOPE (&g->lock);
|
|
struct error_data *error_data;
|
|
struct error_cb_stack *next_stack;
|
|
|
|
error_data = get_error_data (g);
|
|
if (error_data->error_cb_stack) {
|
|
next_stack = error_data->error_cb_stack->next;
|
|
guestfs_set_error_handler (g, error_data->error_cb_stack->error_cb,
|
|
error_data->error_cb_stack->error_cb_data);
|
|
free (error_data->error_cb_stack);
|
|
error_data->error_cb_stack = next_stack;
|
|
}
|
|
else {
|
|
error_data->error_cb = default_error_cb;
|
|
error_data->error_cb_data = NULL;
|
|
}
|
|
}
|
|
|
|
static void
|
|
default_error_cb (guestfs_h *g, void *data, const char *msg)
|
|
{
|
|
fprintf (stderr, _("libguestfs: error: %s\n"), msg);
|
|
}
|
|
|
|
/* When tracing, be careful how we print BufferIn parameters which
|
|
* usually contain large amounts of binary data (RHBZ#646822).
|
|
*/
|
|
void
|
|
guestfs_int_print_BufferIn (FILE *out, const char *buf, size_t buf_size)
|
|
{
|
|
size_t i;
|
|
const size_t orig_size = buf_size;
|
|
|
|
if (buf_size > 256)
|
|
buf_size = 256;
|
|
|
|
fputc ('"', out);
|
|
|
|
for (i = 0; i < buf_size; ++i) {
|
|
if (c_isprint (buf[i]))
|
|
fputc (buf[i], out);
|
|
else
|
|
fprintf (out, "\\x%02x", (unsigned char) buf[i]);
|
|
}
|
|
|
|
fputc ('"', out);
|
|
|
|
if (orig_size > buf_size)
|
|
fprintf (out,
|
|
_("<truncated, original size %zu bytes>"), orig_size);
|
|
}
|
|
|
|
void
|
|
guestfs_int_print_BufferOut (FILE *out, const char *buf, size_t buf_size)
|
|
{
|
|
guestfs_int_print_BufferIn (out, buf, buf_size);
|
|
}
|
|
|
|
/* Some standard error messages for common failures. */
|
|
|
|
#define DEBUG_ADVICE \
|
|
"Do:\n" \
|
|
" export LIBGUESTFS_DEBUG=1 LIBGUESTFS_TRACE=1\n" \
|
|
"and run the command again. For further information, read:\n" \
|
|
" http://libguestfs.org/guestfs-faq.1.html#debugging-libguestfs\n" \
|
|
"You can also run 'libguestfs-test-tool' and post the *complete* output\n" \
|
|
"into a bug report or message to the libguestfs mailing list."
|
|
|
|
/**
|
|
* Raise a launch failed error in a standard format.
|
|
*
|
|
* Since this is the most common error seen by people who have
|
|
* installation problems, buggy qemu, etc, and since no one reads the
|
|
* FAQ, describe in this error message what resources are available to
|
|
* debug launch problems.
|
|
*/
|
|
void
|
|
guestfs_int_launch_failed_error (guestfs_h *g)
|
|
{
|
|
if (g->verbose)
|
|
error (g, _("guestfs_launch failed, see earlier error messages"));
|
|
else
|
|
error (g, _(
|
|
"guestfs_launch failed.\n"
|
|
"This usually means the libguestfs appliance failed to start or crashed.\n"
|
|
DEBUG_ADVICE));
|
|
}
|
|
|
|
/**
|
|
* Raise an error if the appliance unexpectedly crashes after launch.
|
|
*/
|
|
void
|
|
guestfs_int_unexpected_close_error (guestfs_h *g)
|
|
{
|
|
if (g->verbose)
|
|
guestfs_int_error_errno (g, EPIPE,
|
|
_("appliance closed the connection unexpectedly, "
|
|
"see earlier error messages"));
|
|
else
|
|
guestfs_int_error_errno (g, EPIPE,
|
|
_("appliance closed the connection unexpectedly.\n"
|
|
"This usually means the libguestfs appliance "
|
|
"crashed.\n"
|
|
DEBUG_ADVICE));
|
|
}
|
|
|
|
/**
|
|
* Raise an error if the appliance hangs during launch.
|
|
*/
|
|
void
|
|
guestfs_int_launch_timeout (guestfs_h *g)
|
|
{
|
|
if (g->verbose)
|
|
error (g, _("appliance launch timed out, see earlier error messages"));
|
|
else
|
|
error (g, _(
|
|
"appliance launch timed out.\n"
|
|
"This usually means the kernel or appliance hung during launch.\n"
|
|
DEBUG_ADVICE));
|
|
}
|
|
|
|
/**
|
|
* Raise an error if an external command fails.
|
|
*
|
|
* C<status> is the status code of the command (eg. returned from
|
|
* L<waitpid(2)> or L<system(3)>). This function turns the status
|
|
* code into an explanatory string.
|
|
*/
|
|
void
|
|
guestfs_int_external_command_failed (guestfs_h *g, int status,
|
|
const char *cmd_name, const char *extra)
|
|
{
|
|
const size_t len = 80 + strlen (cmd_name);
|
|
CLEANUP_FREE char *status_string = safe_malloc (g, len);
|
|
CLEANUP_FREE char *extra_clean = NULL;
|
|
|
|
guestfs_int_exit_status_to_string (status, cmd_name, status_string, len);
|
|
|
|
if (extra) {
|
|
extra_clean = guestfs_int_replace_string(extra, "\n", " ");
|
|
if (!extra_clean)
|
|
g->abort_cb ();
|
|
}
|
|
|
|
if (g->verbose) {
|
|
if (!extra_clean)
|
|
error (g, _("%s, see debug messages above"), status_string);
|
|
else
|
|
error (g, _("%s: %s: %s, see debug messages above"),
|
|
cmd_name, extra_clean, status_string);
|
|
}
|
|
else {
|
|
if (!extra_clean)
|
|
error (g, _("%s.\n"
|
|
"To see full error messages you may need to enable debugging.\n"
|
|
DEBUG_ADVICE),
|
|
status_string);
|
|
else
|
|
error (g, _("%s: %s: %s.\n"
|
|
"To see full error messages you may need to enable debugging.\n"
|
|
DEBUG_ADVICE),
|
|
cmd_name, extra_clean, status_string);
|
|
}
|
|
}
|