implemented log file monitoring using Windows VSS snapshots
authorVictor Kirhenshtein <victor@netxms.org>
Fri, 3 Nov 2017 16:29:07 +0000 (18:29 +0200)
committerVictor Kirhenshtein <victor@netxms.org>
Fri, 3 Nov 2017 16:29:07 +0000 (18:29 +0200)
13 files changed:
include/nxlpapi.h
src/libnetxms/libnetxms.vcxproj
src/libnetxms/libnetxms.vcxproj.filters
src/libnetxms/log.cpp
src/libnxlp/Makefile.am
src/libnxlp/Makefile.w32
src/libnxlp/file.cpp
src/libnxlp/libnxlp.h
src/libnxlp/libnxlp.vcxproj
src/libnxlp/libnxlp.vcxproj.filters
src/libnxlp/parser.cpp
src/libnxlp/vss.cpp [new file with mode: 0644]
src/nxlptest/nxlptest.cpp

index e6f2bea..5a4bb5f 100644 (file)
@@ -49,7 +49,8 @@ enum LogParserStatus
    LPS_SUSPENDED           = 4,
    LPS_EVT_SUBSCRIBE_ERROR = 5,
    LPS_EVT_READ_ERROR      = 6,
-   LPS_EVT_OPEN_ERROR      = 7
+   LPS_EVT_OPEN_ERROR      = 7,
+   LPS_VSS_FAILURE         = 8
 };
 
 /**
@@ -230,6 +231,7 @@ private:
        LogParserStatus m_status;
 #ifdef _WIN32
    TCHAR *m_marker;
+   bool m_useSnapshot;
 #endif
 
        const TCHAR *checkContext(LogParserRule *rule);
@@ -247,7 +249,8 @@ private:
 #ifdef _WIN32
    void parseEvent(EVENTLOGRECORD *rec);
 
-       bool monitorEventLogV6(CONDITION stopCondition);
+   bool monitorFileWithSnapshot(CONDITION stopCondition, bool readFromCurrPos);
+   bool monitorEventLogV6(CONDITION stopCondition);
        bool monitorEventLogV4(CONDITION stopCondition);
 
    time_t readLastProcessedRecordTimestamp();
@@ -277,6 +280,11 @@ public:
        void setProcessAllFlag(bool flag) { m_processAllRules = flag; }
        bool getProcessAllFlag() { return m_processAllRules; }
 
+#ifdef _WIN32
+   void setSnapshotMode(bool enable) { m_useSnapshot = enable;  }
+   bool isSnapshotMode() const { return m_useSnapshot;  }
+#endif
+
        bool addRule(const TCHAR *regexp, UINT32 eventCode = 0, const TCHAR *eventName = NULL, int numParams = 0, int repeatInterval = 0, int repeatCount = 0, bool resetRepeat = true);
        bool addRule(LogParserRule *rule);
        void setCallback(LogParserCallback cb) { m_cb = cb; }
@@ -302,7 +310,7 @@ public:
 
        bool monitorFile(CONDITION stopCondition, bool readFromCurrPos = true);
 #ifdef _WIN32
-       bool monitorEventLog(CONDITION stopCondition, const TCHAR *markerPrefix);
+   bool monitorEventLog(CONDITION stopCondition, const TCHAR *markerPrefix);
    void saveLastProcessedRecordTimestamp(time_t timestamp);
 #endif
 
index 9a0173b..a326461 100644 (file)
     <ClCompile Include="wcstoll.c" />
     <ClCompile Include="wcstoull.c" />
     <ClCompile Include="xml.cpp" />
+    <ClCompile Include="ztools.cpp" />
   </ItemGroup>
   <ItemGroup>
     <ClInclude Include="..\..\include\base64.h" />
index b0e17a8..61abe6f 100644 (file)
     <ClCompile Include="socket_listener.cpp">
       <Filter>Source Files</Filter>
     </ClCompile>
+    <ClCompile Include="ztools.cpp">
+      <Filter>Source Files</Filter>
+    </ClCompile>
   </ItemGroup>
   <ItemGroup>
     <ClInclude Include="..\..\include\base64.h">
index 761a59f..69fa237 100644 (file)
@@ -61,7 +61,7 @@ static CONDITION s_writerStopCondition = INVALID_CONDITION_HANDLE;
 static NxLogDebugWriter s_debugWriter = NULL;
 static DebugTagTree* volatile tagTreeActive = new DebugTagTree();
 static DebugTagTree* volatile tagTreeSecondary = new DebugTagTree();
-static MUTEX m_mutexDebugTagTreeWrite = INVALID_MUTEX_HANDLE;
+static Mutex s_mutexDebugTagTreeWrite;
 
 /**
  * Swaps tag tree pointers and waits till reader count drops to 0
@@ -83,11 +83,11 @@ void LIBNETXMS_EXPORTABLE nxlog_set_debug_level(int level)
 {
    if ((level >= 0) && (level <= 9))
    {
-      MutexLock(m_mutexDebugTagTreeWrite);
+      s_mutexDebugTagTreeWrite.lock();
       tagTreeSecondary->setRootDebugLevel(level); // Update the secondary tree
       SwapAndWait();
       tagTreeSecondary->setRootDebugLevel(level); // Update the previously active tree
-      MutexUnlock(m_mutexDebugTagTreeWrite);
+      s_mutexDebugTagTreeWrite.unlock();
    }
 }
 
@@ -102,7 +102,7 @@ void LIBNETXMS_EXPORTABLE nxlog_set_debug_level_tag(const TCHAR *tag, int level)
    }
    else
    {
-      MutexLock(m_mutexDebugTagTreeWrite);
+      s_mutexDebugTagTreeWrite.lock();
       if ((level >= 0) && (level <= 9))
       {
          tagTreeSecondary->add(tag, level);
@@ -115,7 +115,7 @@ void LIBNETXMS_EXPORTABLE nxlog_set_debug_level_tag(const TCHAR *tag, int level)
          SwapAndWait();
          tagTreeSecondary->remove(tag);
       }
-      MutexUnlock(m_mutexDebugTagTreeWrite);
+      s_mutexDebugTagTreeWrite.unlock();
    }
 }
 
@@ -490,7 +490,6 @@ bool LIBNETXMS_EXPORTABLE nxlog_open(const TCHAR *logName, UINT32 flags,
       }
 
       m_mutexLogAccess = MutexCreate();
-      m_mutexDebugTagTreeWrite = MutexCreate();
                SetDayStart();
    }
        return (s_flags & NXLOG_IS_OPEN) ? true : false;
@@ -524,8 +523,6 @@ void LIBNETXMS_EXPORTABLE nxlog_close()
             fclose(m_logFileHandle);
          if (m_mutexLogAccess != INVALID_MUTEX_HANDLE)
             MutexDestroy(m_mutexLogAccess);
-         if (m_mutexDebugTagTreeWrite != INVALID_MUTEX_HANDLE)
-            MutexDestroy(m_mutexDebugTagTreeWrite);
       }
           s_flags &= ~NXLOG_IS_OPEN;
    }
index 9e88231..67d40bb 100644 (file)
@@ -18,6 +18,6 @@ endif
 
 EXTRA_DIST = \
        Makefile.w32 \
-       eventlog.cpp wevt.cpp \
+       eventlog.cpp vss.cpp wevt.cpp \
        libnxlp.vcproj \
        libnxlp.h
index c7c00a7..18c9ad8 100644 (file)
@@ -1,8 +1,8 @@
 TARGET = libnxlp.dll
 TYPE = dll
-SOURCES = eventlog.cpp file.cpp main.cpp parser.cpp rule.cpp wevt.cpp
+SOURCES = eventlog.cpp file.cpp main.cpp parser.cpp rule.cpp vss.cpp wevt.cpp
 
 CPPFLAGS = /I$(NETXMS_BASE)\src\libexpat\libexpat /DLIBNXLP_EXPORTS
-LIBS = libnetxms.lib libexpat.lib libtre.lib
+LIBS = libnetxms.lib libexpat.lib libtre.lib vssapi.lib
          
 include ..\..\Makefile.inc.w32
index a629658..ab5810e 100644 (file)
 **/
 
 #include "libnxlp.h"
+#include <nxstat.h>
+
+#define DEBUG_TAG    _T("logwatch")
 
 #ifdef _WIN32
 #include <share.h>
+#include <comdef.h>
 #endif
 
-
-#if defined(_WIN32)
-#define NX_STAT _tstati64
-#define NX_STAT_STRUCT struct _stati64
-#elif HAVE_STAT64 && HAVE_STRUCT_STAT64
-#define NX_STAT stat64
-#define NX_STAT_STRUCT struct stat64
-#else
-#define NX_STAT stat
-#define NX_STAT_STRUCT struct stat
-#endif
-
-#if defined(_WIN32)
-#define NX_FSTAT _fstati64
-#elif HAVE_FSTAT64 && HAVE_STRUCT_STAT64
-#define NX_FSTAT fstat64
-#else
-#define NX_FSTAT fstat
-#endif
-
-#if defined(UNICODE) && !defined(_WIN32)
-inline int __call_stat(const WCHAR *f, NX_STAT_STRUCT *s)
-{
-       char *mbf = MBStringFromWideString(f);
-       int rc = NX_STAT(mbf, s);
-       free(mbf);
-       return rc;
-}
-#define CALL_STAT(f, s) __call_stat(f, s)
-#else
-#define CALL_STAT(f, s) NX_STAT(f, s)
-#endif
-
-
 /**
  * Constants
  */
@@ -443,6 +413,11 @@ bool LogParser::monitorFile(CONDITION stopCondition, bool readFromCurrPos)
                return false;
        }
 
+#ifdef _WIN32
+   if (m_useSnapshot)
+      return monitorFileWithSnapshot(stopCondition, readFromCurrPos);
+#endif
+
        LogParserTrace(0, _T("LogParser: parser thread for file \"%s\" started"), m_fileName);
        bool exclusionPeriod = false;
        while(true)
@@ -467,151 +442,287 @@ bool LogParser::monitorFile(CONDITION stopCondition, bool readFromCurrPos)
           }
 
                ExpandFileName(getFileName(), fname, MAX_PATH, true);
-               if (CALL_STAT(fname, &st) == 0)
-               {
+               if (CALL_STAT(fname, &st) != 0)
+      {
+         setStatus(LPS_NO_FILE);
+         if (ConditionWait(stopCondition, 10000))
+            break;
+         continue;
+      }
+
 #ifdef _WIN32
-                       fh = _tsopen(fname, O_RDONLY, _SH_DENYNO);
+      fh = _tsopen(fname, O_RDONLY, _SH_DENYNO);
 #else
-                       fh = _topen(fname, O_RDONLY);
+               fh = _topen(fname, O_RDONLY);
 #endif
-                       if (fh != -1)
-                       {
-                               setStatus(LPS_RUNNING);
-                               LogParserTrace(3, _T("LogParser: file \"%s\" (pattern \"%s\") successfully opened"), fname, m_fileName);
+               if (fh == -1)
+      {
+         setStatus(LPS_OPEN_ERROR);
+         if (ConditionWait(stopCondition, 10000))
+            break;
+         continue;
+      }
 
-            if (m_fileEncoding == -1)
-            {
-               m_fileEncoding = ScanFileEncoding(fh);
-               lseek(fh, 0, SEEK_SET);
-            }
+               setStatus(LPS_RUNNING);
+               LogParserTrace(3, _T("LogParser: file \"%s\" (pattern \"%s\") successfully opened"), fname, m_fileName);
 
-                               size = (size_t)st.st_size;
-                               if (readFromStart)
-                               {
-                                       LogParserTrace(5, _T("LogParser: parsing existing records in file \"%s\""), fname);
-                                       off_t resetPos = ParseNewRecords(this, fh);
-               lseek(fh, resetPos, SEEK_SET);
-                               }
-                               else if (m_preallocatedFile)
-                               {
-                                  SeekToZero(fh, getCharSize());
-                               }
-                               else
-                               {
-                                       lseek(fh, 0, SEEK_END);
-                               }
+      if (m_fileEncoding == -1)
+      {
+         m_fileEncoding = ScanFileEncoding(fh);
+         lseek(fh, 0, SEEK_SET);
+      }
 
-                               while(true)
-                               {
-                                       if (ConditionWait(stopCondition, 5000))
-                                               goto stop_parser;
+               size = (size_t)st.st_size;
+               if (readFromStart)
+               {
+                       LogParserTrace(5, _T("LogParser: parsing existing records in file \"%s\""), fname);
+                       off_t resetPos = ParseNewRecords(this, fh);
+         lseek(fh, resetPos, SEEK_SET);
+               }
+               else if (m_preallocatedFile)
+               {
+                       SeekToZero(fh, getCharSize());
+               }
+               else
+               {
+                       lseek(fh, 0, SEEK_END);
+               }
 
-                                       // Check if file name was changed
-                                       ExpandFileName(getFileName(), temp, MAX_PATH, true);
-                                       if (_tcscmp(temp, fname))
-                                       {
-                                               LogParserTrace(5, _T("LogParser: file name change for \"%s\" (\"%s\" -> \"%s\")"), m_fileName, fname, temp);
-                                               readFromStart = true;
-                                               break;
-                                       }
+               while(true)
+               {
+                       if (ConditionWait(stopCondition, 5000))
+                               goto stop_parser;
 
-                                       if (NX_FSTAT(fh, &st) < 0)
-                                       {
-                                               LogParserTrace(1, _T("LogParser: fstat(%d) failed, errno=%d"), fh, errno);
-                                               readFromStart = true;
-                                               break;
-                                       }
+                       // Check if file name was changed
+                       ExpandFileName(getFileName(), temp, MAX_PATH, true);
+                       if (_tcscmp(temp, fname))
+                       {
+                               LogParserTrace(5, _T("LogParser: file name change for \"%s\" (\"%s\" -> \"%s\")"), m_fileName, fname, temp);
+                               readFromStart = true;
+                               break;
+                       }
 
-                                       if (CALL_STAT(fname, &stn) < 0)
-                                       {
-                                               LogParserTrace(1, _T("LogParser: stat(%s) failed, errno=%d"), fname, errno);
-                                               readFromStart = true;
-                                               break;
-                                       }
+                       if (NX_FSTAT(fh, &st) < 0)
+                       {
+                               LogParserTrace(1, _T("LogParser: fstat(%d) failed, errno=%d"), fh, errno);
+                               readFromStart = true;
+                               break;
+                       }
+
+                       if (CALL_STAT(fname, &stn) < 0)
+                       {
+                               LogParserTrace(1, _T("LogParser: stat(%s) failed, errno=%d"), fname, errno);
+                               readFromStart = true;
+                               break;
+                       }
 
 #ifdef _WIN32
-                                       if (st.st_ctime != stn.st_ctime)
-                                       {
-                                               LogParserTrace(3, _T("LogParser: creation time for fstat(%d) is not equal to creation time for stat(%s), assume file rename"), fh, fname);
-                                               readFromStart = true;
-                                               break;
-                                       }
+                       if (st.st_ctime != stn.st_ctime)
+                       {
+                               LogParserTrace(3, _T("LogParser: creation time for fstat(%d) is not equal to creation time for stat(%s), assume file rename"), fh, fname);
+                               readFromStart = true;
+                               break;
+                       }
 #else
-                                       if ((st.st_ino != stn.st_ino) || (st.st_dev != stn.st_dev))
-                                       {
-                                               LogParserTrace(3, _T("LogParser: file device or inode differs for stat(%d) and fstat(%s), assume file rename"), fh, fname);
-                                               readFromStart = true;
-                                               break;
-                                       }
+                       if ((st.st_ino != stn.st_ino) || (st.st_dev != stn.st_dev))
+                       {
+                               LogParserTrace(3, _T("LogParser: file device or inode differs for stat(%d) and fstat(%s), assume file rename"), fh, fname);
+                               readFromStart = true;
+                               break;
+                       }
 #endif
 
-                                       if ((size_t)st.st_size != size)
-                                       {
-                                               if ((size_t)st.st_size < size)
-                                               {
-                                                       // File was cleared, start from the beginning
-                                                       lseek(fh, 0, SEEK_SET);
-                                                       LogParserTrace(3, _T("LogParser: file \"%s\" st_size != size"), fname);
-                                               }
-                                               size = (size_t)st.st_size;
-                                               LogParserTrace(6, _T("LogParser: new data available in file \"%s\""), fname);
-                                               off_t resetPos = ParseNewRecords(this, fh);
-                                               lseek(fh, resetPos, SEEK_SET);
-                                       }
-                                       else if (m_preallocatedFile)
-                                       {
-                                          char buffer[4];
-                                          int bytes = _read(fh, buffer, 4);
-                                          if ((bytes == 4) && memcmp(buffer, "\x00\x00\x00\x00", 4))
-                                          {
-                     lseek(fh, -4, SEEK_CUR);
-                         LogParserTrace(6, _T("LogParser: new data available in file \"%s\""), fname);
-                         off_t resetPos = ParseNewRecords(this, fh);
-                         lseek(fh, resetPos, SEEK_SET);
-                                          }
-                                          else
-                                          {
-                     off_t pos = lseek(fh, -bytes, SEEK_CUR);
-                     if (pos > 0)
-                     {
-                        int readSize = std::min(pos, (off_t)4);
-                        lseek(fh, -readSize, SEEK_CUR);
-                        int bytes = _read(fh, buffer, readSize);
-                        if ((bytes == readSize) && !memcmp(buffer, "\x00\x00\x00\x00", readSize))
-                        {
-                           LogParserTrace(6, _T("LogParser: detected reset of preallocated file \"%s\""), fname);
-                           lseek(fh, 0, SEEK_SET);
-                           off_t resetPos = ParseNewRecords(this, fh);
-                           lseek(fh, resetPos, SEEK_SET);
-                        }
-                     }
-                                          }
-                                       }
-
-                                       if (isExclusionPeriod())
-                                       {
-                  LogParserTrace(6, _T("LogParser: closing file \"%s\" because of exclusion period"), fname);
-                  exclusionPeriod = true;
-                  setStatus(LPS_SUSPENDED);
-                                          break;
-                                       }
+                       if ((size_t)st.st_size != size)
+                       {
+                               if ((size_t)st.st_size < size)
+                               {
+                                       // File was cleared, start from the beginning
+                                       lseek(fh, 0, SEEK_SET);
+                                       LogParserTrace(3, _T("LogParser: file \"%s\" st_size != size"), fname);
                                }
-                               _close(fh);
+                               size = (size_t)st.st_size;
+                               LogParserTrace(6, _T("LogParser: new data available in file \"%s\""), fname);
+                               off_t resetPos = ParseNewRecords(this, fh);
+                               lseek(fh, resetPos, SEEK_SET);
                        }
-                       else
+                       else if (m_preallocatedFile)
                        {
-                               setStatus(LPS_OPEN_ERROR);
+                               char buffer[4];
+                               int bytes = _read(fh, buffer, 4);
+                               if ((bytes == 4) && memcmp(buffer, "\x00\x00\x00\x00", 4))
+                               {
+               lseek(fh, -4, SEEK_CUR);
+                   LogParserTrace(6, _T("LogParser: new data available in file \"%s\""), fname);
+                   off_t resetPos = ParseNewRecords(this, fh);
+                   lseek(fh, resetPos, SEEK_SET);
+                               }
+                               else
+                               {
+               off_t pos = lseek(fh, -bytes, SEEK_CUR);
+               if (pos > 0)
+               {
+                  int readSize = std::min(pos, (off_t)4);
+                  lseek(fh, -readSize, SEEK_CUR);
+                  int bytes = _read(fh, buffer, readSize);
+                  if ((bytes == readSize) && !memcmp(buffer, "\x00\x00\x00\x00", readSize))
+                  {
+                     LogParserTrace(6, _T("LogParser: detected reset of preallocated file \"%s\""), fname);
+                     lseek(fh, 0, SEEK_SET);
+                     off_t resetPos = ParseNewRecords(this, fh);
+                     lseek(fh, resetPos, SEEK_SET);
+                  }
+               }
+                               }
                        }
-               }
-               else
-               {
-                       setStatus(LPS_NO_FILE);
-                       if (ConditionWait(stopCondition, 10000))
+
+                       if (isExclusionPeriod())
+                       {
+            LogParserTrace(6, _T("LogParser: closing file \"%s\" because of exclusion period"), fname);
+            exclusionPeriod = true;
+            setStatus(LPS_SUSPENDED);
                                break;
+                       }
                }
+               _close(fh);
        }
 
 stop_parser:
-       LogParserTrace(0, _T("LogParser: parser thread for file \"%s\" stopped"), m_fileName);
+   LogParserTrace(0, _T("LogParser: parser thread for file \"%s\" stopped"), m_fileName);
        return true;
 }
+
+#ifdef _WIN32
+
+/**
+ * File parser thread (using VSS snapshots)
+ */
+bool LogParser::monitorFileWithSnapshot(CONDITION stopCondition, bool readFromCurrPos)
+{
+   HRESULT hr = CoInitializeEx(NULL, COINIT_MULTITHREADED);
+   if (FAILED(hr))
+   {
+      _com_error err(hr);
+      LogParserTrace(0, _T("LogParser: parser thread will not start, COM initialization failed (%s)"), err.ErrorMessage());
+      return false;
+   }
+
+   size_t size = 0;
+   time_t mtime = 0;
+   time_t ctime = 0;
+   off_t lastPos = 0;
+   bool readFromStart = !readFromCurrPos;
+   bool firstRead = true;
+
+   LogParserTrace(0, _T("LogParser: parser thread for file \"%s\" started"), m_fileName);
+   bool exclusionPeriod = false;
+   while(true)
+   {
+      if (isExclusionPeriod())
+      {
+         if (!exclusionPeriod)
+         {
+            exclusionPeriod = true;
+            LogParserTrace(6, _T("LogParser: will not open file \"%s\" because of exclusion period"), getFileName());
+            setStatus(LPS_SUSPENDED);
+         }
+         if (ConditionWait(stopCondition, 30000))
+            break;
+         continue;
+      }
+
+      if (exclusionPeriod)
+      {
+         exclusionPeriod = false;
+         LogParserTrace(6, _T("LogParser: exclusion period for file \"%s\" ended"), getFileName());
+      }
+
+      TCHAR fname[MAX_PATH];
+      ExpandFileName(getFileName(), fname, MAX_PATH, true);
+      
+      NX_STAT_STRUCT st;
+      if (CALL_STAT(fname, &st) != 0)
+      {
+         setStatus(LPS_NO_FILE);
+         if (ConditionWait(stopCondition, 10000))
+            break;
+         continue;
+      }
+
+      if (firstRead)
+         ctime = st.st_ctime; // prevent incorrect rotation detection on first read
+
+      if ((size == st.st_size) && (mtime == st.st_mtime) && (ctime == st.st_ctime) && !readFromStart)
+      {
+         if (ConditionWait(stopCondition, 10000))
+            break;
+         continue;
+      }
+
+      FileSnapshot *snapshot = FileSnapshot::create(fname);
+      if (snapshot == NULL)
+      {
+         setStatus(LPS_VSS_FAILURE);
+         if (ConditionWait(stopCondition, 30000))  // retry in 30 seconds
+            break;
+         continue;
+      }
+
+      int fh = _tsopen(snapshot->name(), O_RDONLY, _SH_DENYNO);
+      if (fh == -1)
+      {
+         delete snapshot;
+         setStatus(LPS_OPEN_ERROR);
+         if (ConditionWait(stopCondition, 10000))  // retry in 10 seconds
+            break;
+         continue;
+      }
+
+      setStatus(LPS_RUNNING);
+      LogParserTrace(3, _T("LogParser: file \"%s\" (pattern \"%s\", snapshot \"%s\") successfully opened"), fname, m_fileName, snapshot->name());
+
+      if ((size > static_cast<size_t>(st.st_size)) || (ctime != st.st_ctime))
+      {
+         nxlog_debug_tag(DEBUG_TAG, 5, _T("LogParser: file \"%s\" rotation detected (size=%llu/%llu, ctime=%llu/%llu)"),  fname,
+            static_cast<UINT64>(size), static_cast<UINT64>(st.st_size), static_cast<UINT64>(ctime), static_cast<UINT64>(st.st_ctime));
+         readFromStart = true;   // Assume file rotation
+         ctime = st.st_ctime;
+      }
+
+      if (m_fileEncoding == -1)
+      {
+         m_fileEncoding = ScanFileEncoding(fh);
+         lseek(fh, 0, SEEK_SET);
+      }
+
+      if (!readFromStart)
+      {
+         if (firstRead)
+         {
+            if (m_preallocatedFile)
+               SeekToZero(fh, getCharSize());
+            else
+               lseek(fh, 0, SEEK_END);
+            firstRead = false;
+         }
+         else
+         {
+            lseek(fh, lastPos, SEEK_SET);
+         }
+      }
+
+      lastPos = ParseNewRecords(this, fh);
+      _close(fh);
+      size = static_cast<size_t>(st.st_size);
+      mtime = st.st_mtime;
+
+      delete snapshot;
+      if (ConditionWait(stopCondition, 10000))
+         break;
+   }
+
+   CoUninitialize();
+   LogParserTrace(0, _T("LogParser: parser thread for file \"%s\" stopped"), m_fileName);
+   return true;
+}
+
+#endif   /* _WIN32 */
index ecea002..2d8dfcf 100644 (file)
 void LogParserTrace(int level, const TCHAR *format, ...);
 
 #ifdef _WIN32
+
+class IVssBackupComponents;
+
+class FileSnapshot
+{
+private:
+   IVssBackupComponents *m_handle;
+   TCHAR *m_name;
+
+   FileSnapshot();
+
+public:
+   static FileSnapshot *create(const TCHAR *path);
+
+   ~FileSnapshot();
+
+   const TCHAR *name() const { return m_name; }
+};
+
 THREAD_RESULT THREAD_CALL ParserThreadEventLog(void *);
-THREAD_RESULT THREAD_CALL ParserThreadEventLogV6(void *);
 bool InitEventLogParsersV6();
 void InitEventLogParsers();
 void CleanupEventLogParsers();
+
 #endif
 
 #endif
index bedd232..321aa5d 100644 (file)
       <GenerateDebugInformation>true</GenerateDebugInformation>
       <SubSystem>Windows</SubSystem>
       <TargetMachine>MachineX86</TargetMachine>
+      <AdditionalDependencies>vssapi.lib;%(AdditionalDependencies)</AdditionalDependencies>
     </Link>
   </ItemDefinitionGroup>
   <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
       <GenerateDebugInformation>true</GenerateDebugInformation>
       <SubSystem>Windows</SubSystem>
       <TargetMachine>MachineX64</TargetMachine>
+      <AdditionalDependencies>vssapi.lib;%(AdditionalDependencies)</AdditionalDependencies>
     </Link>
   </ItemDefinitionGroup>
   <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
       <OptimizeReferences>true</OptimizeReferences>
       <EnableCOMDATFolding>true</EnableCOMDATFolding>
       <TargetMachine>MachineX86</TargetMachine>
+      <AdditionalDependencies>vssapi.lib;%(AdditionalDependencies)</AdditionalDependencies>
     </Link>
   </ItemDefinitionGroup>
   <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
       <OptimizeReferences>true</OptimizeReferences>
       <EnableCOMDATFolding>true</EnableCOMDATFolding>
       <TargetMachine>MachineX64</TargetMachine>
+      <AdditionalDependencies>vssapi.lib;%(AdditionalDependencies)</AdditionalDependencies>
     </Link>
   </ItemDefinitionGroup>
   <ItemGroup>
     <ClCompile Include="main.cpp" />
     <ClCompile Include="parser.cpp" />
     <ClCompile Include="rule.cpp" />
+    <ClCompile Include="vss.cpp" />
     <ClCompile Include="wevt.cpp" />
   </ItemGroup>
   <ItemGroup>
index 3dbfa4c..32b23fa 100644 (file)
@@ -33,6 +33,9 @@
     <ClCompile Include="wevt.cpp">
       <Filter>Source Files</Filter>
     </ClCompile>
+    <ClCompile Include="vss.cpp">
+      <Filter>Source Files</Filter>
+    </ClCompile>
   </ItemGroup>
   <ItemGroup>
     <ClInclude Include="libnxlp.h">
index be1518c..5f06bcb 100644 (file)
@@ -67,7 +67,8 @@ struct XML_PARSER_STATE
        StringList files;
        IntegerArray<INT32> encodings;
    IntegerArray<INT32> preallocFlags;
-       String id;
+   IntegerArray<INT32> snapshotFlags;
+   String id;
        String level;
        String source;
        String context;
@@ -86,7 +87,7 @@ struct XML_PARSER_STATE
        int repeatInterval;
        bool resetRepeat;
 
-       XML_PARSER_STATE() : encodings(4, 4), preallocFlags(4, 4)
+       XML_PARSER_STATE() : encodings(4, 4), preallocFlags(4, 4), snapshotFlags(4, 4)
        {
       state = XML_STATE_INIT;
       parser = NULL;
@@ -450,7 +451,8 @@ static void StartElement(void *userData, const char *name, const char **attrs)
                        ps->encodings.add(LP_FCP_AUTO);
                }
                ps->preallocFlags.add(XMLGetAttrBoolean(attrs, "preallocated", false) ? 1 : 0);
-       }
+      ps->snapshotFlags.add(XMLGetAttrBoolean(attrs, "snapshot", false) ? 1 : 0);
+   }
        else if (!strcmp(name, "macros"))
        {
                ps->state = XML_STATE_MACROS;
@@ -807,6 +809,9 @@ ObjectArray<LogParser> *LogParser::createFromXml(const char *xml, int xmlLen, TC
                                p->setFileName(state.files.get(i));
                                p->m_fileEncoding = state.encodings.get(i);
                                p->m_preallocatedFile = (state.preallocFlags.get(i) != 0);
+#ifdef _WIN32
+            p->m_useSnapshot = (state.snapshotFlags.get(i) != 0);
+#endif
                                parsers->add(p);
                        }
                }
@@ -949,7 +954,8 @@ const TCHAR *LogParser::getStatusText() const
       _T("SUSPENDED"),
       _T("EVENT LOG SUBSCRIBE FAILED"),
       _T("EVENT LOG READ ERROR"),
-      _T("EVENT LOG OPEN ERROR")
+      _T("EVENT LOG OPEN ERROR"),
+      _T("VSS FAILURE")
    };
    return texts[m_status];
 }
diff --git a/src/libnxlp/vss.cpp b/src/libnxlp/vss.cpp
new file mode 100644 (file)
index 0000000..d8a2e0e
--- /dev/null
@@ -0,0 +1,129 @@
+/*
+** NetXMS - Network Management System
+** Log Parsing Library
+** Copyright (C) 2003-2017 Victor Kirhenshtein
+**
+** This program 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,
+** 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.
+**
+** File: wevt.cpp
+**
+**/
+
+#define _WIN32_WINNT 0x0600
+#include "libnxlp.h"
+#include <vss.h>
+#include <vswriter.h>
+#include <vsbackup.h>
+#include <comdef.h>
+
+#define DEBUG_TAG    _T("logwatch.vss")
+
+/**
+* Helper function to display error and return failure in FileSnapshot::create
+*/
+inline FileSnapshot *CreateFailure(HRESULT hr, IVssBackupComponents *bc, const TCHAR *format)
+{
+   _com_error err(hr);
+   nxlog_debug_tag(DEBUG_TAG, 3, format, err.ErrorMessage(), hr);
+   if (bc != NULL)
+      bc->Release();
+   return NULL;
+}
+
+/**
+* Create file snapshot using VSS
+*/
+FileSnapshot *FileSnapshot::create(const TCHAR *path)
+{
+   IVssBackupComponents *bc;
+   HRESULT hr = CreateVssBackupComponents(&bc);
+   if (FAILED(hr))
+      return CreateFailure(hr, NULL, _T("Call to CreateVssBackupComponents failed (%s) HRESULT=0x%08X"));
+
+   hr = bc->InitializeForBackup();
+   if (FAILED(hr))
+      return CreateFailure(hr, bc, _T("Call to IVssBackupComponents::InitializeForBackup failed (%s) HRESULT=0x%08X"));
+
+   hr = bc->SetBackupState(false, false, VSS_BT_COPY);
+   if (FAILED(hr))
+      return CreateFailure(hr, bc, _T("Call to IVssBackupComponents::SetBackupState failed (%s) HRESULT=0x%08X"));
+
+   hr = bc->SetContext(VSS_CTX_FILE_SHARE_BACKUP);
+   if (FAILED(hr))
+      return CreateFailure(hr, bc, _T("Call to IVssBackupComponents::SetContext failed (%s) HRESULT=0x%08X"));
+
+   VSS_ID snapshotSetId;
+   hr = bc->StartSnapshotSet(&snapshotSetId);
+   if (FAILED(hr))
+      return CreateFailure(hr, bc, _T("Call to IVssBackupComponents::StartSnapshotSet failed (%s) HRESULT=0x%08X"));
+
+   const TCHAR *s = _tcschr(path, _T(':'));
+   if (s == NULL)
+      return CreateFailure(S_OK, bc, _T("Unsupported file path format"));
+   s++;
+
+   size_t len = s - path;
+   TCHAR device[64];
+   _tcslcpy(device, path, std::min(static_cast<size_t>(64), len + 1));
+   _tcslcat(device, _T("\\"), 64);
+   nxlog_debug_tag(DEBUG_TAG, 7, _T("Adding device %s to VSS snapshot"), device);
+
+   VSS_ID snapshotId;
+   hr = bc->AddToSnapshotSet(device, GUID_NULL, &snapshotId);
+   if (FAILED(hr))
+      return CreateFailure(hr, bc, _T("Call to IVssBackupComponents::AddToSnapshotSet failed (%s) HRESULT=0x%08X"));
+
+   IVssAsync *async;
+   hr = bc->DoSnapshotSet(&async);
+   if (FAILED(hr))
+      return CreateFailure(hr, bc, _T("Call to IVssBackupComponents::DoSnapshotSet failed (%s) HRESULT=0x%08X"));
+
+   hr = async->Wait();
+   async->Release();
+   if (FAILED(hr))
+      return CreateFailure(hr, bc, _T("Call to IVssAsync::Wait failed (%s) HRESULT=0x%08X"));
+
+   VSS_SNAPSHOT_PROP prop;
+   hr = bc->GetSnapshotProperties(snapshotId, &prop);
+   if (FAILED(hr))
+      return CreateFailure(hr, bc, _T("Call to IVssBackupComponents::GetSnapshotProperties failed (%s) HRESULT=0x%08X"));
+
+   nxlog_debug_tag(DEBUG_TAG, 7, _T("Created VSS snapshot %s"), prop.m_pwszSnapshotDeviceObject);
+   String sname(prop.m_pwszSnapshotDeviceObject);
+   sname.append(s);
+
+   FileSnapshot *object = new FileSnapshot();
+   object->m_handle = bc;
+   object->m_name = _tcsdup(sname);
+   return object;
+}
+
+/**
+ * File snapshot object constructor
+ */
+FileSnapshot::FileSnapshot()
+{
+   m_handle = NULL;
+   m_name = NULL;
+}
+
+/**
+ * File snapshot destructor
+ */
+FileSnapshot::~FileSnapshot()
+{
+   m_handle->Release();
+   free(m_name);
+}
index 5875f63..58b7aaf 100644 (file)
@@ -1,7 +1,7 @@
 /* 
 ** NetXMS - Network Management System
 ** NetXMS Log Parser Testing Utility
-** Copyright (C) 2009 Victor Kirhenshtein
+** Copyright (C) 2009-2017 Victor Kirhenshtein
 **
 ** This program is free software; you can redistribute it and/or modify
 ** it under the terms of the GNU General Public License as published by
 #include <signal.h>
 #endif
 
-//
-// Static data
-//
-
+/**
+ * Stop condition
+ */
 static CONDITION m_stopCondition = INVALID_CONDITION_HANDLE;
 
-
-//
-// Help text
-//
-
+/**
+ * Help text
+ */
 static TCHAR m_helpText[] =
    _T("NetXMS Log Parsing Tester  Version ") NETXMS_VERSION_STRING _T("\n")
    _T("Copyright (c) 2009-2012 Victor Kirhenshtein\n\n")
    _T("Usage:\n")
    _T("   nxlptest [options] parser\n\n")
    _T("Where valid options are:\n")
-       _T("   -f file    : Input file (overrides parser settings)\n")
+   _T("   -D level   : Set debug level\n")
+   _T("   -f file    : Input file (overrides parser settings)\n")
    _T("   -h         : Show this help\n")
-       _T("   -i         : Uses standard input instead of file defined in parser\n" )
-       _T("   -t level   : Set trace level (overrides parser settings)\n")
+       _T("   -i         : Use standard input instead of file defined in parser\n" )
+#ifdef _WIN32
+   _T("   -s         : Use VSS snapshots (overrides parser settings)\n")
+#endif
+   _T("   -t level   : Set trace level (overrides parser settings)\n")
    _T("   -v         : Show version and exit\n")
    _T("\n");
 
 /**
+ * Debug writer
+ */
+static void DebugWriter(const TCHAR *tag, const TCHAR *message)
+{
+   _tprintf(_T("[%s] %s\n"), tag, message);
+}
+
+/**
  * Trace callback
  */
 static void TraceCallback(int level, const TCHAR *format, va_list args)
@@ -97,15 +106,21 @@ int main(int argc, char *argv[])
        BYTE *xml;
        UINT32 size;
        TCHAR *inputFile = NULL;
+#ifdef _WIN32
+   bool vssSnapshots = false;
+#endif
 
    InitNetXMSProcess(true);
 
    // Parse command line
    opterr = 1;
-       while((ch = getopt(argc, argv, "f:hit:v")) != -1)
+       while((ch = getopt(argc, argv, "D:f:hist:v")) != -1)
    {
                switch(ch)
                {
+         case 'D':
+            nxlog_set_debug_level(strtol(optarg, NULL, 0));
+            break;
                        case 'h':
                                _tprintf(m_helpText);
             return 0;
@@ -120,6 +135,11 @@ int main(int argc, char *argv[])
                                inputFile = optarg;
 #endif
                                break;
+#ifdef _WIN32
+         case 's':
+            vssSnapshots = true;
+            break;
+#endif
                        case 't':
                                traceLevel = strtol(optarg, NULL, 0);
                                break;
@@ -136,6 +156,8 @@ int main(int argc, char *argv[])
       return 1;
    }
 
+   nxlog_set_debug_writer(DebugWriter);
+
    InitLogParserLibrary();
    SetLogParserTraceCallback(LoggerCallback);
 
@@ -160,8 +182,12 @@ int main(int argc, char *argv[])
                                parser->setTraceLevel(traceLevel);
                        if (inputFile != NULL)
                                parser->setFileName(inputFile);
+#ifdef _WIN32
+         if (vssSnapshots)
+            parser->setSnapshotMode(true);
+#endif
 
-                       m_stopCondition = ConditionCreate(TRUE);
+                       m_stopCondition = ConditionCreate(true);
                        thread = ThreadCreateEx(ParserThread, 0, parser);
 #ifdef _WIN32
                        _tprintf(_T("Parser started. Press ESC to stop.\nFile: %s\nTrace level: %d\n\n"),