X-Git-Url: http://sjero.net/git/?a=blobdiff_plain;f=src%2Flog.c;h=4befb998d85ba1c533a9daaac09cda02319d8ce1;hb=5f0a2b3f0846dd4c2f72fc62e7171200d1fd6e06;hp=8c159af28bfc663c1e3475cf4b5c3551cda33d04;hpb=31d6616c483359af431f4c33c3c5b237cd8d4426;p=wget diff --git a/src/log.c b/src/log.c index 8c159af2..4befb998 100644 --- a/src/log.c +++ b/src/log.c @@ -1,24 +1,47 @@ /* Messages logging. - Copyright (C) 1998 Free Software Foundation, Inc. + Copyright (C) 1998, 2000, 2001 Free Software Foundation, Inc. -This file is part of Wget. +This file is part of GNU Wget. -This program is free software; you can redistribute it and/or modify +GNU Wget 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 2 of the License, or (at your option) any later version. -This program is distributed in the hope that it will be useful, +GNU Wget 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, write to the Free Software -Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ +along with Wget; if not, write to the Free Software +Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +In addition, as a special exception, the Free Software Foundation +gives permission to link the code of its release of Wget with the +OpenSSL project's "OpenSSL" library (or with modified versions of it +that use the same license as the "OpenSSL" library), and distribute +the linked executables. You must obey the GNU General Public License +in all respects for all of the code used other than "OpenSSL". If you +modify this file, you may extend this exception to your version of the +file, but you are not obligated to do so. If you do not wish to do +so, delete this exception statement from your version. */ #include +/* This allows the architecture-specific .h files to specify the use + of stdargs regardless of __STDC__. */ +#ifndef WGET_USE_STDARG +/* Use stdarg only if the compiler supports ANSI C and stdarg.h is + present. We check for both because there are configurations where + stdarg.h exists, but doesn't work. */ +# ifdef __STDC__ +# ifdef HAVE_STDARG_H +# define WGET_USE_STDARG +# endif +# endif +#endif /* not WGET_USE_STDARG */ + #include #ifdef HAVE_STRING_H # include @@ -26,9 +49,7 @@ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ # include #endif #include -#if defined(__STDC__) && defined(HAVE_STDARG_H) -/* If we have __STDC__ and stdarg.h, we'll assume it's an ANSI system. */ -# define USE_STDARG +#ifdef WGET_USE_STDARG # include #else # include @@ -41,30 +62,219 @@ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ #include "wget.h" #include "utils.h" +#include "log.h" #ifndef errno extern int errno; #endif -/* We expect no message passed to logprintf() to be bigger than this. - Before a message is printed, we make sure that at least this much - room is left for printing it. */ -#define SAVED_LOG_MAXMSG 32768 +/* This file impplement support for "logging". Logging means printing + output, plus several additional features: + + - Cataloguing output by importance. You can specify that a log + message is "verbose" or "debug", and it will not be printed unless + in verbose or debug mode, respectively. -/* Maximum allowed growing size. */ -#define SAVED_LOG_MAXSIZE (10 * 1L << 20) + - Redirecting the log to the file. When Wget's output goes to the + terminal, and Wget receives SIGHUP, all further output is + redirected to a log file. When this is the case, Wget can also + print the last several lines of "context" to the log file so that + it does not begin in the middle of a line. For this to work, the + logging code stores the last several lines of context. Callers may + request for certain output not to be stored. -static char *saved_log; -/* Size of the current log. */ -static long saved_log_size; -/* Offset into the log where we are about to print (size of the - used-up part of SAVED_LOG). */ -static long saved_log_offset; -/* Whether logging is saved at all. */ -int save_log_p; + - Inhibiting output. When Wget receives SIGHUP, but redirecting + the output fails, logging is inhibited. */ + +/* The file descriptor used for logging. This is NULL before log_init + is called; logging functions log to stderr then. log_init sets it + either to stderr or to a file pointer obtained from fopen(). If + logging is inhibited, logfp is set back to NULL. */ static FILE *logfp; +/* If non-zero, it means logging is inhibited, i.e. nothing is printed + or stored. */ +static int inhibit_logging; + +/* Whether the last output lines are stored for use as context. */ +static int save_context_p; + +/* Whether the log is flushed after each command. */ +static int flush_log_p = 1; + +/* Whether any output has been received while flush_log_p was 0. */ +static int needs_flushing; + +/* In the event of a hang-up, and if its output was on a TTY, Wget + redirects its output to `wget-log'. + + For the convenience of reading this newly-created log, we store the + last several lines ("screenful", hence the choice of 24) of Wget + output, and dump them as context when the time comes. */ +#define SAVED_LOG_LINES 24 + +/* log_lines is a circular buffer that stores SAVED_LOG_LINES lines of + output. log_line_current always points to the position in the + buffer that will be written to next. When log_line_current reaches + SAVED_LOG_LINES, it is reset to zero. + + The problem here is that we'd have to either (re)allocate and free + strings all the time, or limit the lines to an arbitrary number of + characters. Instead of settling for either of these, we do both: + if the line is smaller than a certain "usual" line length (128 + chars by default), a preallocated memory is used. The rare lines + that are longer than 128 characters are malloc'ed and freed + separately. This gives good performance with minimum memory + consumption and fragmentation. */ + +#define STATIC_LENGTH 128 + +static struct log_ln { + char static_line[STATIC_LENGTH + 1]; /* statically allocated + line. */ + char *malloced_line; /* malloc'ed line, for lines of output + larger than 80 characters. */ + char *content; /* this points either to malloced_line + or to the appropriate static_line. + If this is NULL, it means the line + has not yet been used. */ +} log_lines[SAVED_LOG_LINES]; + +/* The current position in the ring. */ +static int log_line_current = -1; + +/* Whether the most recently written line was "trailing", i.e. did not + finish with \n. This is an important piece of information because + the code is always careful to append data to trailing lines, rather + than create new ones. */ +static int trailing_line; + +static void check_redirect_output PARAMS ((void)); + +#define ROT_ADVANCE(num) do { \ + if (++num >= SAVED_LOG_LINES) \ + num = 0; \ +} while (0) + +/* Free the log line index with NUM. This calls free on + ln->malloced_line if it's non-NULL, and it also resets + ln->malloced_line and ln->content to NULL. */ + +static void +free_log_line (int num) +{ + struct log_ln *ln = log_lines + num; + if (ln->malloced_line) + { + xfree (ln->malloced_line); + ln->malloced_line = NULL; + } + ln->content = NULL; +} + +/* Append bytes in the range [start, end) to one line in the log. The + region is not supposed to contain newlines, except for the last + character (at end[-1]). */ + +static void +saved_append_1 (const char *start, const char *end) +{ + int len = end - start; + if (!len) + return; + + /* First, check whether we need to append to an existing line or to + create a new one. */ + if (!trailing_line) + { + /* Create a new line. */ + struct log_ln *ln; + + if (log_line_current == -1) + log_line_current = 0; + else + free_log_line (log_line_current); + ln = log_lines + log_line_current; + if (len > STATIC_LENGTH) + { + ln->malloced_line = strdupdelim (start, end); + ln->content = ln->malloced_line; + } + else + { + memcpy (ln->static_line, start, len); + ln->static_line[len] = '\0'; + ln->content = ln->static_line; + } + } + else + { + /* Append to the last line. If the line is malloc'ed, we just + call realloc and append the new string. If the line is + static, we have to check whether appending the new string + would make it exceed STATIC_LENGTH characters, and if so, + convert it to malloc(). */ + struct log_ln *ln = log_lines + log_line_current; + if (ln->malloced_line) + { + /* Resize malloc'ed line and append. */ + int old_len = strlen (ln->malloced_line); + ln->malloced_line = xrealloc (ln->malloced_line, old_len + len + 1); + memcpy (ln->malloced_line + old_len, start, len); + ln->malloced_line[old_len + len] = '\0'; + /* might have changed due to realloc */ + ln->content = ln->malloced_line; + } + else + { + int old_len = strlen (ln->static_line); + if (old_len + len > STATIC_LENGTH) + { + /* Allocate memory and concatenate the old and the new + contents. */ + ln->malloced_line = (char *)xmalloc (old_len + len + 1); + memcpy (ln->malloced_line, ln->static_line, + old_len); + memcpy (ln->malloced_line + old_len, start, len); + ln->malloced_line[old_len + len] = '\0'; + ln->content = ln->malloced_line; + } + else + { + /* Just append to the old, statically allocated + contents. */ + memcpy (ln->static_line + old_len, start, len); + ln->static_line[old_len + len] = '\0'; + ln->content = ln->static_line; + } + } + } + trailing_line = !(end[-1] == '\n'); + if (!trailing_line) + ROT_ADVANCE (log_line_current); +} + +/* Log the contents of S, as explained above. If S consists of + multiple lines, they are logged separately. If S does not end with + a newline, it will form a "trailing" line, to which things will get + appended the next time this function is called. */ + +static void +saved_append (const char *s) +{ + while (*s) + { + const char *end = strchr (s, '\n'); + if (!end) + end = s + strlen (s); + else + ++end; + saved_append_1 (s, end); + s = end; + } +} + /* Check X against opt.verbose and opt.quiet. The semantics is as follows: @@ -93,143 +303,240 @@ static FILE *logfp; return; \ } -#define CANONICALIZE_LOGFP_OR_RETURN do { \ - if (logfp == stdin) \ - return; \ - else if (!logfp) \ - /* #### Should this ever happen? */ \ - logfp = stderr; \ -} while (0) +/* Returns the file descriptor for logging. This is LOGFP, except if + called before log_init, in which case it returns stderr. This is + useful in case someone calls a logging function before log_init. + + If logging is inhibited, return NULL. */ +static FILE * +get_log_fp (void) +{ + if (inhibit_logging) + return NULL; + if (logfp) + return logfp; + return stderr; +} +/* Log a literal string S. The string is logged as-is, without a + newline appended. */ + void logputs (enum log_options o, const char *s) { + FILE *fp; + + check_redirect_output (); + if (!(fp = get_log_fp ())) + return; CHECK_VERBOSE (o); - CANONICALIZE_LOGFP_OR_RETURN; - fputs (s, logfp); - if (!opt.no_flush) - fflush (logfp); + fputs (s, fp); + if (save_context_p) + saved_append (s); + if (flush_log_p) + logflush (); + else + needs_flushing = 1; +} + +struct logvprintf_state { + char *bigmsg; + int expected_size; + int allocated; +}; - if (save_log_p && saved_log_size < SAVED_LOG_MAXSIZE) - { - int len = strlen (s); +/* Print a message to the log. A copy of message will be saved to + saved_log, for later reusal by log_dump_context(). - /* Increase size of SAVED_LOG exponentially. */ - DO_REALLOC (saved_log, saved_log_size, - saved_log_offset + len + 1, char); - memcpy (saved_log + saved_log_offset, s, len + 1); - saved_log_offset += len; - } -} + It is not possible to code this function in a "natural" way, using + a loop, because of the braindeadness of the varargs API. + Specifically, each call to vsnprintf() must be preceded by va_start + and followed by va_end. And this is possible only in the function + that contains the `...' declaration. The alternative would be to + use va_copy, but that's not portable. */ -/* Print a message to the log file logfp. If logfp is NULL, print to - stderr. If logfp is stdin, don't print at all. A copy of message - will be saved to saved_log, for later reusal by dump_log(). */ -static void -logvprintf (enum log_options o, const char *fmt, va_list args) +static int +logvprintf (struct logvprintf_state *state, const char *fmt, va_list args) { - CHECK_VERBOSE (o); - CANONICALIZE_LOGFP_OR_RETURN; + char smallmsg[128]; + char *write_ptr = smallmsg; + int available_size = sizeof (smallmsg); + int numwritten; + FILE *fp = get_log_fp (); + + if (!save_context_p) + { + /* In the simple case just call vfprintf(), to avoid needless + allocation and games with vsnprintf(). */ + vfprintf (fp, fmt, args); + goto flush; + } + + if (state->allocated != 0) + { + write_ptr = state->bigmsg; + available_size = state->allocated; + } - /* Originally, we first used vfprintf(), and then checked whether - the message needs to be stored with vsprintf(). However, Watcom - C didn't like ARGS being used twice, so now we first vsprintf() - the message, and then fwrite() it to LOGFP. */ - if (save_log_p && saved_log_size < SAVED_LOG_MAXSIZE) + /* The GNU coding standards advise not to rely on the return value + of sprintf(). However, vsnprintf() is a relatively new function + missing from legacy systems. Therefore I consider it safe to + assume that its return value is meaningful. On the systems where + vsnprintf() is not available, we use the implementation from + snprintf.c which does return the correct value. */ + numwritten = vsnprintf (write_ptr, available_size, fmt, args); + + /* vsnprintf() will not step over the limit given by available_size. + If it fails, it will return either -1 (POSIX?) or the number of + characters that *would have* been written, if there had been + enough room (C99). In the former case, we double the + available_size and malloc to get a larger buffer, and try again. + In the latter case, we use the returned information to build a + buffer of the correct size. */ + + if (numwritten == -1) + { + /* Writing failed, and we don't know the needed size. Try + again with doubled size. */ + int newsize = available_size << 1; + state->bigmsg = xrealloc (state->bigmsg, newsize); + state->allocated = newsize; + return 0; + } + else if (numwritten >= available_size) { - int len; - /* Increase size of `saved_log' exponentially. */ - DO_REALLOC (saved_log, saved_log_size, - saved_log_offset + SAVED_LOG_MAXMSG, char); - /* Print the message to the log saver... */ -#ifdef HAVE_VSNPRINTF - vsnprintf (saved_log + saved_log_offset, SAVED_LOG_MAXMSG, fmt, args); -#else /* not HAVE_VSNPRINTF */ - vsprintf (saved_log + saved_log_offset, fmt, args); -#endif /* not HAVE_VSNPRINTF */ - /* ...and then dump it to LOGFP. */ - len = strlen (saved_log + saved_log_offset); - fwrite (saved_log + saved_log_offset, len, 1, logfp); - saved_log_offset += len; - /* If we ran off the limits and corrupted something, bail out - immediately. */ - assert (saved_log_size >= saved_log_offset); + /* Writing failed, but we know exactly how much space we + need. */ + int newsize = numwritten + 1; + state->bigmsg = xrealloc (state->bigmsg, newsize); + state->allocated = newsize; + return 0; } + + /* Writing succeeded. */ + saved_append (write_ptr); + fputs (write_ptr, fp); + if (state->bigmsg) + xfree (state->bigmsg); + + flush: + if (flush_log_p) + logflush (); else - vfprintf (logfp, fmt, args); + needs_flushing = 1; - if (!opt.no_flush) - fflush (logfp); + return 1; } -/* Flush LOGFP. */ +/* Flush LOGFP. Useful while flushing is disabled. */ void logflush (void) { - CANONICALIZE_LOGFP_OR_RETURN; - fflush (logfp); + FILE *fp = get_log_fp (); + if (fp) + fflush (fp); + needs_flushing = 0; +} + +/* Enable or disable log flushing. */ +void +log_set_flush (int flush) +{ + if (flush == flush_log_p) + return; + + if (flush == 0) + { + /* Disable flushing by setting flush_log_p to 0. */ + flush_log_p = 0; + } + else + { + /* Reenable flushing. If anything was printed in no-flush mode, + flush the log now. */ + if (needs_flushing) + logflush (); + flush_log_p = 1; + } +} + +/* (Temporarily) disable storing log to memory. Returns the old + status of storing, with which this function can be called again to + reestablish storing. */ + +int +log_set_save_context (int savep) +{ + int old = save_context_p; + save_context_p = savep; + return old; } -/* Portability makes these two functions look like @#%#@$@#$. */ +/* Handle difference in va_start between pre-ANSI and ANSI C. Note + that we always use `...' in function definitions and let ansi2knr + convert it for us. */ + +#ifdef WGET_USE_STDARG +# define VA_START(args, arg1) va_start (args, arg1) +#else +# define VA_START(args, ignored) va_start (args) +#endif + +/* Print a message to the screen or to the log. The first argument + defines the verbosity of the message, and the rest are as in + printf(3). */ -#ifdef USE_STDARG void logprintf (enum log_options o, const char *fmt, ...) -#else /* not USE_STDARG */ -void -logprintf (va_alist) - va_dcl -#endif /* not USE_STDARG */ { va_list args; -#ifndef USE_STDARG - enum log_options o; - const char *fmt; -#endif + struct logvprintf_state lpstate; + int done; -#ifdef USE_STDARG - va_start (args, fmt); -#else - va_start (args); - o = va_arg (args, enum log_options); - fmt = va_arg (args, char *); -#endif - logvprintf (o, fmt, args); - va_end (args); + check_redirect_output (); + if (inhibit_logging) + return; + CHECK_VERBOSE (o); + + xzero (lpstate); + do + { + VA_START (args, fmt); + done = logvprintf (&lpstate, fmt, args); + va_end (args); + } + while (!done); } -#ifdef DEBUG +#ifdef ENABLE_DEBUG /* The same as logprintf(), but does anything only if opt.debug is non-zero. */ -#ifdef USE_STDARG void debug_logprintf (const char *fmt, ...) -#else /* not USE_STDARG */ -void -debug_logprintf (va_alist) - va_dcl -#endif /* not USE_STDARG */ { if (opt.debug) { va_list args; -#ifndef USE_STDARG - const char *fmt; -#endif + struct logvprintf_state lpstate; + int done; -#ifdef USE_STDARG - va_start (args, fmt); -#else - va_start (args); - fmt = va_arg (args, char *); -#endif - logvprintf (LOG_ALWAYS, fmt, args); - va_end (args); + check_redirect_output (); + if (inhibit_logging) + return; + + xzero (lpstate); + do + { + VA_START (args, fmt); + done = logvprintf (&lpstate, fmt, args); + va_end (args); + } + while (!done); } } -#endif /* DEBUG */ +#endif /* ENABLE_DEBUG */ /* Open FILE and set up a logging stream. If FILE cannot be opened, exit with status of 1. */ @@ -247,18 +554,25 @@ log_init (const char *file, int appendp) } else { + /* The log goes to stderr to avoid collisions with the output if + the user specifies `-O -'. #### Francois Pinard suggests + that it's a better idea to print to stdout by default, and to + stderr only if the user actually specifies `-O -'. He says + this inconsistency is harder to document, but is overall + easier on the user. */ logfp = stderr; - /* If the output is a TTY, enable logging, which will make Wget - remember all the printed messages, to be able to dump them to - a log file in case SIGHUP or SIGUSR1 is received (or - Ctrl+Break is pressed under Windows). */ + + /* If the output is a TTY, enable storing, which will make Wget + remember the last several printed messages, to be able to + dump them to a log file in case SIGHUP or SIGUSR1 is received + (or Ctrl+Break is pressed under Windows). */ if (1 #ifdef HAVE_ISATTY && isatty (fileno (logfp)) #endif ) { - save_log_p = 1; + save_context_p = 1; } } } @@ -268,50 +582,101 @@ log_init (const char *file, int appendp) void log_close (void) { - fclose (logfp); - save_log_p = 0; - FREE_MAYBE (saved_log); - saved_log = NULL; - saved_log_size = saved_log_offset = 0; + int i; + + if (logfp) + fclose (logfp); + logfp = NULL; + inhibit_logging = 1; + save_context_p = 0; + + for (i = 0; i < SAVED_LOG_LINES; i++) + free_log_line (i); + log_line_current = -1; + trailing_line = 0; } -/* Dump SAVED_LOG using logprintf(), but quit further logging to memory. - Also, free the memory occupied by saved_log. */ +/* Dump saved lines to logfp. */ static void -log_dump (void) +log_dump_context (void) { - save_log_p = 0; - if (!saved_log) + int num = log_line_current; + FILE *fp = get_log_fp (); + if (!fp) + return; + + if (num == -1) return; - logputs (LOG_ALWAYS, saved_log); - free (saved_log); - saved_log = NULL; - saved_log_size = saved_log_offset = 0; + if (trailing_line) + ROT_ADVANCE (num); + do + { + struct log_ln *ln = log_lines + num; + if (ln->content) + fputs (ln->content, fp); + ROT_ADVANCE (num); + } + while (num != log_line_current); + if (trailing_line) + if (log_lines[log_line_current].content) + fputs (log_lines[log_line_current].content, fp); + fflush (fp); } + +/* When SIGHUP or SIGUSR1 are received, the output is redirected + elsewhere. Such redirection is only allowed once. */ +enum { RR_NONE, RR_REQUESTED, RR_DONE } redirect_request = RR_NONE; +static const char *redirect_request_signal_name; -/* Redirect output to `wget-log' if opt.lfile is stdout. MESSIJ is - printed on stdout, and should contain *exactly one* `%s', which - will be replaced by the log file name. +/* Redirect output to `wget-log'. */ - If logging was not enabled, MESSIJ will not be printed. */ -void -redirect_output (const char *messij) +static void +redirect_output (void) { - char *logfile; - - if (!save_log_p) - return; - - logfile = unique_name (DEFAULT_LOGFILE); + char *logfile = unique_name (DEFAULT_LOGFILE, 0); + fprintf (stderr, _("\n%s received, redirecting output to `%s'.\n"), + redirect_request_signal_name, logfile); logfp = fopen (logfile, "w"); if (!logfp) { - printf ("%s: %s: %s\n", exec_name, logfile, strerror (errno)); - /* `stdin' is magic to not print anything. */ - logfp = stdin; + /* Eek! Opening the alternate log file has failed. Nothing we + can do but disable printing completely. */ + fprintf (stderr, _("%s: %s; disabling logging.\n"), + logfile, strerror (errno)); + inhibit_logging = 1; + } + else + { + /* Dump the context output to the newly opened log. */ + log_dump_context (); + } + xfree (logfile); + save_context_p = 0; +} + +/* Check whether a signal handler requested the output to be + redirected. */ + +static void +check_redirect_output (void) +{ + if (redirect_request == RR_REQUESTED) + { + redirect_request = RR_DONE; + redirect_output (); } - printf (messij, logfile); - free (logfile); - /* Dump all the previous messages to LOGFILE. */ - log_dump (); +} + +/* Request redirection at a convenient time. This may be called from + a signal handler. */ + +void +log_request_redirect_output (const char *signal_name) +{ + if (redirect_request == RR_NONE && save_context_p) + /* Request output redirection. The request will be processed by + check_redirect_output(), which is called from entry point log + functions. */ + redirect_request = RR_REQUESTED; + redirect_request_signal_name = signal_name; }