/* GIMP - The GNU Image Manipulation Program * Copyright (C) 1995 Spencer Kimball and Peter Mattis * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * This program 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "config.h" #define _GNU_SOURCE /* need the POSIX signal API */ #include #include #ifdef HAVE_UNISTD_H #include #endif #include #include #include #include #include "libgimpbase/gimpbase.h" #include "core/core-types.h" #include "core/gimp.h" #include "core/gimpdrawable.h" #include "core/gimpimage.h" #include "core/gimpitem.h" #include "core/gimpparamspecs.h" #include "config/gimpcoreconfig.h" #include "pdb/gimppdb.h" #include "errors.h" #include "gimp-log.h" #ifdef G_OS_WIN32 #include #endif /* private variables */ static Gimp *the_errors_gimp = NULL; static gboolean use_debug_handler = FALSE; static GimpStackTraceMode stack_trace_mode = GIMP_STACK_TRACE_QUERY; static gchar *full_prog_name = NULL; static gchar *backtrace_file = NULL; static gchar *backup_path = NULL; static GimpLogHandler log_domain_handler = 0; static guint global_handler_id = 0; /* local function prototypes */ static void gimp_message_log_func (const gchar *log_domain, GLogLevelFlags flags, const gchar *message, gpointer data); static void gimp_error_log_func (const gchar *domain, GLogLevelFlags flags, const gchar *message, gpointer data) G_GNUC_NORETURN; static G_GNUC_NORETURN void gimp_eek (const gchar *reason, const gchar *message, gboolean use_handler); /* public functions */ void errors_init (Gimp *gimp, const gchar *_full_prog_name, gboolean _use_debug_handler, GimpStackTraceMode _stack_trace_mode, const gchar *_backtrace_file) { g_return_if_fail (GIMP_IS_GIMP (gimp)); g_return_if_fail (_full_prog_name != NULL); g_return_if_fail (full_prog_name == NULL); #ifdef GIMP_UNSTABLE g_printerr ("This is a development version of GIMP. " "Debug messages may appear here.\n\n"); #endif /* GIMP_UNSTABLE */ the_errors_gimp = gimp; use_debug_handler = _use_debug_handler ? TRUE : FALSE; stack_trace_mode = _stack_trace_mode; full_prog_name = g_strdup (_full_prog_name); /* Create parent directories for both the crash and backup files. */ backtrace_file = g_path_get_dirname (_backtrace_file); backup_path = g_build_filename (gimp_directory (), "backups", NULL); g_mkdir_with_parents (backtrace_file, S_IRUSR | S_IWUSR | S_IXUSR); g_free (backtrace_file); backtrace_file = g_strdup (_backtrace_file); g_mkdir_with_parents (backup_path, S_IRUSR | S_IWUSR | S_IXUSR); g_free (backup_path); backup_path = g_build_filename (gimp_directory (), "backups", "backup-XXX.xcf", NULL); log_domain_handler = gimp_log_set_handler (FALSE, G_LOG_LEVEL_WARNING | G_LOG_LEVEL_MESSAGE | G_LOG_LEVEL_CRITICAL, gimp_message_log_func, gimp); global_handler_id = g_log_set_handler (NULL, G_LOG_LEVEL_ERROR | G_LOG_FLAG_FATAL, gimp_error_log_func, gimp); } void errors_exit (void) { if (log_domain_handler) { gimp_log_remove_handler (log_domain_handler); log_domain_handler = 0; } if (global_handler_id) { g_log_remove_handler (NULL, global_handler_id); global_handler_id = 0; } the_errors_gimp = NULL; if (backtrace_file) g_free (backtrace_file); if (full_prog_name) g_free (full_prog_name); if (backup_path) g_free (backup_path); } GList * errors_recovered (void) { GList *recovered = NULL; gchar *backup_path = g_build_filename (gimp_directory (), "backups", NULL); GDir *backup_dir = NULL; if ((backup_dir = g_dir_open (backup_path, 0, NULL))) { const gchar *file; while ((file = g_dir_read_name (backup_dir))) { if (g_str_has_suffix (file, ".xcf")) { gchar *path = g_build_filename (backup_path, file, NULL); if (g_file_test (path, G_FILE_TEST_IS_REGULAR) && ! g_file_test (path, G_FILE_TEST_IS_SYMLINK)) { /* A quick basic security check. It is not foolproof, * but better than nothing to make sure we are not * trying to read, then delete a folder or a symlink * to a file outside the backup directory. */ recovered = g_list_append (recovered, path); } else { g_free (path); } } } g_dir_close (backup_dir); } g_free (backup_path); return recovered; } void gimp_fatal_error (const gchar *message) { gimp_eek ("fatal error", message, TRUE); } void gimp_terminate (const gchar *message) { gimp_eek ("terminated", message, use_debug_handler); } /* private functions */ static void gimp_message_log_func (const gchar *log_domain, GLogLevelFlags flags, const gchar *message, gpointer data) { Gimp *gimp = data; GimpCoreConfig *config = gimp->config; const gchar *msg_domain = NULL; GimpMessageSeverity severity = GIMP_MESSAGE_WARNING; gboolean gui_message = TRUE; GimpDebugPolicy debug_policy; /* All GIMP messages are processed under the same domain, but * we need to keep the log domain information for third party * messages. */ if (! log_domain || (! g_str_has_prefix (log_domain, "Gimp") && ! g_str_has_prefix (log_domain, "LibGimp"))) msg_domain = log_domain; /* If debug policy requires it, WARNING and CRITICAL errors must be * routed for appropriate debugging. */ g_object_get (G_OBJECT (config), "debug-policy", &debug_policy, NULL); switch (flags & G_LOG_LEVEL_MASK) { case G_LOG_LEVEL_WARNING: severity = GIMP_MESSAGE_BUG_WARNING; if (debug_policy > GIMP_DEBUG_POLICY_WARNING) gui_message = FALSE; break; case G_LOG_LEVEL_CRITICAL: severity = GIMP_MESSAGE_BUG_CRITICAL; if (debug_policy > GIMP_DEBUG_POLICY_CRITICAL) gui_message = FALSE; break; } if (gimp && gui_message) { gimp_show_message (gimp, NULL, severity, msg_domain, message); } else { const gchar *reason = "Message"; gimp_enum_get_value (GIMP_TYPE_MESSAGE_SEVERITY, severity, NULL, NULL, &reason, NULL); g_printerr ("%s: %s-%s: %s\n", gimp_filename_to_utf8 (full_prog_name), log_domain, reason, message); } } static void gimp_error_log_func (const gchar *domain, GLogLevelFlags flags, const gchar *message, gpointer data) { gimp_fatal_error (message); } static void gimp_eek (const gchar *reason, const gchar *message, gboolean use_handler) { GimpCoreConfig *config = the_errors_gimp->config; gboolean eek_handled = FALSE; GimpDebugPolicy debug_policy; GList *iter; gint num_idx; gint i = 0; /* GIMP has 2 ways to handle termination signals and fatal errors: one * is the stack trace mode which is set at start as command line * option --stack-trace-mode, this won't change for the length of the * session and outputs a trace in terminal; the other is set in * preferences, outputs a trace in a GUI and can change anytime during * the session. * The GUI backtrace has priority if it is set. */ g_object_get (G_OBJECT (config), "debug-policy", &debug_policy, NULL); /* Let's just always output on stdout at least so that there is a * trace if the rest fails. */ g_printerr ("%s: %s: %s\n", full_prog_name, reason, message); #if ! defined (G_OS_WIN32) || defined (HAVE_EXCHNDL) if (use_handler) { #ifndef GIMP_CONSOLE_COMPILATION if (debug_policy != GIMP_DEBUG_POLICY_NEVER && ! the_errors_gimp->no_interface && backtrace_file) { FILE *fd; gboolean has_backtrace = TRUE; /* If GUI backtrace enabled (it is disabled by default), it * takes precedence over the command line argument. */ #ifdef G_OS_WIN32 const gchar *gimpdebug = "gimp-debug-tool-" GIMP_TOOL_VERSION ".exe"; #elif defined (PLATFORM_OSX) const gchar *gimpdebug = "gimp-debug-tool-" GIMP_TOOL_VERSION; #else const gchar *gimpdebug = LIBEXECDIR "/gimp-debug-tool-" GIMP_TOOL_VERSION; #endif gchar *args[9] = { (gchar *) gimpdebug, full_prog_name, NULL, (gchar *) reason, (gchar *) message, backtrace_file, the_errors_gimp->config->last_known_release, NULL, NULL }; gchar pid[16]; gchar timestamp[16]; g_snprintf (pid, 16, "%u", (guint) getpid ()); args[2] = pid; g_snprintf (timestamp, 16, "%lu", the_errors_gimp->config->last_release_timestamp); args[7] = timestamp; #ifndef G_OS_WIN32 /* On Win32, the trace has already been processed by ExcHnl * and is waiting for us in a text file. */ fd = g_fopen (backtrace_file, "w"); has_backtrace = gimp_stack_trace_print ((const gchar *) full_prog_name, fd, NULL); fclose (fd); #endif /* We don't care about any return value. If it fails, too * bad, we just won't have any stack trace. * We still need to use the sync() variant because we have * to keep GIMP up long enough for the debugger to get its * trace. */ if (has_backtrace && g_file_test (backtrace_file, G_FILE_TEST_IS_REGULAR) && g_spawn_async (NULL, args, NULL, G_SPAWN_SEARCH_PATH | G_SPAWN_STDERR_TO_DEV_NULL | G_SPAWN_STDOUT_TO_DEV_NULL, NULL, NULL, NULL, NULL)) eek_handled = TRUE; } #endif /* !GIMP_CONSOLE_COMPILATION */ #ifndef G_OS_WIN32 if (! eek_handled) { switch (stack_trace_mode) { case GIMP_STACK_TRACE_NEVER: break; case GIMP_STACK_TRACE_QUERY: { sigset_t sigset; sigemptyset (&sigset); sigprocmask (SIG_SETMASK, &sigset, NULL); if (the_errors_gimp) gimp_gui_ungrab (the_errors_gimp); gimp_stack_trace_query ((const gchar *) full_prog_name); } break; case GIMP_STACK_TRACE_ALWAYS: { sigset_t sigset; sigemptyset (&sigset); sigprocmask (SIG_SETMASK, &sigset, NULL); gimp_stack_trace_print ((const gchar *) full_prog_name, stdout, NULL); } break; default: break; } } #endif /* ! G_OS_WIN32 */ } #endif /* ! G_OS_WIN32 || HAVE_EXCHNDL */ #if defined (G_OS_WIN32) && ! defined (GIMP_CONSOLE_COMPILATION) /* g_on_error_* don't do anything reasonable on Win32. */ if (! eek_handled && ! the_errors_gimp->no_interface) MessageBox (NULL, g_strdup_printf ("%s: %s", reason, message), full_prog_name, MB_OK|MB_ICONERROR); #endif /* Let's try to back-up all unsaved images! * It is not 100%: when I tested with various bugs created on purpose, * I had cases where saving failed. I am not sure if this is because * of some memory management along the way to XCF saving or some other * messed up state of GIMP, but this is normal not to expect too much * during a crash. * Nevertheless in various test cases, I had successful backups XCF of * the work in progress. Yeah! */ if (backup_path) { /* increase the busy counter, so XCF saving calling * gimp_set_busy() and gimp_unset_busy() won't call the GUI * layer and do whatever windowing system calls to set cursors. */ the_errors_gimp->busy++; /* The index of 'XXX' in backup_path string. */ num_idx = strlen (backup_path) - 7; iter = gimp_get_image_iter (the_errors_gimp); for (; iter && i < 1000; iter = iter->next) { GimpImage *image = iter->data; GimpItem *item; if (! gimp_image_is_dirty (image)) continue; item = GIMP_ITEM (gimp_image_get_active_drawable (image)); /* This is a trick because we want to avoid any memory * allocation when the process is abnormally terminated. * We just assume that you'll never have more than 1000 images * open (which is already far fetched). */ backup_path[num_idx + 2] = '0' + (i % 10); backup_path[num_idx + 1] = '0' + ((i/10) % 10); backup_path[num_idx] = '0' + ((i/100) % 10); /* Saving. */ gimp_pdb_execute_procedure_by_name (the_errors_gimp->pdb, gimp_get_user_context (the_errors_gimp), NULL, NULL, "gimp-xcf-save", GIMP_TYPE_INT32, 0, GIMP_TYPE_IMAGE_ID, gimp_image_get_ID (image), GIMP_TYPE_DRAWABLE_ID, gimp_item_get_ID (item), G_TYPE_STRING, backup_path, G_TYPE_STRING, backup_path, G_TYPE_NONE); i++; } } exit (EXIT_FAILURE); }