lintian doesn't like orphan packages with uploaders...
[debian/amanda] / common-src / debug.c
index a6de4154c5efcf350f3bd7a5947649e7c8e7e06c..cb3bdd558a8c9a8527578ea33defa06e8d02544f 100644 (file)
@@ -1,6 +1,7 @@
 /*
  * Amanda, The Advanced Maryland Automatic Network Disk Archiver
  * Copyright (c) 1991-1998 University of Maryland at College Park
+ * Copyright (c) 2007-2012 Zmanda, Inc.  All Rights Reserved.
  * All Rights Reserved.
  *
  * Permission to use, copy, modify, distribute, and sell this software and its
 #include "timestamp.h"
 #include "conffile.h"
 
+#ifdef HAVE_GLIBC_BACKTRACE
+#include <execinfo.h>
+#endif
+
 /* Minimum file descriptor on which to keep the debug file.  This is intended
  * to keep the descriptor "out of the way" of other processing.  It's not clear
  * that this is required any longer, but it doesn't hurt anything.
@@ -55,19 +60,16 @@ static char *dbgdir = NULL;
 /* time debug log was opened (timestamp of the file) */
 static time_t open_time;
 
-/* pointer to logfile.c's 'logerror()', if we're linked
- * with it */
-static void (*logerror_fn)(char *) = NULL;
-
 /* storage for global variables */
-erroutput_type_t erroutput_type = ERR_INTERACTIVE;
 int error_exit_status = 1;
 
 /* static function prototypes */
 static char *get_debug_name(time_t t, int n);
+static void debug_unlink_old(void);
 static void debug_setup_1(char *config, char *subdir);
 static void debug_setup_2(char *s, int fd, char *annotation);
 static char *msg_timestamp(void);
+static char *msg_thread(void);
 
 static void debug_logging_handler(const gchar *log_domain,
        GLogLevelFlags log_level,
@@ -75,6 +77,12 @@ static void debug_logging_handler(const gchar *log_domain,
        gpointer user_data);
 static void debug_setup_logging(void);
 
+/* By default, do not suppress tracebacks */
+static gboolean do_suppress_error_traceback = FALSE;
+
+/* configured amanda_log_handlers */
+static GSList *amanda_log_handlers = NULL;
+
 /*
  * Generate a debug file name.  The name is based on the program name,
  * followed by a timestamp, an optional sequence number, and ".debug".
@@ -106,6 +114,15 @@ get_debug_name(
     return result;
 }
 
+/* Call this to suppress tracebacks on error() or g_critical().  This is used
+ * when a critical error is indicated in perl, and the traceback will not be
+ * useful. */
+void
+suppress_error_traceback(void)
+{
+    do_suppress_error_traceback = 1;
+}
+
 /* A GLogFunc to handle g_log calls.  This function assumes that user_data
  * is either NULL or a pointer to one of the debug_* configuration variables
  * in conffile.c, indicating whether logging for this log domain is enabled.
@@ -121,43 +138,64 @@ debug_logging_handler(const gchar *log_domain G_GNUC_UNUSED,
            const gchar *message,
            gpointer user_data G_GNUC_UNUSED)
 {
-    char *maxlevel = NULL;
-
-    /* convert the highest level to a string and dbprintf it */
-    if (log_level & G_LOG_LEVEL_ERROR)
-       maxlevel = _("error (fatal): ");
-    else if (log_level & G_LOG_LEVEL_CRITICAL)
-       maxlevel = _("critical (fatal): ");
-    else if (log_level & G_LOG_LEVEL_WARNING)
-       maxlevel = _("warning: ");
-    else if (log_level & G_LOG_LEVEL_MESSAGE)
-       maxlevel = _("message: ");
-    else if (log_level & G_LOG_LEVEL_INFO)
-       maxlevel = _("info: ");
-    else
-       maxlevel = ""; /* no level displayed for debugging */
-
-    debug_printf("%s%s\n", maxlevel, message);
+    GLogLevelFlags maxlevel;
+    char *levprefix = NULL;
+    pcontext_t context = get_pcontext();
+
+    /* glib allows a message to have multiple levels, so calculate the "worst"
+     * level */
+    if (log_level & G_LOG_LEVEL_ERROR) {
+       maxlevel = G_LOG_LEVEL_ERROR;
+       levprefix = _("error (fatal): ");
+    } else if (log_level & G_LOG_LEVEL_CRITICAL) {
+       maxlevel = G_LOG_LEVEL_CRITICAL;
+       levprefix = _("critical (fatal): ");
+    } else if (log_level & G_LOG_LEVEL_WARNING) {
+       maxlevel = G_LOG_LEVEL_WARNING;
+       levprefix = _("warning: ");
+    } else if (log_level & G_LOG_LEVEL_MESSAGE) {
+       maxlevel = G_LOG_LEVEL_MESSAGE;
+       levprefix = _("message: ");
+    } else if (log_level & G_LOG_LEVEL_INFO) {
+       maxlevel = G_LOG_LEVEL_INFO;
+       levprefix = _("info: ");
+    } else {
+       maxlevel = G_LOG_LEVEL_DEBUG;
+       levprefix = ""; /* no level displayed for debugging */
+    }
 
-    /* error and critical levels have special handling */
-    if (log_level & (G_LOG_LEVEL_ERROR|G_LOG_LEVEL_CRITICAL)) {
-       if (erroutput_type & ERR_AMANDALOG && logerror_fn != NULL)
-           (*logerror_fn)((char *)message); /* discard 'const' */
+    /* scriptutil context doesn't do any logging except for critical
+     * and error levels */
+    if (context != CONTEXT_SCRIPTUTIL) {
+       /* convert the highest level to a string and dbprintf it */
+       debug_printf("%s%s\n", levprefix, message);
+    }
 
-       if (erroutput_type & ERR_SYSLOG) {
-#ifdef LOG_AUTH
-           openlog(get_pname(), LOG_PID, LOG_AUTH);
-#else
-           openlog(get_pname(), LOG_PID, 0);
-#endif
-           syslog(LOG_NOTICE, "%s", message);
-           closelog();
+    if (amanda_log_handlers) {
+       GSList *iter = amanda_log_handlers;
+       while (iter) {
+           amanda_log_handler_t *hdlr = (amanda_log_handler_t *)iter->data;
+           hdlr(maxlevel, message);
+           iter = g_slist_next(iter);
        }
+    } else {
+       /* call the appropriate handlers, based on the context */
+       amanda_log_stderr(maxlevel, message);
+       if (context == CONTEXT_DAEMON)
+           amanda_log_syslog(maxlevel, message);
+    }
 
-       if (erroutput_type & ERR_INTERACTIVE) {
-           g_fprintf(stderr, "%s: %s\n", get_pname(), message);
-           fflush(stderr);
+    /* error and critical levels have special handling */
+    if (log_level & (G_LOG_LEVEL_ERROR|G_LOG_LEVEL_CRITICAL)) {
+#ifdef HAVE_GLIBC_BACKTRACE
+       /* try logging a traceback to the debug log */
+       if (!do_suppress_error_traceback && db_fd != -1) {
+           void *stack[32];
+           int naddrs;
+           naddrs = backtrace(stack, sizeof(stack)/sizeof(*stack));
+           backtrace_symbols_fd(stack, naddrs, db_fd);
        }
+#endif
 
        /* we're done */
        if (log_level & G_LOG_LEVEL_CRITICAL)
@@ -183,8 +221,62 @@ debug_setup_logging(void)
                      debug_logging_handler, NULL);
 }
 
-/* Set the global dbgdir according to 'config' and 'subdir', and clean
- * old debug files out of that directory
+void
+add_amanda_log_handler(amanda_log_handler_t *hdlr)
+{
+    amanda_log_handlers = g_slist_append(amanda_log_handlers, (gpointer)hdlr);
+}
+
+void
+amanda_log_syslog(GLogLevelFlags log_level, const gchar *message)
+{
+    int priority = LOG_ERR;
+    switch (log_level) {
+       case G_LOG_LEVEL_ERROR:
+       case G_LOG_LEVEL_CRITICAL:
+           priority = LOG_ERR;
+           break;
+
+       case G_LOG_LEVEL_WARNING:
+#ifdef LOG_WARNING
+           priority = LOG_WARNING;
+#endif
+           break;
+
+       default:
+           return;
+    }
+
+#ifdef LOG_DAEMON
+    openlog(get_pname(), LOG_PID, LOG_DAEMON);
+#else
+    openlog(get_pname(), LOG_PID, 0);
+#endif
+    syslog(priority, "%s", message);
+    closelog();
+
+}
+
+void
+amanda_log_stderr(GLogLevelFlags log_level, const gchar *message)
+{
+    switch (log_level) {
+       case G_LOG_LEVEL_ERROR:
+       case G_LOG_LEVEL_CRITICAL:
+           g_fprintf(stderr, "%s: %s\n", get_pname(), message);
+           break;
+
+       default:
+           return;
+    }
+}
+
+void
+amanda_log_null(GLogLevelFlags log_level G_GNUC_UNUSED, const gchar *message G_GNUC_UNUSED)
+{
+}
+
+/* Set the global dbgdir according to 'config' and 'subdir'
  *
  * The global open_time is set to the current time, and used to delete
  * old files.
@@ -195,25 +287,7 @@ debug_setup_logging(void)
 static void
 debug_setup_1(char *config, char *subdir)
 {
-    char *pname;
-    size_t pname_len;
-    char *e = NULL;
-    char *s = NULL;
-    DIR *d;
-    struct dirent *entry;
-    int do_rename;
-    char *test_name;
-    size_t test_name_len;
-    size_t d_name_len;
-    struct stat sbuf;
-    char *dbfilename = NULL;
     char *sane_config = NULL;
-    int i;
-
-    memset(&sbuf, 0, SIZEOF(sbuf));
-
-    pname = get_pname();
-    pname_len = strlen(pname);
 
     /*
      * Create the debug directory if it does not yet exist.
@@ -237,19 +311,45 @@ debug_setup_1(char *config, char *subdir)
     }
     amfree(sane_config);
 
-    /*
-     * Clean out old debug files.  We also rename files with old style
-     * names (XXX.debug or XXX.$PID.debug) into the new name format.
-     * We assume no system has 17 digit PID-s :-) and that there will
-     * not be a conflict between an old and new name.
-     */
+    time(&open_time);
+}
+
+/*
+ * Clean out old debug files.  We also rename files with old style
+ * names (XXX.debug or XXX.$PID.debug) into the new name format.
+ * We assume no system has 17 digit PID-s :-) and that there will
+ * not be a conflict between an old and new name.
+ */
+static void
+debug_unlink_old(void)
+{
+    char *pname;
+    size_t pname_len;
+    char *e = NULL;
+    char *s = NULL;
+    struct dirent *entry;
+    int do_rename;
+    char *test_name;
+    size_t test_name_len;
+    size_t d_name_len;
+    char *dbfilename = NULL;
+    int i;
+    DIR *d;
+    struct stat sbuf;
+
+    assert(dbgdir != NULL);
+
+    memset(&sbuf, 0, SIZEOF(sbuf));
+
+    pname = get_pname();
+    pname_len = strlen(pname);
+
     if((d = opendir(dbgdir)) == NULL) {
        error(_("open debug directory \"%s\": %s"),
              dbgdir, strerror(errno));
        /*NOTREACHED*/
     }
-    time(&open_time);
-    test_name = get_debug_name(open_time - (AMANDA_DEBUG_DAYS * 24 * 60 * 60), 0);
+    test_name = get_debug_name(open_time - (getconf_int(CNF_DEBUG_DAYS) * 24 * 60 * 60), 0);
     test_name_len = strlen(test_name);
     while((entry = readdir(d)) != NULL) {
        if(is_dot_or_dotdot(entry->d_name)) {
@@ -344,7 +444,7 @@ debug_setup_2(
                     db_filename, (int)get_client_uid(), (int)get_client_gid(), strerror(errno));
        }
     }
-    amfree(dbgdir);
+
     /*
      * Move the file descriptor up high so it stays out of the way
      * of other processing, e.g. sendbackup.
@@ -365,9 +465,10 @@ debug_setup_2(
        /*
         * Make the first debug log file entry.
         */
-       debug_printf(_("pid %ld ruid %ld euid %ld: %s at %s"),
+       debug_printf(_("pid %ld ruid %ld euid %ld version %s: %s at %s"),
                     (long)getpid(),
                     (long)getuid(), (long)geteuid(),
+                    VERSION,
                     annotation,
                     ctime(&open_time));
     }
@@ -382,24 +483,50 @@ debug_setup_2(
 static char *
 msg_timestamp(void)
 {
-    static char timestamp[128];
-    struct timeval tv;
+    static char  timestamp[128];
+    char        *r;
+    time_t       curtime;
 
-    gettimeofday(&tv, NULL);
-    g_snprintf(timestamp, SIZEOF(timestamp), "%lld.%06ld",
-               (long long)tv.tv_sec, (long)tv.tv_usec);
+    time(&curtime);
+    ctime_r(&curtime, timestamp);
+    r = strchr(timestamp, '\n');
+    if (r)
+       *r = '\0';
 
     return timestamp;
 }
 
+/* Get current GMT time and return a message timestamp.
+ * Used for g_printf calls to logs and such.  The return value
+ * is to a static buffer, so it should be used immediately.
+ *
+ * @returns: timestamp
+ */
+static char *
+msg_thread(void)
+{
+    static char  thread[128];
+
+    sprintf(thread, "thd-%p", g_thread_self());
+
+    return thread;
+}
+
 /*
  * ---- public functions
  */
 
 void
-set_logerror(void (*f)(char *))
+debug_init(void)
 {
-    logerror_fn = f;
+    debug_setup_logging();
+
+    /* the scriptutil context does not create a debug log, since such
+     * processes are invoked many times.
+     */
+    if (get_pcontext() != CONTEXT_SCRIPTUTIL) {
+       debug_open(get_ptype());
+    }
 }
 
 void
@@ -410,6 +537,9 @@ debug_open(char *subdir)
     char *s = NULL;
     mode_t mask;
 
+    /* create AMANDA_TMPDIR */
+    make_amanda_tmpdir();
+
     /* set up logging while we're here */
     debug_setup_logging();
 
@@ -503,8 +633,16 @@ debug_rename(
     if (!db_filename)
        return;
 
+    if (get_pcontext() == CONTEXT_SCRIPTUTIL) {
+       return;
+    }
+
+    /* Remove old log from source directory */
+    debug_unlink_old();
     /* set 'dbgdir' and clean out old debug files */
     debug_setup_1(config, subdir);
+    /* Remove old log from destination directory */
+    debug_unlink_old();
 
     s = newvstralloc(s, dbgdir, db_name, NULL);
 
@@ -610,6 +748,12 @@ debug_close(void)
 {
     time_t curtime;
 
+    if (get_pcontext() == CONTEXT_SCRIPTUTIL) {
+       return;
+    }
+
+    debug_unlink_old();
+
     time(&curtime);
     debug_printf(_("pid %ld finish time %s"), (long)getpid(), ctime(&curtime));
 
@@ -648,13 +792,19 @@ printf_arglist_function(void debug_printf, const char *, format)
        db_file = stderr;
     }
     if(db_file != NULL) {
+       char *prefix;
+       char *text;
+
        if (db_file != stderr)
-           g_fprintf(db_file, "%s: %s: ", msg_timestamp(), get_pname());
+           prefix = g_strdup_printf("%s: %s: %s:", msg_timestamp(), msg_thread(), get_pname());
        else 
-           g_fprintf(db_file, "%s: ", get_pname());
+           prefix = g_strdup_printf("%s:", get_pname());
        arglist_start(argp, format);
-       g_vfprintf(db_file, format, argp);
+       text = g_strdup_vprintf(format, argp);
        arglist_end(argp);
+       fprintf(db_file, "%s %s", prefix, text);
+       amfree(prefix);
+       amfree(text);
        fflush(db_file);
     }
     errno = save_errno;
@@ -685,7 +835,7 @@ debug_dup_stderr_to_debug(void)
     {
        if(dup2(db_fd, STDERR_FILENO) != STDERR_FILENO)
        {
-          error(_("can't redirect stderr to the debug file"));
+          error(_("can't redirect stderr to the debug file: %d, %s"), db_fd, strerror(errno));
           g_assert_not_reached();
        }
     }