]> sjero.net Git - wget/blobdiff - src/log.c
Fix build when libpsl is not available
[wget] / src / log.c
index 8c159af28bfc663c1e3475cf4b5c3551cda33d04..3bd583305dedff4d814546ce53d7466ae73a7397 100644 (file)
--- a/src/log.c
+++ b/src/log.c
 /* Messages logging.
-   Copyright (C) 1998 Free Software Foundation, Inc.
+   Copyright (C) 1998, 1999, 2000, 2001, 2002, 2003, 2004, 2005, 2006,
+   2007, 2008, 2009, 2010, 2011 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
+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,
+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, see <http://www.gnu.org/licenses/>.
 
-#include <config.h>
+Additional permission under GNU GPL version 3 section 7
+
+If you modify this program, or any covered work, by linking or
+combining it with the OpenSSL project's OpenSSL library (or a
+modified version of that library), containing parts covered by the
+terms of the OpenSSL or SSLeay licenses, the Free Software Foundation
+grants you additional permission to convey the resulting work.
+Corresponding Source for a non-source form of such a combination
+shall include the source code for the parts of OpenSSL used as well
+as that of the covered work.  */
+
+#include "wget.h"
 
 #include <stdio.h>
-#ifdef HAVE_STRING_H
-# include <string.h>
-#else
-# include <strings.h>
-#endif
+#include <string.h>
 #include <stdlib.h>
-#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
-# include <stdarg.h>
-#else
-# include <varargs.h>
-#endif
-#ifdef HAVE_UNISTD_H
-# include <unistd.h>
-#endif
+#include <stdarg.h>
+#include <unistd.h>
 #include <assert.h>
 #include <errno.h>
 
-#include "wget.h"
 #include "utils.h"
+#include "log.h"
+
+/* 2005-10-25 SMS.
+   VMS log files are often VFC record format, not stream, so fputs() can
+   produce multiple records, even when there's no newline terminator in
+   the buffer.  The result is unsightly output with spurious newlines.
+   Using fprintf() instead of fputs(), along with inhibiting some
+   fflush() activity below, seems to solve the problem.
+*/
+#ifdef __VMS
+# define FPUTS( s, f) fprintf( (f), "%s", (s))
+#else /* def __VMS */
+# define FPUTS( s, f) fputs( (s), (f))
+#endif /* def __VMS [else] */
+
+/* This file implements 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.
+
+   - 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.
+
+   - Inhibiting output.  When Wget receives SIGHUP, but redirecting
+   the output fails, logging is inhibited.  */
 
-#ifndef errno
-extern int errno;
-#endif
+\f
+/* 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;
 
-/* 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
+/* A second file descriptor pointing to the temporary log file for the
+   WARC writer.  If WARC writing is disabled, this is NULL.  */
+static FILE *warclogfp;
+
+/* If true, it means logging is inhibited, i.e. nothing is printed or
+   stored.  */
+static bool inhibit_logging;
+
+/* Whether the last output lines are stored for use as context.  */
+static bool save_context_p;
+
+/* Whether the log is flushed after each command. */
+static bool flush_log_p = true;
+
+/* Whether any output has been received while flush_log_p was 0. */
+static bool 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 bool trailing_line;
+
+static void check_redirect_output (void);
+\f
+#define ROT_ADVANCE(num) do {                   \
+  if (++num >= SAVED_LOG_LINES)                 \
+    num = 0;                                    \
+} while (0)
 
-/* Maximum allowed growing size.  */
-#define SAVED_LOG_MAXSIZE (10 * 1L << 20)
+/* 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 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;
+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;
+}
 
-static FILE *logfp;
+/* 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 = 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;
+    }
+}
+\f
 /* Check X against opt.verbose and opt.quiet.  The semantics is as
    follows:
 
@@ -75,243 +275,635 @@ static FILE *logfp;
    * LOG_NONVERBOSE - print the message if opt.verbose is zero;
 
    * LOG_VERBOSE - print the message if opt.verbose is non-zero.  */
-#define CHECK_VERBOSE(x)                       \
-  switch (x)                                   \
-    {                                          \
-    case LOG_ALWAYS:                           \
-      break;                                   \
-    case LOG_NOTQUIET:                         \
-      if (opt.quiet)                           \
-       return;                                 \
-      break;                                   \
-    case LOG_NONVERBOSE:                       \
-      if (opt.verbose || opt.quiet)            \
-       return;                                 \
-      break;                                   \
-    case LOG_VERBOSE:                          \
-      if (!opt.verbose)                                \
-       return;                                 \
+#define CHECK_VERBOSE(x)                        \
+  switch (x)                                    \
+    {                                           \
+    case LOG_PROGRESS:                          \
+      if (!opt.show_progress)                   \
+        return;                                 \
+      break;                                    \
+    case LOG_ALWAYS:                            \
+      break;                                    \
+    case LOG_NOTQUIET:                          \
+      if (opt.quiet)                            \
+        return;                                 \
+      break;                                    \
+    case LOG_NONVERBOSE:                        \
+      if (opt.verbose || opt.quiet)             \
+        return;                                 \
+      break;                                    \
+    case LOG_VERBOSE:                           \
+      if (!opt.verbose)                         \
+        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;
+}
+
+/* Returns the file descriptor for the secondary log file. This is
+   WARCLOGFP, 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_warc_log_fp (void)
+{
+  if (inhibit_logging)
+    return NULL;
+  if (warclogfp)
+    return warclogfp;
+  return NULL;
+}
+
+/* Sets the file descriptor for the secondary log file.  */
 
+void
+log_set_warc_log_fp (FILE * fp)
+{
+  warclogfp = fp;
+}
 \f
+/* 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;
+  FILE *warcfp;
+
+  check_redirect_output ();
+  if ((fp = get_log_fp ()) == NULL)
+    return;
+  warcfp = get_warc_log_fp ();
   CHECK_VERBOSE (o);
-  CANONICALIZE_LOGFP_OR_RETURN;
 
-  fputs (s, logfp);
-  if (!opt.no_flush)
-    fflush (logfp);
+  FPUTS (s, fp);
+  if (warcfp != NULL)
+    FPUTS (s, warcfp);
+  if (save_context_p)
+    saved_append (s);
+  if (flush_log_p)
+    logflush ();
+  else
+    needs_flushing = true;
+}
 
-  if (save_log_p && saved_log_size < SAVED_LOG_MAXSIZE)
+struct logvprintf_state {
+  char *bigmsg;
+  int expected_size;
+  int allocated;
+};
+
+/* Print a message to the log.  A copy of message will be saved to
+   saved_log, for later reusal by log_dump_context().
+
+   Normally we'd want this function to loop around vsnprintf until
+   sufficient room is allocated, as the Linux man page recommends.
+   However each call to vsnprintf() must be preceded by va_start and
+   followed by va_end.  Since calling va_start/va_end is possible only
+   in the function that contains the `...' declaration, we cannot call
+   vsnprintf more than once.  Therefore this function saves its state
+   to logvprintf_state and signals the parent to call it again.
+
+   (An alternative approach would be to use va_copy, but that's not
+   portable.)  */
+
+static bool
+log_vprintf_internal (struct logvprintf_state *state, const char *fmt,
+                      va_list args)
+{
+  char smallmsg[128];
+  char *write_ptr = smallmsg;
+  int available_size = sizeof (smallmsg);
+  int numwritten;
+  FILE *fp = get_log_fp ();
+  FILE *warcfp = get_warc_log_fp ();
+
+  if (!save_context_p && warcfp == NULL)
     {
-      int len = strlen (s);
-
-      /* 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;
+      /* In the simple case just call vfprintf(), to avoid needless
+         allocation and games with vsnprintf(). */
+      vfprintf (fp, fmt, args);
+      goto flush;
     }
-}
 
-/* 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)
-{
-  CHECK_VERBOSE (o);
-  CANONICALIZE_LOGFP_OR_RETURN;
+  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 returns either -1 (older implementations) or the
+     number of characters (not counting the terminating \0) that
+     *would have* been written if there had been enough room (C99).
+     In the former case, we double 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 false;
+    }
+  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 false;
     }
+
+  /* Writing succeeded. */
+  if (save_context_p)
+    saved_append (write_ptr);
+  FPUTS (write_ptr, fp);
+  if (warcfp != NULL)
+    FPUTS (write_ptr, warcfp);
+  if (state->bigmsg)
+    xfree (state->bigmsg);
+
+ flush:
+  if (flush_log_p)
+    logflush ();
   else
-    vfprintf (logfp, fmt, args);
+    needs_flushing = true;
 
-  if (!opt.no_flush)
-    fflush (logfp);
+  return true;
 }
 
-/* Flush LOGFP.  */
+/* Flush LOGFP.  Useful while flushing is disabled.  */
 void
 logflush (void)
 {
-  CANONICALIZE_LOGFP_OR_RETURN;
-  fflush (logfp);
+  FILE *fp = get_log_fp ();
+  FILE *warcfp = get_warc_log_fp ();
+  if (fp)
+    {
+/* 2005-10-25 SMS.
+   On VMS, flush only for a terminal.  See note at FPUTS macro, above.
+*/
+#ifdef __VMS
+      if (isatty( fileno( fp)))
+        {
+          fflush (fp);
+        }
+#else /* def __VMS */
+      fflush (fp);
+#endif /* def __VMS [else] */
+    }
+
+  if (warcfp != NULL)
+    fflush (warcfp);
+
+  needs_flushing = false;
 }
 
-/* Portability makes these two functions look like @#%#@$@#$.  */
+/* Enable or disable log flushing. */
+void
+log_set_flush (bool flush)
+{
+  if (flush == flush_log_p)
+    return;
+
+  if (flush == false)
+    {
+      /* Disable flushing by setting flush_log_p to 0. */
+      flush_log_p = false;
+    }
+  else
+    {
+      /* Reenable flushing.  If anything was printed in no-flush mode,
+         flush the log now.  */
+      if (needs_flushing)
+        logflush ();
+      flush_log_p = true;
+    }
+}
+
+/* (Temporarily) disable storing log to memory.  Returns the old
+   status of storing, with which this function can be called again to
+   reestablish storing. */
+
+bool
+log_set_save_context (bool savep)
+{
+  bool old = save_context_p;
+  save_context_p = savep;
+  return old;
+}
+
+/* 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;
+  bool 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 = log_vprintf_internal (&lpstate, fmt, args);
+      va_end (args);
+
+      if (done && errno == EPIPE)
+        exit (1);
+    }
+  while (!done);
 }
 
-#ifdef DEBUG
+#ifdef ENABLE_DEBUG
 /* The same as logprintf(), but does anything only if opt.debug is
-   non-zero.  */
-#ifdef USE_STDARG
+   true.  */
 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
-
-#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);
+      struct logvprintf_state lpstate;
+      bool done;
+
+      check_redirect_output ();
+      if (inhibit_logging)
+        return;
+
+      xzero (lpstate);
+      do
+        {
+          va_start (args, fmt);
+          done = log_vprintf_internal (&lpstate, fmt, args);
+          va_end (args);
+        }
+      while (!done);
     }
 }
-#endif /* DEBUG */
+#endif /* ENABLE_DEBUG */
 \f
 /* Open FILE and set up a logging stream.  If FILE cannot be opened,
    exit with status of 1.  */
 void
-log_init (const char *file, int appendp)
+log_init (const char *file, bool appendp)
 {
   if (file)
     {
       logfp = fopen (file, appendp ? "a" : "w");
       if (!logfp)
-       {
-         perror (opt.lfilename);
-         exit (1);
-       }
+        {
+          fprintf (stderr, "%s: %s: %s\n", exec_name, file, strerror (errno));
+          exit (1);
+        }
     }
   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 (1
 #ifdef HAVE_ISATTY
-         && isatty (fileno (logfp))
+          && isatty (fileno (logfp))
 #endif
-         )
-       {
-         save_log_p = 1;
-       }
+          )
+        {
+          /* If the output is a TTY, enable save context, i.e. store
+             the most recent several messages ("context") and dump
+             them to a log file in case SIGHUP or SIGUSR1 is received
+             (or Ctrl+Break is pressed under Windows).  */
+          save_context_p = true;
+        }
     }
 }
 
-/* Close LOGFP, inhibit further logging and free the memory associated
-   with it.  */
+/* Close LOGFP (only if we opened it, not if it's stderr), inhibit
+   further logging and free the memory associated with it.  */
 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 && (logfp != stderr))
+    fclose (logfp);
+  logfp = NULL;
+  inhibit_logging = true;
+  save_context_p = false;
+
+  for (i = 0; i < SAVED_LOG_LINES; i++)
+    free_log_line (i);
+  log_line_current = -1;
+  trailing_line = false;
 }
 
-/* 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 ();
+  FILE *warcfp = get_warc_log_fp ();
+  if (!fp)
     return;
-  logputs (LOG_ALWAYS, saved_log);
-  free (saved_log);
-  saved_log = NULL;
-  saved_log_size = saved_log_offset = 0;
+
+  if (num == -1)
+    return;
+  if (trailing_line)
+    ROT_ADVANCE (num);
+  do
+    {
+      struct log_ln *ln = log_lines + num;
+      if (ln->content)
+        {
+          FPUTS (ln->content, fp);
+          if (warcfp != NULL)
+            FPUTS (ln->content, warcfp);
+        }
+      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);
+        if (warcfp != NULL)
+          FPUTS (log_lines[log_line_current].content, warcfp);
+      }
+  fflush (fp);
+  fflush (warcfp);
 }
+\f
+/* String escape functions. */
 
-/* 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.
+/* Return the number of non-printable characters in SOURCE.
+   Non-printable characters are determined as per c-ctype.c.  */
+
+static int
+count_nonprint (const char *source)
+{
+  const char *p;
+  int cnt;
+  for (p = source, cnt = 0; *p; p++)
+    if (!c_isprint (*p))
+      ++cnt;
+  return cnt;
+}
+
+/* Copy SOURCE to DEST, escaping non-printable characters.
+
+   Non-printable refers to anything outside the non-control ASCII
+   range (32-126) which means that, for example, CR, LF, and TAB are
+   considered non-printable along with ESC, BS, and other control
+   chars.  This is by design: it makes sure that messages from remote
+   servers cannot be easily used to deceive the users by mimicking
+   Wget's output.  Disallowing non-ASCII characters is another
+   necessary security measure, which makes sure that remote servers
+   cannot garble the screen or guess the local charset and perform
+   homographic attacks.
+
+   Of course, the above mandates that escnonprint only be used in
+   contexts expected to be ASCII, such as when printing host names,
+   URL components, HTTP headers, FTP server messages, and the like.
+
+   ESCAPE is the leading character of the escape sequence.  BASE
+   should be the base of the escape sequence, and must be either 8 for
+   octal or 16 for hex.
+
+   DEST must point to a location with sufficient room to store an
+   encoded version of SOURCE.  */
+
+static void
+copy_and_escape (const char *source, char *dest, char escape, int base)
+{
+  const char *from = source;
+  char *to = dest;
+  unsigned char c;
+
+  /* Copy chars from SOURCE to DEST, escaping non-printable ones. */
+  switch (base)
+    {
+    case 8:
+      while ((c = *from++) != '\0')
+        if (c_isprint (c))
+          *to++ = c;
+        else
+          {
+            *to++ = escape;
+            *to++ = '0' + (c >> 6);
+            *to++ = '0' + ((c >> 3) & 7);
+            *to++ = '0' + (c & 7);
+          }
+      break;
+    case 16:
+      while ((c = *from++) != '\0')
+        if (c_isprint (c))
+          *to++ = c;
+        else
+          {
+            *to++ = escape;
+            *to++ = XNUM_TO_DIGIT (c >> 4);
+            *to++ = XNUM_TO_DIGIT (c & 0xf);
+          }
+      break;
+    default:
+      abort ();
+    }
+  *to = '\0';
+}
+
+#define RING_SIZE 3
+struct ringel {
+  char *buffer;
+  int size;
+};
+static struct ringel ring[RING_SIZE];   /* ring data */
+
+static const char *
+escnonprint_internal (const char *str, char escape, int base)
+{
+  static int ringpos;                   /* current ring position */
+  int nprcnt;
+
+  assert (base == 8 || base == 16);
+
+  nprcnt = count_nonprint (str);
+  if (nprcnt == 0)
+    /* If there are no non-printable chars in STR, don't bother
+       copying anything, just return STR.  */
+    return str;
+
+  {
+    /* Set up a pointer to the current ring position, so we can write
+       simply r->X instead of ring[ringpos].X. */
+    struct ringel *r = ring + ringpos;
+
+    /* Every non-printable character is replaced with the escape char
+       and three (or two, depending on BASE) *additional* chars.  Size
+       must also include the length of the original string and one
+       additional char for the terminating \0. */
+    int needed_size = strlen (str) + 1 + (base == 8 ? 3 * nprcnt : 2 * nprcnt);
+
+    /* If the current buffer is uninitialized or too small,
+       (re)allocate it.  */
+    if (r->buffer == NULL || r->size < needed_size)
+      {
+        r->buffer = xrealloc (r->buffer, needed_size);
+        r->size = needed_size;
+      }
+
+    copy_and_escape (str, r->buffer, escape, base);
+    ringpos = (ringpos + 1) % RING_SIZE;
+    return r->buffer;
+  }
+}
+
+/* Return a pointer to a static copy of STR with the non-printable
+   characters escaped as \ooo.  If there are no non-printable
+   characters in STR, STR is returned.  See copy_and_escape for more
+   information on which characters are considered non-printable.
+
+   DON'T call this function on translated strings because escaping
+   will break them.  Don't call it on literal strings from the source,
+   which are by definition trusted.  If newlines are allowed in the
+   string, escape and print it line by line because escaping the whole
+   string will convert newlines to \012.  (This is so that expectedly
+   single-line messages cannot use embedded newlines to mimic Wget's
+   output and deceive the user.)
+
+   escnonprint doesn't quote its escape character because it is notf
+   meant as a general and reversible quoting mechanism, but as a quick
+   way to defang binary junk sent by malicious or buggy servers.
+
+   NOTE: since this function can return a pointer to static data, be
+   careful to copy its result before calling it again.  However, to be
+   more useful with printf, it maintains an internal ring of static
+   buffers to return.  Currently the ring size is 3, which means you
+   can print up to three values in the same printf; if more is needed,
+   bump RING_SIZE.  */
+
+const char *
+escnonprint (const char *str)
+{
+  return escnonprint_internal (str, '\\', 8);
+}
+
+/* Return a pointer to a static copy of STR with the non-printable
+   characters escaped as %XX.  If there are no non-printable
+   characters in STR, STR is returned.
+
+   See escnonprint for usage details.  */
+
+const char *
+escnonprint_uri (const char *str)
+{
+  return escnonprint_internal (str, '%', 16);
+}
 
-   If logging was not enabled, MESSIJ will not be printed.  */
 void
-redirect_output (const char *messij)
+log_cleanup (void)
+{
+  size_t i;
+  for (i = 0; i < countof (ring); i++)
+    xfree_null (ring[i].buffer);
+}
+\f
+/* When SIGHUP or SIGUSR1 are received, the output is redirected
+   elsewhere.  Such redirection is only allowed once. */
+static enum { RR_NONE, RR_REQUESTED, RR_DONE } redirect_request = RR_NONE;
+static const char *redirect_request_signal_name;
+
+/* Redirect output to `wget-log'.  */
+
+static void
+redirect_output (void)
 {
   char *logfile;
+  logfp = unique_create (DEFAULT_LOGFILE, false, &logfile);
+  if (logfp)
+    {
+      fprintf (stderr, _("\n%s received, redirecting output to %s.\n"),
+               redirect_request_signal_name, quote (logfile));
+      xfree (logfile);
+      /* Dump the context output to the newly opened log.  */
+      log_dump_context ();
+    }
+  else
+    {
+      /* Eek!  Opening the alternate log file has failed.  Nothing we
+         can do but disable printing completely. */
+      fprintf (stderr, _("\n%s received.\n"), redirect_request_signal_name);
+      fprintf (stderr, _("%s: %s; disabling logging.\n"),
+               (logfile) ? logfile : DEFAULT_LOGFILE, strerror (errno));
+      inhibit_logging = true;
+    }
+  save_context_p = false;
+}
 
-  if (!save_log_p)
-    return;
+/* Check whether a signal handler requested the output to be
+   redirected. */
 
-  logfile = unique_name (DEFAULT_LOGFILE);
-  logfp = fopen (logfile, "w");
-  if (!logfp)
+static void
+check_redirect_output (void)
+{
+  if (redirect_request == RR_REQUESTED)
     {
-      printf ("%s: %s: %s\n", exec_name, logfile, strerror (errno));
-      /* `stdin' is magic to not print anything.  */
-      logfp = stdin;
+      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;
 }