Hello,

    The attached patch allows Squid to report various problems (e.g.,
mishandled exceptions) that may occur very early in Squid lifetime,
before proper logging is configured by the first _db_init() call.

To enable such early reporting, we started with a trivial debug.cc change:

  -FILE *debug_log = NULL;
  +FILE *debug_log = stderr;

... but then realized that debug_log may not be assigned early enough!
The resulting changes ensure that we can log (to stderr if necessary) as
soon as stderr itself is initialized. They also polish related logging
code, including localization of stderr checks and elimination of
double-closure during log rotation on Windows.

These reporting changes do not bypass or eliminate any failures.

This patch is independent from the "Do not die silently when dying via
std::terminate()" patch posted a few days ago, but, in combination, the
two patches fix reporting of early std::terminate() deaths.


Cheers,

Alex.
Do not die silently when dying early.

Report various problems (e.g., unhandled exceptions) that may occur very
early in Squid lifetime, before proper logging is configured by the
first _db_init() call.

To enable such early reporting, we started with a trivial change:
  -FILE *debug_log = NULL;
  +FILE *debug_log = stderr;
... but realized that debug_log may not be assigned early enough! The
resulting (larger) changes ensure that we can log (to stderr if
necessary) as soon as stderr itself is initialized. They also polish
related logging code, including localization of stderr checks and
elimination of double-closure during log rotation on Windows.

These reporting changes do not bypass or eliminate any failures.

=== modified file 'src/Debug.h'
--- src/Debug.h	2017-01-01 00:12:22 +0000
+++ src/Debug.h	2017-05-19 21:41:38 +0000
@@ -82,41 +82,51 @@ public:
     static int override_X;
     static int log_stderr;
     static bool log_syslog;
 
     static void parseOptions(char const *);
 
     /// minimum level required by the current debugs() call
     static int Level() { return Current ? Current->level : 1; }
     /// maximum level currently allowed
     static int SectionLevel() { return Current ? Current->sectionLevel : 1; }
 
     /// opens debugging context and returns output buffer
     static std::ostringstream &Start(const int section, const int level);
     /// logs output buffer created in Start() and closes debugging context
     static void Finish();
 
 private:
     static Context *Current; ///< deepest active context; nil outside debugs()
 };
 
-extern FILE *debug_log;
+/// cache.log FILE or, as the last resort, stderr stream;
+/// may be nil during static initialization and destruction!
+FILE *DebugStream();
+/// change-avoidance macro; new code should call DebugStream() instead
+#define debug_log DebugStream()
+
+/// start logging to stderr (instead of cache.log, if any)
+void StopUsingDebugLog();
+
+/// a hack for low-level file descriptor manipulations in ipcCreate()
+void ResyncDebugLog(FILE *newDestination);
 
 size_t BuildPrefixInit();
 const char * SkipBuildPrefix(const char* path);
 
 /* Debug stream
  *
  * Unit tests can enable full debugging to stderr for one
  * debug section; to enable this, #define ENABLE_DEBUG_SECTION to the
  * section number before any header
  */
 #define debugs(SECTION, LEVEL, CONTENT) \
    do { \
         const int _dbg_level = (LEVEL); \
         if (Debug::Enabled((SECTION), _dbg_level)) { \
             std::ostream &_dbo = Debug::Start((SECTION), _dbg_level); \
             if (_dbg_level > DBG_IMPORTANT) { \
                 _dbo << (SECTION) << ',' << _dbg_level << "| " \
                      << SkipBuildPrefix(__FILE__)<<"("<<__LINE__<<") "<<__FUNCTION__<<": "; \
             } \
             _dbo << CONTENT; \

=== modified file 'src/debug.cc'
--- src/debug.cc	2017-01-01 00:12:22 +0000
+++ src/debug.cc	2017-05-19 21:41:38 +0000
@@ -9,60 +9,122 @@
 /* DEBUG: section 00    Debug Routines */
 
 #include "squid.h"
 #include "Debug.h"
 #include "ipc/Kids.h"
 #include "SquidTime.h"
 #include "util.h"
 
 #include <algorithm>
 
 /* for shutting_down flag in xassert() */
 #include "globals.h"
 
 char *Debug::debugOptions = NULL;
 int Debug::override_X = 0;
 int Debug::log_stderr = -1;
 bool Debug::log_syslog = false;
 int Debug::Levels[MAX_DEBUG_SECTIONS];
 char *Debug::cache_log = NULL;
 int Debug::rotateNumber = -1;
-FILE *debug_log = NULL;
-static char *debug_log_file = NULL;
 static int Ctx_Lock = 0;
 static const char *debugLogTime(void);
 static const char *debugLogKid(void);
 static void ctx_print(void);
 #if HAVE_SYSLOG
 #ifdef LOG_LOCAL4
 static int syslog_facility = 0;
 #endif
 static void _db_print_syslog(const char *format, va_list args);
 #endif
 static void _db_print_stderr(const char *format, va_list args);
 static void _db_print_file(const char *format, va_list args);
 
 #if _SQUID_WINDOWS_
 extern LPCRITICAL_SECTION dbg_mutex;
 typedef BOOL (WINAPI * PFInitializeCriticalSectionAndSpinCount) (LPCRITICAL_SECTION, DWORD);
 #endif
 
+/// a (FILE*, file name) pair that uses stderr FILE as the last resort
+class DebugFile
+{
+public:
+    DebugFile() {}
+    ~DebugFile() { clear(); }
+    DebugFile(DebugFile &&) = delete; // no copying or moving of any kind
+
+    /// switches to the new pair, absorbing FILE and duping the name
+    void reset(FILE *newFile, const char *newName);
+
+    /// go back to the initial state
+    void clear() { reset(nullptr, nullptr); }
+
+    /// logging stream; the only method that uses stderr as the last resort
+    FILE *file() { return file_ ? file_ : stderr; }
+
+    char *name = nullptr;
+
+private:
+    friend void ResyncDebugLog(FILE *newFile);
+
+    FILE *file_ = nullptr; ///< opened "real" file or nil; never stderr
+};
+
+/// configured cache.log file or stderr
+/// safe during static initialization, even if it has not been constructed yet
+static DebugFile TheLog;
+
+FILE *
+DebugStream() {
+    return TheLog.file();
+}
+
+void
+StopUsingDebugLog()
+{
+    TheLog.clear();
+}
+
+void
+ResyncDebugLog(FILE *newFile)
+{
+    TheLog.file_ = newFile;
+}
+
+void
+DebugFile::reset(FILE *newFile, const char *newName)
+{
+    // callers must use nullptr instead of the used-as-the-last-resort stderr
+    assert(newFile != stderr || !stderr);
+
+    if (file_)
+        fclose(file_);
+    file_ = newFile; // may be nil
+
+    xfree(name);
+    name = newName ? xstrdup(newName) : nullptr;
+
+    // all open files must have a name
+    // all cleared files must not have a name
+    assert(!file_ == !name);
+}
+
 void
 _db_print(const char *format,...)
 {
     char f[BUFSIZ];
     f[0]='\0';
     va_list args1;
     va_list args2;
     va_list args3;
 
 #if _SQUID_WINDOWS_
     /* Multiple WIN32 threads may call this simultaneously */
 
     if (!dbg_mutex) {
         HMODULE krnl_lib = GetModuleHandle("Kernel32");
         PFInitializeCriticalSectionAndSpinCount InitializeCriticalSectionAndSpinCount = NULL;
 
         if (krnl_lib)
             InitializeCriticalSectionAndSpinCount =
                 (PFInitializeCriticalSectionAndSpinCount) GetProcAddress(krnl_lib,
                         "InitializeCriticalSectionAndSpinCount");
@@ -193,72 +255,63 @@ debugArg(const char *arg)
 
     if (l < 0)
         l = 0;
 
     if (l > 10)
         l = 10;
 
     if (s >= 0) {
         Debug::Levels[s] = l;
         return;
     }
 
     for (i = 0; i < MAX_DEBUG_SECTIONS; ++i)
         Debug::Levels[i] = l;
 }
 
 static void
 debugOpenLog(const char *logfile)
 {
     if (logfile == NULL) {
-        debug_log = stderr;
+        TheLog.clear();
         return;
     }
 
-    if (debug_log_file)
-        xfree(debug_log_file);
-
-    debug_log_file = xstrdup(logfile);  /* keep a static copy */
-
-    if (debug_log && debug_log != stderr)
-        fclose(debug_log);
-
     // Bug 4423: ignore the stdio: logging module name if present
     const char *logfilename;
     if (strncmp(logfile, "stdio:",6) == 0)
         logfilename = logfile + 6;
     else
         logfilename = logfile;
 
-    debug_log = fopen(logfilename, "a+");
-
-    if (!debug_log) {
+    if (auto log = fopen(logfilename, "a+")) {
+#if _SQUID_WINDOWS_
+        setmode(fileno(log), O_TEXT);
+#endif
+        TheLog.reset(log, logfilename);
+    } else {
         fprintf(stderr, "WARNING: Cannot write log file: %s\n", logfile);
         perror(logfile);
         fprintf(stderr, "         messages will be sent to 'stderr'.\n");
         fflush(stderr);
-        debug_log = stderr;
+        TheLog.clear();
     }
-
-#if _SQUID_WINDOWS_
-    setmode(fileno(debug_log), O_TEXT);
-#endif
 }
 
 #if HAVE_SYSLOG
 #ifdef LOG_LOCAL4
 
 static struct syslog_facility_name {
     const char *name;
     int facility;
 }
 
 syslog_facility_names[] = {
 
 #ifdef LOG_AUTH
     {
         "auth", LOG_AUTH
     },
 #endif
 #ifdef LOG_AUTHPRIV
     {
         "authpriv", LOG_AUTHPRIV
@@ -428,107 +481,102 @@ void
 _db_init(const char *logfile, const char *options)
 {
     Debug::parseOptions(options);
 
     debugOpenLog(logfile);
 
 #if HAVE_SYSLOG && defined(LOG_LOCAL4)
 
     if (Debug::log_syslog)
         openlog(APP_SHORTNAME, LOG_PID | LOG_NDELAY | LOG_CONS, syslog_facility);
 
 #endif /* HAVE_SYSLOG */
 
     /* Pre-Init TZ env, see bug #2656 */
     tzset();
 }
 
 void
 _db_rotate_log(void)
 {
-    if (debug_log_file == NULL)
+    if (!TheLog.name)
         return;
 
 #ifdef S_ISREG
     struct stat sb;
-    if (stat(debug_log_file, &sb) == 0)
+    if (stat(TheLog.name, &sb) == 0)
         if (S_ISREG(sb.st_mode) == 0)
             return;
 #endif
 
     char from[MAXPATHLEN];
     from[0] = '\0';
 
     char to[MAXPATHLEN];
     to[0] = '\0';
 
     /*
      * NOTE: we cannot use xrename here without having it in a
      * separate file -- tools.c has too many dependencies to be
      * used everywhere debug.c is used.
      */
     /* Rotate numbers 0 through N up one */
     for (int i = Debug::rotateNumber; i > 1;) {
         --i;
-        snprintf(from, MAXPATHLEN, "%s.%d", debug_log_file, i - 1);
-        snprintf(to, MAXPATHLEN, "%s.%d", debug_log_file, i);
+        snprintf(from, MAXPATHLEN, "%s.%d", TheLog.name, i - 1);
+        snprintf(to, MAXPATHLEN, "%s.%d", TheLog.name, i);
 #if _SQUID_WINDOWS_
         remove
         (to);
 #endif
         errno = 0;
         if (rename(from, to) == -1) {
             const auto saved_errno = errno;
             debugs(0, DBG_IMPORTANT, "log rotation failed: " << xstrerr(saved_errno));
         }
     }
 
-    /*
-     * You can't rename open files on Microsoft "operating systems"
-     * so we close before renaming.
-     */
-#if _SQUID_WINDOWS_
-    if (debug_log != stderr)
-        fclose(debug_log);
-#endif
     /* Rotate the current log to .0 */
     if (Debug::rotateNumber > 0) {
-        snprintf(to, MAXPATHLEN, "%s.%d", debug_log_file, 0);
+        // form file names before we may clear TheLog below
+        snprintf(from, MAXPATHLEN, "%s", TheLog.name);
+        snprintf(to, MAXPATHLEN, "%s.%d", TheLog.name, 0);
+
 #if _SQUID_WINDOWS_
         errno = 0;
         if (remove(to) == -1) {
             const auto saved_errno = errno;
             debugs(0, DBG_IMPORTANT, "removal of log file " << to << " failed: " << xstrerr(saved_errno));
         }
+        TheLog.clear(); // Windows cannot rename() open files
 #endif
         errno = 0;
-        if (rename(debug_log_file, to) == -1) {
+        if (rename(from, to) == -1) {
             const auto saved_errno = errno;
-            debugs(0, DBG_IMPORTANT, "renaming file " << debug_log_file << " to "
+            debugs(0, DBG_IMPORTANT, "renaming file " << from << " to "
                    << to << "failed: " << xstrerr(saved_errno));
         }
     }
 
-    /* Close and reopen the log.  It may have been renamed "manually"
-     * before HUP'ing us. */
-    if (debug_log != stderr)
-        debugOpenLog(Debug::cache_log);
+    // Close (if we have not already) and reopen the log because
+    // it may have been renamed "manually" before HUP'ing us.
+    debugOpenLog(Debug::cache_log);
 }
 
 static const char *
 debugLogTime(void)
 {
 
     time_t t = getCurrentTime();
 
     struct tm *tm;
     static char buf[128];
     static time_t last_t = 0;
 
     if (Debug::Level() > 1) {
         char buf2[128];
         tm = localtime(&t);
         strftime(buf2, 127, "%Y/%m/%d %H:%M:%S", tm);
         buf2[127] = '\0';
         snprintf(buf, 127, "%s.%03d", buf2, (int) current_time.tv_usec / 1000);
         last_t = t;
     } else if (t != last_t) {

=== modified file 'src/ipc.cc'
--- src/ipc.cc	2017-01-01 00:12:22 +0000
+++ src/ipc.cc	2017-05-19 21:41:38 +0000
@@ -399,29 +399,29 @@ ipcCreate(int type, const char *prog, co
     dup2(t3, 2);
 
     close(t1);
 
     close(t2);
 
     close(t3);
 
     /* Make sure all other filedescriptors are closed */
     for (x = 3; x < SQUID_MAXFD; ++x)
         close(x);
 
 #if HAVE_SETSID
     if (opt_no_daemon)
         setsid();
 #endif
 
     execvp(prog, (char *const *) args);
     xerrno = errno;
 
-    debug_log = fdopen(2, "a+");
+    ResyncDebugLog(fdopen(2, "a+"));
 
     debugs(54, DBG_CRITICAL, "ipcCreate: " << prog << ": " << xstrerr(xerrno));
 
     _exit(1);
 
     return 0;
 }
 

=== modified file 'src/main.cc'
--- src/main.cc	2017-03-03 23:18:25 +0000
+++ src/main.cc	2017-05-19 21:41:38 +0000
@@ -1397,41 +1397,40 @@ ConfigureCurrentKid(const char *processN
                 TheProcessKind = pkCoordinator;
             else if (!strcmp(TheKidName, "squid"))
                 TheProcessKind = pkWorker;
             else if (!strcmp(TheKidName, "squid-disk"))
                 TheProcessKind = pkDisker;
             else
                 TheProcessKind = pkOther; // including coordinator
         }
     } else {
         xstrncpy(TheKidName, APP_SHORTNAME, sizeof(TheKidName));
         KidIdentifier = 0;
     }
 }
 
 int
 SquidMain(int argc, char **argv)
 {
     ConfigureCurrentKid(argv[0]);
 
     Debug::parseOptions(NULL);
-    debug_log = stderr;
 
 #if defined(SQUID_MAXFD_LIMIT)
 
     if (SQUID_MAXFD_LIMIT < Squid_MaxFD)
         Squid_MaxFD = SQUID_MAXFD_LIMIT;
 
 #endif
 
     /* NOP under non-windows */
     int WIN32_init_err=0;
     if ((WIN32_init_err = WIN32_Subsystem_Init(&argc, &argv)))
         return WIN32_init_err;
 
     /* call mallopt() before anything else */
 #if HAVE_MALLOPT
 #ifdef M_GRAIN
     /* Round up all sizes to a multiple of this */
     mallopt(M_GRAIN, 16);
 
 #endif
@@ -1650,41 +1649,41 @@ SquidMain(int argc, char **argv)
 
     /* at this point we are finished the synchronous startup. */
     starting_up = 0;
 
     mainLoop.run();
 
     if (mainLoop.errcount == 10)
         fatal_dump("Event loop exited with failure.");
 
     /* shutdown squid now */
     SquidShutdown();
 
     /* NOTREACHED */
     return 0;
 }
 
 static void
 sendSignal(void)
 {
     pid_t pid;
-    debug_log = stderr;
+    StopUsingDebugLog();
 
     if (strcmp(Config.pidFilename, "none") == 0) {
         debugs(0, DBG_IMPORTANT, "No pid_filename specified. Trusting you know what you are doing.");
     }
 
     pid = readPidFile();
 
     if (pid > 1) {
 #if USE_WIN32_SERVICE
         if (opt_signal_service) {
             WIN32_sendSignal(opt_send_signal);
             exit(0);
         } else {
             fprintf(stderr, "%s: ERROR: Could not send ", APP_SHORTNAME);
             fprintf(stderr, "signal to Squid Service:\n");
             fprintf(stderr, "missing -n command line switch.\n");
             exit(1);
         }
         /* NOTREACHED */
 #endif
@@ -1741,44 +1740,40 @@ mainStartScript(const char *prog)
         execl(script, squid_start_script, (char *)NULL);
         _exit(-1);
     } else {
         do {
             PidStatus status;
             rpid = WaitForOnePid(cpid, status, 0);
         } while (rpid != cpid);
     }
 }
 
 #endif /* _SQUID_WINDOWS_ */
 
 static int
 checkRunningPid(void)
 {
     // master process must start alone, but its kids processes may co-exist
     if (!IamMasterProcess())
         return 0;
 
     pid_t pid;
-
-    if (!debug_log)
-        debug_log = stderr;
-
     pid = readPidFile();
 
     if (pid < 2)
         return 0;
 
     if (kill(pid, 0) < 0)
         return 0;
 
     debugs(0, DBG_CRITICAL, "Squid is already running!  Process ID " <<  pid);
 
     return 1;
 }
 
 #if !_SQUID_WINDOWS_
 static void
 masterCheckAndBroadcastSignals()
 {
     // if (do_reconfigure)
     //     TODO: hot-reconfiguration of the number of kids and PID file location
 

=== modified file 'src/tests/stub_debug.cc'
--- src/tests/stub_debug.cc	2017-01-01 00:12:22 +0000
+++ src/tests/stub_debug.cc	2017-05-19 21:41:38 +0000
@@ -1,48 +1,58 @@
 /*
  * Copyright (C) 1996-2017 The Squid Software Foundation and contributors
  *
  * Squid software is distributed under GPLv2+ license and includes
  * contributions from numerous individuals and organizations.
  * Please see the COPYING and CONTRIBUTORS files for details.
  */
 
 /*
  * A stub implementation of the Debug.h API.
  * For use by test binaries which do not need the full context debugging
  *
  * Note: it doesn't use the STUB API as the functions defined here must
  * not abort the unit test.
  */
 #include "squid.h"
 #include "Debug.h"
 
-FILE *debug_log = NULL;
+#define STUB_API "debug.cc"
+#include "tests/STUB.h"
 
 char *Debug::debugOptions;
 char *Debug::cache_log= NULL;
 int Debug::rotateNumber = 0;
 int Debug::Levels[MAX_DEBUG_SECTIONS];
 int Debug::override_X = 0;
 int Debug::log_stderr = 1;
 bool Debug::log_syslog = false;
 
+void StopUsingDebugLog() STUB
+void ResyncDebugLog(FILE *) STUB
+
+FILE *
+DebugStream()
+{
+    return stderr;
+}
+
 Ctx
 ctx_enter(const char *)
 {
     return -1;
 }
 
 void
 ctx_exit(Ctx)
 {}
 
 void
 _db_init(const char *, const char *)
 {}
 
 void
 _db_set_syslog(const char *)
 {}
 
 void
 _db_rotate_log(void)

_______________________________________________
squid-dev mailing list
[email protected]
http://lists.squid-cache.org/listinfo/squid-dev

Reply via email to