X-Git-Url: https://git.gag.com/?a=blobdiff_plain;f=common-src%2Fdebug.c;h=7adbc00b241b2346c73914e6fb70d2193f4afca0;hb=94a044f90357edefa6f4ae9f0b1d5885b0e34aee;hp=ced7bc1cd1ee9f720ce43f214a5d8a7fc8c0494f;hpb=3ab887b9bc819a846c75dd7f2ee5d41fac22b19f;p=debian%2Famanda diff --git a/common-src/debug.c b/common-src/debug.c index ced7bc1..7adbc00 100644 --- a/common-src/debug.c +++ b/common-src/debug.c @@ -25,69 +25,68 @@ * University of Maryland at College Park */ /* - * $Id: debug.c,v 1.17.4.3.4.3.2.9 2003/01/04 17:46:09 martinea Exp $ + * $Id: debug.c,v 1.40 2006/07/26 11:49:32 martinea Exp $ * - * debug log subroutines + * Logging support */ #include "amanda.h" -#include "clock.h" #include "util.h" #include "arglist.h" +#include "clock.h" +#include "timestamp.h" +#include "conffile.h" -#ifndef AMANDA_DBGDIR -# define AMANDA_DBGDIR AMANDA_TMPDIR -#endif - -#ifdef DEBUG_CODE - -int debug = 1; - +/* 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. + */ #define MIN_DB_FD 10 -static int db_fd = 2; /* default is stderr */ -static FILE *db_file = NULL; /* stderr may not be a constant */ -static char *db_filename = NULL; +/* information on the current debug file */ +static int db_fd = 2; /* file descriptor (default stderr) */ +static FILE *db_file = NULL; /* stdio stream */ +static char *db_name = NULL; /* unqualified filename */ +static char *db_filename = NULL; /* fully qualified pathname */ -static pid_t debug_prefix_pid = 0; +/* directory containing debug file, including trailing slash */ +static char *dbgdir = NULL; -/* - * Format and write a debug message to the process debug file. - */ -printf_arglist_function(void debug_printf, char *, format) -{ - va_list argp; - int save_errno; +/* time debug log was opened (timestamp of the file) */ +static time_t open_time; - /* - * It is common in the code to call dbprintf to write out - * syserrno(errno) and then turn around and try to do something else - * with errno (e.g. printf() or log()), so we make sure errno goes - * back out with the same value it came in with. - */ - save_errno = errno; +/* pointer to logfile.c's 'logerror()', if we're linked + * with it */ +static void (*logerror_fn)(char *) = NULL; - if(db_file == NULL && db_fd == 2) { - db_file = stderr; - } - if(db_file != NULL) { - arglist_start(argp, format); - vfprintf(db_file, format, argp); - fflush(db_file); - arglist_end(argp); - } +/* storage for global variables */ +erroutput_type_t erroutput_type = ERR_INTERACTIVE; +int error_exit_status = 1; - errno = save_errno; -} +/* static function prototypes */ +static char *get_debug_name(time_t t, int n); +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 void debug_logging_handler(const gchar *log_domain, + GLogLevelFlags log_level, + const gchar *message, + gpointer user_data); +static void debug_setup_logging(void); /* * Generate a debug file name. The name is based on the program name, * followed by a timestamp, an optional sequence number, and ".debug". + * + * @param t: timestamp + * @param n: sequence number between 1 and 1000; if zero, no sequence number + * is included. */ static char * -get_debug_name(t, n) - time_t t; - int n; +get_debug_name( + time_t t, + int n) { char number[NUM_STR_SIZE]; char *ts; @@ -96,56 +95,147 @@ get_debug_name(t, n) if(n < 0 || n > 1000) { return NULL; } - ts = construct_timestamp(&t); + ts = get_timestamp_from_time(t); if(n == 0) { number[0] = '\0'; } else { - ap_snprintf(number, sizeof(number), "%03d", n - 1); + g_snprintf(number, SIZEOF(number), "%03d", n - 1); } result = vstralloc(get_pname(), ".", ts, number, ".debug", NULL); amfree(ts); return result; } -void debug_open() +/* 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. + * + * @param log_domain: the log domain, or NULL for general logging + * @param log_level: level, fatality, and recursion flags + * @param message: the message to log + * @param user_pointer: unused + */ +static void +debug_logging_handler(const gchar *log_domain G_GNUC_UNUSED, + GLogLevelFlags log_level, + const gchar *message, + gpointer user_data G_GNUC_UNUSED) { - time_t curtime; - int saved_debug; - char *dbgdir = NULL; + 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); + + /* 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' */ + + 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 (erroutput_type & ERR_INTERACTIVE) { + g_fprintf(stderr, "%s: %s: %s\n", get_pname(), msg_timestamp(), message); + fflush(stderr); + } + + /* we're done */ + if (log_level & G_LOG_LEVEL_CRITICAL) + exit(error_exit_status); + else + abort(); + g_assert_not_reached(); + } +} + +/* Install our handler into the glib log handling system. + */ +static void +debug_setup_logging(void) +{ + /* g_error and g_critical should be fatal, although the log handler + * takes care of this anyway */ + g_log_set_always_fatal(G_LOG_LEVEL_ERROR | G_LOG_LEVEL_CRITICAL); + + /* set up handler (g_log_set_default_handler is new in glib-2.6, and + * hence not useable here) */ + g_log_set_handler(NULL, G_LOG_LEVEL_MASK | G_LOG_FLAG_FATAL | G_LOG_FLAG_RECURSION, + debug_logging_handler, NULL); +} + +/* Set the global dbgdir according to 'config' and 'subdir', and clean + * old debug files out of that directory + * + * The global open_time is set to the current time, and used to delete + * old files. + * + * @param config: configuration or NULL + * @param subdir: subdirectory (server, client, etc.) or NULL + */ +static void +debug_setup_1(char *config, char *subdir) +{ + char *pname; + size_t pname_len; char *e = NULL; char *s = NULL; - char *dbfilename = NULL; DIR *d; struct dirent *entry; - char *pname; - size_t pname_len; int do_rename; - char *test_name = NULL; + char *test_name; size_t test_name_len; - int fd = -1; - int i; size_t d_name_len; - int fd_close[MIN_DB_FD+1]; - struct passwd *pwent; struct stat sbuf; + char *dbfilename = NULL; + char *sane_config = NULL; + int i; + + memset(&sbuf, 0, SIZEOF(sbuf)); pname = get_pname(); pname_len = strlen(pname); - if(client_uid == (uid_t) -1 && (pwent = getpwnam(CLIENT_LOGIN)) != NULL) { - client_uid = pwent->pw_uid; - client_gid = pwent->pw_gid; - endpwent(); - } - /* * Create the debug directory if it does not yet exist. */ - dbgdir = stralloc2(AMANDA_DBGDIR, "/"); - if(mkpdir(dbgdir, 02700, client_uid, client_gid) == -1) { - error("create debug directory \"%s\": %s", - AMANDA_DBGDIR, strerror(errno)); + amfree(dbgdir); + if (config) + sane_config = sanitise_filename(config); + if (sane_config && subdir) + dbgdir = vstralloc(AMANDA_DBGDIR, "/", subdir, "/", sane_config, + "/", NULL); + else if (sane_config) + dbgdir = vstralloc(AMANDA_DBGDIR, "/", sane_config, "/", NULL); + else if (subdir) + dbgdir = vstralloc(AMANDA_DBGDIR, "/", subdir, "/", NULL); + else + dbgdir = stralloc2(AMANDA_DBGDIR, "/"); + if(mkpdir(dbgdir, 02700, get_client_uid(), get_client_gid()) == -1) { + error(_("create debug directory \"%s\": %s"), + dbgdir, strerror(errno)); + /*NOTREACHED*/ } + amfree(sane_config); /* * Clean out old debug files. We also rename files with old style @@ -153,12 +243,13 @@ void debug_open() * We assume no system has 17 digit PID-s :-) and that there will * not be a conflict between an old and new name. */ - if((d = opendir(AMANDA_DBGDIR)) == NULL) { - error("open debug directory \"%s\": %s", - AMANDA_DBGDIR, strerror(errno)); + if((d = opendir(dbgdir)) == NULL) { + error(_("open debug directory \"%s\": %s"), + dbgdir, strerror(errno)); + /*NOTREACHED*/ } - time(&curtime); - test_name = get_debug_name(curtime - (AMANDA_DEBUG_DAYS * 24 * 60 * 60), 0); + time(&open_time); + test_name = get_debug_name(open_time - (AMANDA_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)) { @@ -202,7 +293,8 @@ void debug_open() dbfilename = get_debug_name((time_t)sbuf.st_mtime, ++i); } if(dbfilename == NULL) { - error("cannot rename old debug file \"%s\"", entry->d_name); + error(_("cannot rename old debug file \"%s\""), entry->d_name); + /*NOTREACHED*/ } } } @@ -211,139 +303,388 @@ void debug_open() amfree(s); amfree(test_name); closedir(d); +} + +/* Given an already-opened debug file, set the file's ownership + * appropriately, move its file descriptor above MIN_DB_FD, and + * add an initial log entry to the file. + * + * This function records the file's identity in the globals + * db_filename, db_fd, and db_file. It does *not* set db_name. + * db_file is not set if fd is -1 + * + * This function uses the global 'open_time', which is set by + * debug_setup_1. + * + * @param s: the filename of the debug file; string should be malloc'd, + * and should *not* be freed by the caller. + * @param fd: the descriptor connected to the debug file, or -1 if + * no decriptor moving should take place. + * @param annotation: an extra string to include in the initial + * log entry. + */ +static void +debug_setup_2( + char * s, + int fd, + char * annotation) +{ + int i; + int fd_close[MIN_DB_FD+1]; - /* - * Create the new file. - */ - for(i = 0; - (dbfilename = get_debug_name(curtime, i)) != NULL - && (s = newvstralloc(s, dbgdir, dbfilename, NULL)) != NULL - && (fd = open(s, O_WRONLY|O_CREAT|O_EXCL|O_APPEND, 0600)) < 0; - i++, free(dbfilename)) {} - if(dbfilename == NULL) { - error("cannot create %s debug file", get_pname()); - } - amfree(dbfilename); amfree(db_filename); db_filename = s; s = NULL; - (void) chown(db_filename, client_uid, client_gid); - amfree(dbgdir); + /* If we're root, change the ownership of the debug files. If we're not root, + * this would either be redundant or an error. */ + if (geteuid() == 0) { + if (chown(db_filename, get_client_uid(), get_client_gid()) < 0) { + dbprintf(_("chown(%s, %d, %d) failed: %s"), + 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. */ + if (fd >= 0) { + i = 0; + fd_close[i++] = fd; + while((db_fd = dup(fd)) < MIN_DB_FD) { + fd_close[i++] = db_fd; + } + while(--i >= 0) { + close(fd_close[i]); + } + db_file = fdopen(db_fd, "a"); + } + + if (annotation) { + /* + * Make the first debug log file entry. + */ + debug_printf(_("pid %ld ruid %ld euid %ld: %s at %s"), + (long)getpid(), + (long)getuid(), (long)geteuid(), + annotation, + ctime(&open_time)); + } +} + +/* 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_timestamp(void) +{ + static char timestamp[128]; + struct timeval tv; + + gettimeofday(&tv, NULL); + g_snprintf(timestamp, SIZEOF(timestamp), "%lld.%06ld", + (long long)tv.tv_sec, (long)tv.tv_usec); + + return timestamp; +} + +/* + * ---- public functions + */ + +void +set_logerror(void (*f)(char *)) +{ + logerror_fn = f; +} + +void +debug_open(char *subdir) +{ + int fd = -1; + int i; + char *s = NULL; + mode_t mask; + + /* set up logging while we're here */ + debug_setup_logging(); + + /* set 'dbgdir' and clean out old debug files */ + debug_setup_1(NULL, subdir); + + /* + * Create the new file with a unique sequence number. + */ + mask = (mode_t)umask((mode_t)0037); /* Allow the group read bit through */ + + /* iteratate through sequence numbers until we find one that + * is not already in use */ + for(i = 0; fd < 0; i++) { + amfree(db_name); + if ((db_name = get_debug_name(open_time, i)) == NULL) { + error(_("Cannot create debug file name in %d tries."), i); + /*NOTREACHED*/ + } + + if ((s = newvstralloc(s, dbgdir, db_name, NULL)) == NULL) { + error(_("Cannot allocate debug file name memory")); + /*NOTREACHED*/ + } + + if ((fd = open(s, O_WRONLY|O_CREAT|O_EXCL|O_APPEND, 0640)) < 0) { + if (errno != EEXIST) { + error(_("Cannot create debug file \"%s\": %s"), + s, strerror(errno)); + /*NOTREACHED*/ + } + amfree(s); + } + } + (void)umask(mask); /* Restore mask */ + + /* + * Finish setup. + * + * Note: we release control of the string 's' points to. + */ + debug_setup_2(s, fd, "start"); +} + +void +debug_reopen( + char * dbfilename, + char * annotation) +{ + char *s = NULL; + int fd; + + if (dbfilename == NULL) { + return; + } + + /* set 'dbgdir' and clean out old debug files */ + debug_setup_1(NULL, NULL); + + /* + * Reopen the file. + */ + if (*dbfilename == '/') { + s = stralloc(dbfilename); + } else { + s = newvstralloc(s, dbgdir, dbfilename, NULL); + } + if ((fd = open(s, O_RDWR|O_APPEND)) < 0) { + error(_("cannot reopen debug file %s"), dbfilename); + /*NOTREACHED*/ + } + + /* + * Finish setup. + * + * Note: we release control of the string 's' points to. + */ + debug_setup_2(s, fd, annotation); +} + +void +debug_rename( + char *config, + char *subdir) +{ + int fd = -1; + int i; + char *s = NULL; + mode_t mask; + + if (!db_filename) + return; + + /* set 'dbgdir' and clean out old debug files */ + debug_setup_1(config, subdir); + + s = newvstralloc(s, dbgdir, db_name, NULL); + + if (strcmp(db_filename, s) == 0) { + amfree(s); + return; + } + + mask = (mode_t)umask((mode_t)0037); + +#if defined(__CYGWIN__) + /* + * On cygwin, rename will not overwrite an existing file nor + * will it rename a file that is open for writing... + * + * Rename file directly. Expect failure if file already exists + * or is open by another user. + */ + i = 0; - fd_close[i++] = fd; - while((db_fd = dup(fd)) < MIN_DB_FD) { - fd_close[i++] = db_fd; + while (rename(db_filename, s) < 0) { + if (errno != EEXIST) { + /* + * If the failure was not due to the target file name already + * existing then we have bigger issues at hand so we keep + * the existing file. + */ + dbprintf(_("Cannot rename \"%s\" to \"%s\": %s\n"), + db_filename, s, strerror(errno)); + s = newvstralloc(s, db_filename, NULL); + i = -1; + break; + } + + /* + * Files already exists: + * Continue searching for a unique file name that will work. + */ + amfree(db_name); + if ((db_name = get_debug_name(open_time, i++)) == NULL) { + dbprintf(_("Cannot create unique debug file name")); + break; + } + s = newvstralloc(s, dbgdir, db_name, NULL); } - while(--i >= 0) { - close(fd_close[i]); + if (i >= 0) { + /* + * We need to close and reopen the original file handle to + * release control of the original debug file name. + */ + if ((fd = open(s, O_WRONLY|O_APPEND, 0640)) >= 0) { + /* + * We can safely close the the original log file + * since we now have a new working handle. + */ + db_fd = 2; + fclose(db_file); + db_file = NULL; + } + } +#else + /* check if a file with the same name already exists. */ + if ((fd = open(s, O_WRONLY|O_CREAT|O_EXCL|O_APPEND, 0640)) < 0) { + for(i = 0; fd < 0; i++) { + amfree(db_name); + if ((db_name = get_debug_name(open_time, i)) == NULL) { + dbprintf(_("Cannot create debug file")); + break; + } + + s = newvstralloc(s, dbgdir, db_name, NULL); + if ((fd = open(s, O_WRONLY|O_CREAT|O_EXCL|O_APPEND, 0640)) < 0) { + if (errno != EEXIST) { + dbprintf(_("Cannot create debug file: %s"), + strerror(errno)); + break; + } + } + } + } + + if (fd >= 0) { + close(fd); + if (rename(db_filename, s) == -1) { + dbprintf(_("Cannot rename \"%s\" to \"%s\": %s\n"), + db_filename, s, strerror(errno)); + } + fd = -1; } - db_file = fdopen(db_fd, "a"); +#endif + (void)umask(mask); /* Restore mask */ /* - * Make the first debug log file entry. + * Finish setup. + * + * Note: we release control of the string 's' points to. */ - saved_debug = debug; debug = 1; - debug_printf("%s: debug %d pid %ld ruid %ld euid %ld: start at %s", - pname, saved_debug, (long)getpid(), - (long)getuid(), (long)geteuid(), - ctime(&curtime)); - debug = saved_debug; + debug_setup_2(s, fd, "rename"); } -void debug_close() +void +debug_close(void) { time_t curtime; - int save_debug; - pid_t save_pid; time(&curtime); - save_debug = debug; - debug = 1; - save_pid = debug_prefix_pid; - debug_prefix_pid = 0; - debug_printf("%s: pid %ld finish time %s", - debug_prefix_time(NULL), - (long)getpid(), - ctime(&curtime)); - debug_prefix_pid = save_pid; - debug = save_debug; + debug_printf(_("pid %ld finish time %s"), (long)getpid(), ctime(&curtime)); if(db_file && fclose(db_file) == EOF) { int save_errno = errno; db_file = NULL; /* prevent recursion */ - error("close debug file: %s", strerror(save_errno)); + g_fprintf(stderr, _("close debug file: %s"), strerror(save_errno)); + /*NOTREACHED*/ } - db_fd = -1; + db_fd = 2; db_file = NULL; amfree(db_filename); + amfree(db_name); } -int debug_fd() +/* + * Format and write a debug message to the process debug file. + */ +printf_arglist_function(void debug_printf, const char *, format) { - return db_fd; -} + va_list argp; + int save_errno; -FILE *debug_fp() -{ - return db_file; + /* + * It is common in the code to call dbprintf to write out + * syserrno(errno) and then turn around and try to do something else + * with errno (e.g. g_printf() or log()), so we make sure errno goes + * back out with the same value it came in with. + */ + + save_errno = errno; + + /* handle the default (stderr) if debug_open hasn't been called yet */ + if(db_file == NULL && db_fd == 2) { + db_file = stderr; + } + if(db_file != NULL) { + g_fprintf(db_file, "%s: %s: ", msg_timestamp(), get_pname()); + arglist_start(argp, format); + g_vfprintf(db_file, format, argp); + arglist_end(argp); + fflush(db_file); + } + errno = save_errno; } -char *debug_fn() +int +debug_fd(void) { - return db_filename; + return db_fd; } -/* - * Routines for returning a common debug file line prefix. Always starts - * with the current program name, possibly with an optional suffix. - * May then be followed by a PID. May then be followed by an elapsed - * time indicator. - */ - -void set_debug_prefix_pid(p) - pid_t p; +FILE * +debug_fp(void) { - debug_prefix_pid = p; + return db_file; } -char *debug_prefix(suffix) - char *suffix; +char * +debug_fn(void) { - static char *s = NULL; - char debug_pid[NUM_STR_SIZE]; - - s = newvstralloc(s, get_pname(), suffix, NULL); - if (debug_prefix_pid != (pid_t) 0) { - ap_snprintf(debug_pid, sizeof(debug_pid), - "%ld", - (long) debug_prefix_pid); - s = newvstralloc(s, s, "[", debug_pid, "]", NULL); - } - return s; + return db_filename; } -char *debug_prefix_time(suffix) - char *suffix; +void +debug_dup_stderr_to_debug(void) { - static char *s = NULL; - char *t1; - char *t2; - - if (clock_is_running()) { - t1 = ": time "; - t2 = walltime_str(curclock()); - } else { - t1 = t2 = NULL; + if(db_fd != -1 && db_fd != STDERR_FILENO) + { + if(dup2(db_fd, STDERR_FILENO) != STDERR_FILENO) + { + error(_("can't redirect stderr to the debug file")); + g_assert_not_reached(); + } } - - s = newvstralloc(s, debug_prefix(suffix), t1, t2, NULL); - - return s; } -#endif +