Files
libguestfs/lib/errors.c
Richard W.M. Jones 6c05d666b8 lib, lua: Fix usage of strerror_r
$ ./run guestfish -vx add-drive foo "readonly:true"
  libguestfs: trace: set_pgroup true
  libguestfs: trace: set_pgroup = 0
  libguestfs: trace: add_drive "foo" "readonly:true"
  libguestfs: error: foo:
  libguestfs: trace: add_drive = -1 (error)
  libguestfs: trace: close
  libguestfs: closing guestfs handle 0x55c0bacf12a0 (state 0)

Fix the usage of strerror_r by using the new wrapper defined in
libguestfs-common.  A similar fix is made in the Lua bindings.

Fixes: https://bugzilla.redhat.com/show_bug.cgi?id=2030396
Reported-by: Masayoshi Mizuma <m.mizuma@jp.fujitsu.com>
Tested-by: Masayoshi Mizuma <m.mizuma@jp.fujitsu.com>
Reviewed-by: Laszlo Ersek <lersek@redhat.com>
2021-12-09 13:54:15 +00:00

562 lines
16 KiB
C

/* libguestfs
* Copyright (C) 2009-2020 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);
guestfs_int_exit_status_to_string (status, cmd_name, status_string, len);
if (g->verbose) {
if (!extra)
error (g, _("%s, see debug messages above"), status_string);
else
error (g, _("%s: %s: %s, see debug messages above"),
cmd_name, extra, status_string);
}
else {
if (!extra)
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, status_string);
}
}