/* 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 or C macros along error paths, eg: * * if (something_bad) { * error (g, "something bad happened"); * return -1; * } * * Make sure to call the C or C macro exactly once * along each error path, since the handle can only store a single * error and the previous error will be overwritten. */ #include #include #include #include #include #include #include #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 call this function directly. Use the * S> 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 call this function directly. To add debug * messages in the library, use the S> macro. * The macro checks if Cverbose> 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> * 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, 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> 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, _(""), 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 is the status code of the command (eg. returned from * L or L). 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); } }