diff --git a/.gitignore b/.gitignore index 3e759b75..4ec8368f 100644 --- a/.gitignore +++ b/.gitignore @@ -26,6 +26,7 @@ bld/ # Visual Studio 2015/2017 cache/options directory .vs/ +.vscode/ # Uncomment if you have tasks that create the project's static files in wwwroot #wwwroot/ diff --git a/LogMonitor/LogMonitorTests/ConfigFileParserTests.cpp b/LogMonitor/LogMonitorTests/ConfigFileParserTests.cpp index 9bb00c36..a145fe5e 100644 --- a/LogMonitor/LogMonitorTests/ConfigFileParserTests.cpp +++ b/LogMonitor/LogMonitorTests/ConfigFileParserTests.cpp @@ -447,6 +447,7 @@ namespace LogMonitorTests Assert::AreEqual(directory.c_str(), sourceFile->Directory.c_str()); Assert::AreEqual(L"", sourceFile->Filter.c_str()); Assert::AreEqual(false, sourceFile->IncludeSubdirectories); + Assert::AreEqual(300.0, sourceFile->WaitInSeconds); } } @@ -1336,6 +1337,65 @@ namespace LogMonitorTests } } + TEST_METHOD(TestRootDirectoryConfigurations) + { + + const std::wstring directory = L"C:\\"; + bool includeSubdirectories = false; + + std::wstring configFileStr; + std::wstring configFileStrFormat = + L"{ \ + \"LogConfig\": { \ + \"sources\": [ \ + {\ + \"type\": \"File\",\ + \"directory\": \"%s\",\ + \"includeSubdirectories\": %s\ + }\ + ]\ + }\ + }"; + + // Valid: Root dir and includeSubdirectories = false + { + configFileStr = Utility::FormatString( + configFileStrFormat.c_str(), + Utility::ReplaceAll(directory, L"\\", L"\\\\").c_str(), + includeSubdirectories ? L"true" : L"false"); + + JsonFileParser jsonParser(configFileStr); + LoggerSettings settings; + + bool success = ReadConfigFile(jsonParser, settings); + Assert::IsTrue(success); + + std::wstring output = RecoverOuput(); + Assert::AreEqual(L"", output.c_str()); + } + + // Invalid: Root dir and includeSubdirectories = true + { + includeSubdirectories = true; + configFileStr = Utility::FormatString( + configFileStrFormat.c_str(), + Utility::ReplaceAll(directory, L"\\", L"\\\\").c_str(), + includeSubdirectories ? L"true" : L"false"); + + fflush(stdout); + ZeroMemory(bigOutBuf, sizeof(bigOutBuf)); + + JsonFileParser jsonParser(configFileStr); + LoggerSettings settings; + + bool success = ReadConfigFile(jsonParser, settings); + Assert::IsTrue(success); + + std::wstring output = RecoverOuput(); + Assert::IsTrue(output.find(L"WARNING") != std::wstring::npos); + } + } + /// /// Check that invalid ETW sources are not returned by ReadConfigFile. /// @@ -1543,5 +1603,114 @@ namespace LogMonitorTests Assert::AreEqual(succcess, true); } + TEST_METHOD(TestWaitInSeconds){ + // Test WaitInSeconds input as value + TestWaitInSecondsValues(L"242", false); + + // Test WaitInSeconds input as string + TestWaitInSecondsValues(L"359", true); + + // Test WaitInSeconds input is Infinity + TestWaitInSecondsValues(L"INFINITY", true); + } + + TEST_METHOD(TestInvalidWaitInSeconds) { + std::wstring directory = L"C:\\LogMonitor\\logs"; + TestInvalidWaitInSecondsValues(L"-10", false); + TestInvalidWaitInSecondsValues(L"-Inf", true); + } + + private: + void TestWaitInSecondsValues(std::wstring waitInSeconds, bool asString = false) { + std::wstring directory = L"C:\\LogMonitor\\logs"; + std::wstring configFileStr = GetConfigFileStrFormat(directory, waitInSeconds, asString); + + JsonFileParser jsonParser(configFileStr); + LoggerSettings settings; + + bool success = ReadConfigFile(jsonParser, settings); + + // + // The config string was valid + // + Assert::IsTrue(success); + + // + // The source Event Log is valid + // + Assert::AreEqual((size_t)1, settings.Sources.size()); + Assert::AreEqual((int)LogSourceType::File, (int)settings.Sources[0]->Type); + + std::shared_ptr sourceFile = std::reinterpret_pointer_cast(settings.Sources[0]); + + if (isinf(std::stod(waitInSeconds))) { + Assert::IsTrue(isinf(sourceFile->WaitInSeconds)); + } + else { + double precision = 1e-6; + Assert::AreEqual(std::stod(waitInSeconds), sourceFile->WaitInSeconds, precision); + } + } + + void TestInvalidWaitInSecondsValues(std::wstring waitInSeconds, bool asString = false) { + std::wstring directory = L"C:\\LogMonitor\\logs"; + std::wstring configFileStr = GetConfigFileStrFormat(directory, waitInSeconds, asString); + + JsonFileParser jsonParser(configFileStr); + LoggerSettings settings; + + bool success = ReadConfigFile(jsonParser, settings); + + std::wstring output = RecoverOuput(); + + Assert::IsTrue(success); + Assert::IsTrue(output.find(L"WARNING") != std::wstring::npos); + + Assert::IsTrue(success); + Assert::IsTrue(output.find(L"WARNING") != std::wstring::npos); + } + + std::wstring GetConfigFileStrFormat(std::wstring directory, std::wstring waitInSeconds, bool asString) { + std::wstring configFileStrFormat; + if (asString) { + configFileStrFormat = + L"{ \ + \"LogConfig\": { \ + \"sources\": [ \ + {\ + \"type\": \"File\",\ + \"directory\": \"%s\",\ + \"waitInSeconds\": \"%s\"\ + }\ + ]\ + }\ + }"; + + return Utility::FormatString( + configFileStrFormat.c_str(), + Utility::ReplaceAll(directory, L"\\", L"\\\\").c_str(), + waitInSeconds.c_str() + ); + } + else { + configFileStrFormat = + L"{ \ + \"LogConfig\": { \ + \"sources\": [ \ + {\ + \"type\": \"File\",\ + \"directory\": \"%s\",\ + \"waitInSeconds\": %f\ + }\ + ]\ + }\ + }"; + return Utility::FormatString( + configFileStrFormat.c_str(), + Utility::ReplaceAll(directory, L"\\", L"\\\\").c_str(), + std::stod(waitInSeconds) + ); + } + } }; } diff --git a/LogMonitor/LogMonitorTests/LogFileMonitorTests.cpp b/LogMonitor/LogMonitorTests/LogFileMonitorTests.cpp index 9ea46d26..a31b871b 100644 --- a/LogMonitor/LogMonitorTests/LogFileMonitorTests.cpp +++ b/LogMonitor/LogMonitorTests/LogFileMonitorTests.cpp @@ -176,7 +176,7 @@ namespace LogMonitorTests fflush(stdout); ZeroMemory(bigOutBuf, sizeof(bigOutBuf)); - std::shared_ptr logfileMon = std::make_shared(sourceFile.Directory, sourceFile.Filter, sourceFile.IncludeSubdirectories, L"json", L""); + std::shared_ptr logfileMon = std::make_shared(sourceFile.Directory, sourceFile.Filter, sourceFile.IncludeSubdirectories, sourceFile.WaitInSeconds, L"json", L""); Sleep(WAIT_TIME_LOGFILEMONITOR_START); // @@ -224,7 +224,7 @@ namespace LogMonitorTests fflush(stdout); ZeroMemory(bigOutBuf, sizeof(bigOutBuf)); - std::shared_ptr logfileMon = std::make_shared(sourceFile.Directory, sourceFile.Filter, sourceFile.IncludeSubdirectories, L"json", L""); + std::shared_ptr logfileMon = std::make_shared(sourceFile.Directory, sourceFile.Filter, sourceFile.IncludeSubdirectories, sourceFile.WaitInSeconds, L"json", L""); Sleep(WAIT_TIME_LOGFILEMONITOR_START); // @@ -241,7 +241,7 @@ namespace LogMonitorTests // Check that LogFileMonitor started successfully. // output = RecoverOuput(); - Assert::AreEqual(L"", output.c_str()); + Assert::IsTrue(output.find(L"INFO") != std::wstring::npos); { std::wstring filename = sourceFile.Directory + L"\\testfile.txt"; std::string content = "Hello World!"; @@ -288,11 +288,12 @@ namespace LogMonitorTests sourceFile.Directory = tempDirectory; sourceFile.Filter = L"*.log"; sourceFile.IncludeSubdirectories = true; + sourceFile.WaitInSeconds = 10; fflush(stdout); ZeroMemory(bigOutBuf, sizeof(bigOutBuf)); - std::shared_ptr logfileMon = std::make_shared(sourceFile.Directory, sourceFile.Filter, sourceFile.IncludeSubdirectories, L"json", L""); + std::shared_ptr logfileMon = std::make_shared(sourceFile.Directory, sourceFile.Filter, sourceFile.IncludeSubdirectories, sourceFile.WaitInSeconds, L"json", L""); Sleep(WAIT_TIME_LOGFILEMONITOR_START); // @@ -395,7 +396,7 @@ namespace LogMonitorTests fflush(stdout); ZeroMemory(bigOutBuf, sizeof(bigOutBuf)); - std::shared_ptr logfileMon = std::make_shared(sourceFile.Directory, sourceFile.Filter, sourceFile.IncludeSubdirectories, L"json", L""); + std::shared_ptr logfileMon = std::make_shared(sourceFile.Directory, sourceFile.Filter, sourceFile.IncludeSubdirectories, sourceFile.WaitInSeconds, L"json", L""); Sleep(WAIT_TIME_LOGFILEMONITOR_START); // @@ -566,11 +567,12 @@ namespace LogMonitorTests sourceFile.Directory = tempDirectory; sourceFile.Filter = L"*.log"; sourceFile.IncludeSubdirectories = true; + sourceFile.WaitInSeconds = 10; fflush(stdout); ZeroMemory(bigOutBuf, sizeof(bigOutBuf)); - std::shared_ptr logfileMon = std::make_shared(sourceFile.Directory, sourceFile.Filter, sourceFile.IncludeSubdirectories, L"json", L""); + std::shared_ptr logfileMon = std::make_shared(sourceFile.Directory, sourceFile.Filter, sourceFile.IncludeSubdirectories, sourceFile.WaitInSeconds, L"json", L""); Sleep(WAIT_TIME_LOGFILEMONITOR_START); // @@ -671,11 +673,12 @@ namespace LogMonitorTests sourceFile.Directory = tempDirectory; sourceFile.Filter = L"*.log"; sourceFile.IncludeSubdirectories = true; + sourceFile.WaitInSeconds = 10; fflush(stdout); ZeroMemory(bigOutBuf, sizeof(bigOutBuf)); - std::shared_ptr logfileMon = std::make_shared(sourceFile.Directory, sourceFile.Filter, sourceFile.IncludeSubdirectories, L"json", L""); + std::shared_ptr logfileMon = std::make_shared(sourceFile.Directory, sourceFile.Filter, sourceFile.IncludeSubdirectories, sourceFile.WaitInSeconds, L"json", L""); Sleep(WAIT_TIME_LOGFILEMONITOR_START); // @@ -790,11 +793,12 @@ namespace LogMonitorTests sourceFile.Directory = tempDirectory; sourceFile.Filter = L"*.log"; sourceFile.IncludeSubdirectories = true; + sourceFile.WaitInSeconds = 10; fflush(stdout); ZeroMemory(bigOutBuf, sizeof(bigOutBuf)); - std::shared_ptr logfileMon = std::make_shared(sourceFile.Directory, sourceFile.Filter, sourceFile.IncludeSubdirectories, L"json", L""); + std::shared_ptr logfileMon = std::make_shared(sourceFile.Directory, sourceFile.Filter, sourceFile.IncludeSubdirectories, sourceFile.WaitInSeconds, L"json", L""); Sleep(WAIT_TIME_LOGFILEMONITOR_START); // @@ -980,11 +984,12 @@ namespace LogMonitorTests sourceFile.Directory = tempDirectory; sourceFile.Filter = L"*.log"; sourceFile.IncludeSubdirectories = true; + sourceFile.WaitInSeconds = 10; fflush(stdout); ZeroMemory(bigOutBuf, sizeof(bigOutBuf)); - std::shared_ptr logfileMon = std::make_shared(sourceFile.Directory, sourceFile.Filter, sourceFile.IncludeSubdirectories, L"json", L""); + std::shared_ptr logfileMon = std::make_shared(sourceFile.Directory, sourceFile.Filter, sourceFile.IncludeSubdirectories, sourceFile.WaitInSeconds, L"json", L""); Sleep(WAIT_TIME_LOGFILEMONITOR_START); // diff --git a/LogMonitor/LogMonitorTests/LogMonitorTests.cpp b/LogMonitor/LogMonitorTests/LogMonitorTests.cpp index 9ec03f52..5aba0f2d 100644 --- a/LogMonitor/LogMonitorTests/LogMonitorTests.cpp +++ b/LogMonitor/LogMonitorTests/LogMonitorTests.cpp @@ -9,7 +9,7 @@ #include "../src/LogMonitor/EtwMonitor.cpp" #include "../src/LogMonitor/EventMonitor.cpp" #include "../src/LogMonitor/JsonFileParser.cpp" -#include "../src/LogMonitor/FileMonitor/Utilities.cpp" +#include "../src/LogMonitor/FileMonitor/FileMonitorUtilities.cpp" #include "../src/LogMonitor/LogFileMonitor.cpp" #include "../src/LogMonitor/ProcessMonitor.cpp" #include "../src/LogMonitor/Utility.cpp" diff --git a/LogMonitor/LogMonitorTests/pch.h b/LogMonitor/LogMonitorTests/pch.h index 2e83e088..c6ad8c39 100644 --- a/LogMonitor/LogMonitorTests/pch.h +++ b/LogMonitor/LogMonitorTests/pch.h @@ -59,7 +59,7 @@ #include "../src/LogMonitor/LogWriter.h" #include "../src/LogMonitor/EtwMonitor.h" #include "../src/LogMonitor/EventMonitor.h" -#include "../src/LogMonitor/FileMonitor/Utilities.h" +#include "../src/LogMonitor/FileMonitor/FileMonitorUtilities.h" #include "../src/LogMonitor/LogFileMonitor.h" #include "../src/LogMonitor/ProcessMonitor.h" #include "Utility.h" diff --git a/LogMonitor/docs/README.md b/LogMonitor/docs/README.md index fafb5e4d..8cb264f1 100644 --- a/LogMonitor/docs/README.md +++ b/LogMonitor/docs/README.md @@ -218,7 +218,42 @@ This will monitor any changes in log files matching a specified filter, given th - `filter` (optional): uses [MS-DOS wildcard match type](https://learn.microsoft.com/en-us/previous-versions/windows/desktop/indexsrv/ms-dos-and-windows-wildcard-characters) i.e.. `*, ?`. Can be set to empty, which will be default to `"*"`. - `includeSubdirectories` (optional) : `"true|false"`, specify if sub-directories also need to be monitored. Defaults to `false`. - `includeFileNames` (optional): `"true|false"`, specifies whether to include file names in the logline, eg. `sample.log: xxxxx`. Defaults to `false`. +- `waitInSeconds` (optional): specifies the duration to wait for a file or folder to be created if it does not exist. It takes integer values between 0-INFINITY. Defaults to `300` seconds, i.e, 5 minutes. It can be passed as a value or a string. + - `waitInSeconds = 0` + + When the value is zero(0), this is means that we do not wait and LogMonitor terminates with an error + + - `waitInSeconds = +integer` + + When the value is a positive integer, LogMonitor will wait for the specified time. Once the predefined time elapses, LogMonitor will terminate with an error. + + - `waitInSeconds = "INFINITY"` + + In this case, LogMonitor will wait forever for the folder to be created. + + **NOTE:** + - This field is case insensitive + - When "INFINITY" is passed, it must be passed as a string. + - The infinity symbol, ∞, is also allowed as a string or the symbol itself. + +
+ + **Examples:** + 1. Wait for 10 seconds + * As a value: `"waitInSeconds": 10` + * As a string: `"waitInSeconds": "10"` + 2. Wait forever/infinitely: + * `"waitInSeconds": "INFINITY"` or `"waitInSeconds": "inf"` or `"waitInSeconds": "∞"` + * This field is case-insensitive + +
+ + If a user provides an invalid value, a value less than 0, an error occurs: + ``` + ERROR: Error parsing configuration file. 'waitInSeconds' attribute must be greater or equal to zero + WARNING: Failed to parse configuration file. Error retrieving source attributes. Invalid source + ``` ### Examples @@ -231,13 +266,36 @@ This will monitor any changes in log files matching a specified filter, given th "directory": "c:\\inetpub\\logs", "filter": "*.log", "includeSubdirectories": true, - "includeFileNames": false + "includeFileNames": false, + "waitInSeconds": 10 } ] } } ``` +**Note:** When the directory is the root directory (e.g. C:\\ ) we can only monitor a file that is in the root directory, not a subfolder. This is due to access issues (even when running LogMonitor as an Admin) for some of the folders in the root directory. Therefore, `includeSubdirectories` must be `false` for the root directory. See example below: + +```json +{ + "LogConfig": { + "sources": [ + { + "type": "File", + "directory": "C:", + "filter": "*.log", + "includeSubdirectories": false + } + ] + } +} +``` +When the root directory is passed and `includeSubdirectories = true`, we get an error: +``` +ERROR: LoggerSettings: Invalid Source File atrribute 'directory' (C:) and 'includeSubdirectories' (true).'includeSubdirectories' attribute cannot be 'true' for the root directory +WARNING: Failed to parse configuration file. Error retrieving source attributes. Invalid source +``` + ## Process Monitoring ### Description diff --git a/LogMonitor/src/LogMonitor/ConfigFileParser.cpp b/LogMonitor/src/LogMonitor/ConfigFileParser.cpp index 7f3fa8eb..609c4ef3 100644 --- a/LogMonitor/src/LogMonitor/ConfigFileParser.cpp +++ b/LogMonitor/src/LogMonitor/ConfigFileParser.cpp @@ -6,6 +6,7 @@ #include "pch.h" #include "./Parser/ConfigFileParser.h" #include "./LogWriter.h" +#include "./FileMonitor/FileMonitorUtilities.h" /// ConfigFileParser.cpp /// @@ -312,8 +313,13 @@ ReadSourceAttributes( // * filter // * lineLogFormat // - else if (_wcsnicmp(key.c_str(), JSON_TAG_DIRECTORY, _countof(JSON_TAG_DIRECTORY)) == 0 - || _wcsnicmp(key.c_str(), JSON_TAG_FILTER, _countof(JSON_TAG_FILTER)) == 0 + else if (_wcsnicmp(key.c_str(), JSON_TAG_DIRECTORY, _countof(JSON_TAG_DIRECTORY)) == 0) + { + std::wstring directory = Parser.ParseStringValue(); + FileMonitorUtilities::ParseDirectoryValue(directory); + Attributes[key] = new std::wstring(directory); + } + else if (_wcsnicmp(key.c_str(), JSON_TAG_FILTER, _countof(JSON_TAG_FILTER)) == 0 || _wcsnicmp(key.c_str(), JSON_TAG_CUSTOM_LOG_FORMAT, _countof(JSON_TAG_CUSTOM_LOG_FORMAT)) == 0) { Attributes[key] = new std::wstring(Parser.ParseStringValue()); @@ -370,6 +376,29 @@ ReadSourceAttributes( Attributes[key] = providers; } } + else if (_wcsnicmp(key.c_str(), JSON_TAG_WAITINSECONDS, _countof(JSON_TAG_WAITINSECONDS)) == 0) + { + try + { + auto parsedValue = new std::double_t(Parser.ParseNumericValue()); + if (*parsedValue < 0) + { + logWriter.TraceError(L"Error parsing configuration file. 'waitInSeconds' attribute must be greater or equal to zero"); + success = false; + } + else + { + Attributes[key] = parsedValue; + } + } + catch(const std::exception& ex) + { + logWriter.TraceError( + Utility::FormatString(L"Error parsing configuration file atrribute 'waitInSeconds'. %S", ex.what()).c_str() + ); + success = false; + } + } else { // @@ -380,6 +409,12 @@ ReadSourceAttributes( } while (Parser.ParseNextObjectElement()); } + bool isSourceFileValid = ValidateDirectoryAttributes(Attributes); + if (!isSourceFileValid) + { + success = false; + } + return success; } @@ -624,6 +659,36 @@ AddNewSource( return true; } +/// +/// Validates that when root directory is passed, includeSubdirectories is false +/// +/// \param Attributes An AttributesMap that contains the attributes of the new source objet. +/// \return false when root directory is passed, includeSubdirectories = true. Otherwise, true +bool ValidateDirectoryAttributes(_In_ AttributesMap &Attributes) +{ + if (!Utility::ConfigAttributeExists(Attributes, JSON_TAG_DIRECTORY) || + !Utility::ConfigAttributeExists(Attributes, JSON_TAG_INCLUDE_SUBDIRECTORIES)) + { + return true; + } + + std::wstring directory = *(std::wstring *)Attributes[JSON_TAG_DIRECTORY]; + const bool includeSubdirectories = *(bool *)Attributes[JSON_TAG_INCLUDE_SUBDIRECTORIES]; + + // Check if Log file monitor config is valid + const bool isValid = FileMonitorUtilities::IsValidSourceFile(directory, includeSubdirectories); + if (!isValid) + { + logWriter.TraceError( + Utility::FormatString( + L"LoggerSettings: Invalid Source File atrribute 'directory' (%s) and 'includeSubdirectories' (%s)." + L"'includeSubdirectories' attribute cannot be 'true' for the root directory", + directory.c_str(), includeSubdirectories ? L"true" : L"false") + .c_str()); + } + return isValid; +} + /// /// Debug function /// @@ -663,6 +728,7 @@ void _PrintSettings(_Out_ LoggerSettings& Config) std::wprintf(L"\t\tDirectory: %ls\n", sourceFile->Directory.c_str()); std::wprintf(L"\t\tFilter: %ls\n", sourceFile->Filter.c_str()); std::wprintf(L"\t\tIncludeSubdirectories: %ls\n", sourceFile->IncludeSubdirectories ? L"true" : L"false"); + std::wprintf(L"\t\twaitInSeconds: %d\n", int(sourceFile->WaitInSeconds)); std::wprintf(L"\n"); break; diff --git a/LogMonitor/src/LogMonitor/EtwMonitor.cpp b/LogMonitor/src/LogMonitor/EtwMonitor.cpp index a26635b6..24490c6b 100644 --- a/LogMonitor/src/LogMonitor/EtwMonitor.cpp +++ b/LogMonitor/src/LogMonitor/EtwMonitor.cpp @@ -680,9 +680,22 @@ EtwMonitor::OnRecordEvent( if (ERROR_SUCCESS != status) { + GUID providerId = EventRecord->EventHeader.ProviderId; + + LPOLESTR clsidString; + if (StringFromCLSID(providerId, &clsidString) != S_OK) + { + logWriter.TraceError( + Utility::FormatString(L"Failed to convert GUID to string, ran out of memory Error: %lu", + E_OUTOFMEMORY).c_str()); + } + + std::wstring guidString(clsidString); + CoTaskMemFree(clsidString); + logWriter.TraceError( - Utility::FormatString(L"Failed to query ETW event information. Error: %lu", status).c_str() - ); + Utility::FormatString(L"Failed to query ETW event information for ProviderGUID: %s Error: %lu", + guidString, status).c_str()); } diff --git a/LogMonitor/src/LogMonitor/EventMonitor.cpp b/LogMonitor/src/LogMonitor/EventMonitor.cpp index 49327554..68b75286 100644 --- a/LogMonitor/src/LogMonitor/EventMonitor.cpp +++ b/LogMonitor/src/LogMonitor/EventMonitor.cpp @@ -622,20 +622,107 @@ EventMonitor::PrintEvent( void EventMonitor::EnableEventLogChannels() { - for (const auto& eventChannel : m_eventChannels) - { - EnableEventLogChannel(eventChannel.Name.c_str()); + for (const auto& eventChannel : m_eventChannels) { + DWORD status = EnableEventLogChannel(eventChannel.Name.c_str()); + + if (status == RPC_S_SERVER_UNAVAILABLE) { + HANDLE timerEvent = CreateWaitableTimer(NULL, FALSE, NULL); + + if (!timerEvent) { + status = GetLastError(); + logWriter.TraceError( + Utility::FormatString( + L"Failed to create timer object.", status).c_str()); + break; + } + + std::double_t waitInSeconds = 300; + + int elapsedTime = 0; + + const int eventsCount = 2; + HANDLE channelEnableEvents[eventsCount] = {m_stopEvent, timerEvent}; + + while (elapsedTime < waitInSeconds) { + int waitInterval = Utility::GetWaitInterval(waitInSeconds, elapsedTime); + LARGE_INTEGER timeToWait = Utility::ConvertWaitIntervalToLargeInt(waitInterval); + BOOL waitableTimer = SetWaitableTimer(timerEvent, &timeToWait, 0, NULL, NULL, 0); + if (!waitableTimer) { + status = GetLastError(); + logWriter.TraceError( + Utility::FormatString( + L"Failed to set timer object to enable %s event channel. Error: %lu", + eventChannel.Name.c_str(), + status).c_str()); + break; + } + + DWORD wait = WaitForMultipleObjects(eventsCount, channelEnableEvents, FALSE, INFINITE); + switch (wait) { + case WAIT_OBJECT_0: + { + // + // The process is exiting. Stop the timer and return. + // + CancelWaitableTimer(timerEvent); + CloseHandle(timerEvent); + } + + case WAIT_OBJECT_0 + 1: + { + // + // Timer event. Retry enabling the failing channel. + // + break; + } + + default: + { + // + // Wait failed, return the failure. + // + status = GetLastError(); + + logWriter.TraceError( + Utility::FormatString( + L"Failed to enable event channel. Channel: %ws Error: 0x%X", + eventChannel.Name.c_str(), status).c_str()); + + CancelWaitableTimer(timerEvent); + CloseHandle(timerEvent); + } + } + + DWORD status = EnableEventLogChannel(eventChannel.Name.c_str()); + + if (status == RPC_S_SERVER_UNAVAILABLE) { + elapsedTime += Utility::WAIT_INTERVAL; + } else { + logWriter.TraceInfo( + Utility::FormatString( + L"Enabled %s event channel after %d seconds.", + eventChannel.Name.c_str(), + elapsedTime).c_str() ); + status = ERROR_SUCCESS; + break; + } + } + + CancelWaitableTimer(timerEvent); + CloseHandle(timerEvent); + } } } + /// Enables or disables an Event Log channel. /// /// \param ChannelPath Full path to the event log channel. /// /// \return None /// -void +DWORD EventMonitor::EnableEventLogChannel( _In_ LPCWSTR ChannelPath ) @@ -649,8 +736,7 @@ EventMonitor::EnableEventLogChannel( // Open the channel configuration. // channelConfig = EvtOpenChannelConfig(NULL, ChannelPath, 0); - if (NULL == channelConfig) - { + if (NULL == channelConfig) { status = GetLastError(); goto Exit; } @@ -660,26 +746,20 @@ EventMonitor::EnableEventLogChannel( 0, sizeof(EVT_VARIANT), &propValue, - &dwPropValSize)) - { + &dwPropValSize)) { // // Return if event channel is already enabled. // - if (propValue.BooleanVal) - { + if (propValue.BooleanVal) { goto Exit; } - } - else - { + } else { status = GetLastError(); logWriter.TraceError( Utility::FormatString( L"Failed to query event channel configuration. Channel: %ws Error: 0x%X", ChannelPath, - status - ).c_str() - ); + status).c_str()); } // @@ -693,9 +773,7 @@ EventMonitor::EnableEventLogChannel( channelConfig, EvtChannelConfigEnabled, 0, - &propValue - )) - { + &propValue)) { status = GetLastError(); goto Exit; } @@ -703,18 +781,14 @@ EventMonitor::EnableEventLogChannel( // // Save changes. // - if (!EvtSaveChannelConfig(channelConfig, 0)) - { + if (!EvtSaveChannelConfig(channelConfig, 0)) { status = GetLastError(); - if (status == ERROR_EVT_INVALID_OPERATION_OVER_ENABLED_DIRECT_CHANNEL) - { + if (status == ERROR_EVT_INVALID_OPERATION_OVER_ENABLED_DIRECT_CHANNEL) { // // The channel is already enabled. // status = ERROR_SUCCESS; - } - else - { + } else { goto Exit; } } @@ -723,17 +797,17 @@ EventMonitor::EnableEventLogChannel( Exit: - if (ERROR_SUCCESS != status) - { - logWriter.TraceError( - Utility::FormatString(L"Failed to enable event channel %ws: 0x%X", ChannelPath, status).c_str() - ); + if (ERROR_SUCCESS != status) { + logWriter.TraceInfo( + Utility::FormatString(L"Waiting for %ws event channel to be enabled", + ChannelPath).c_str()); } - if (channelConfig != NULL) - { + if (channelConfig != NULL) { EvtClose(channelConfig); } + + return status; } std::wstring EventMonitor::EventFieldsMapping(_In_ std::wstring eventField, _In_ void* pLogEntryData) diff --git a/LogMonitor/src/LogMonitor/EventMonitor.h b/LogMonitor/src/LogMonitor/EventMonitor.h index 4ce506ba..93f49ef2 100644 --- a/LogMonitor/src/LogMonitor/EventMonitor.h +++ b/LogMonitor/src/LogMonitor/EventMonitor.h @@ -8,6 +8,7 @@ class EventMonitor final { public: + EventMonitor() = delete; EventMonitor( @@ -71,7 +72,9 @@ class EventMonitor final _In_ const HANDLE& EventHandle ); + void EnableEventLogChannels(); - static void EnableEventLogChannel(_In_ LPCWSTR ChannelPath); + static DWORD EnableEventLogChannel( + _In_ LPCWSTR ChannelPath); }; diff --git a/LogMonitor/src/LogMonitor/FileMonitor/FileMonitorUtilities.cpp b/LogMonitor/src/LogMonitor/FileMonitor/FileMonitorUtilities.cpp new file mode 100644 index 00000000..8738bd19 --- /dev/null +++ b/LogMonitor/src/LogMonitor/FileMonitor/FileMonitorUtilities.cpp @@ -0,0 +1,230 @@ +// +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. +// + +#include "pch.h" +#include + +/** + * Wrapper around Create Event API + * + * @param bManualReset + * @param bInitialState + * + * return event handle + */ +HANDLE FileMonitorUtilities::CreateFileMonitorEvent( + _In_ BOOL bManualReset, + _In_ BOOL bInitialState) +{ + HANDLE event = CreateEvent(nullptr, bManualReset, bInitialState, nullptr); + if (!event) + { + throw std::system_error(std::error_code(GetLastError(), std::system_category()), "CreateEvent"); + } + + return event; +} + +/** + * @brief Get Log Directory Handle Object + * + * @param logDirectory - path to get handle + * @param stopEvent - pass an event to use when we want to stop waiting + * + * @return HANDLE + */ +HANDLE FileMonitorUtilities::GetLogDirHandle( + _In_ std::wstring logDirectory, + _In_ HANDLE stopEvent, + _In_ std::double_t waitInSeconds) +{ + DWORD status = ERROR_SUCCESS; + + HANDLE logDirHandle = CreateFileW(logDirectory.c_str(), + FILE_LIST_DIRECTORY, + FILE_SHARE_READ | FILE_SHARE_WRITE, + nullptr, + OPEN_EXISTING, + FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OVERLAPPED, + nullptr); + + if (logDirHandle == INVALID_HANDLE_VALUE) + { + status = GetLastError(); + } + + if (status == ERROR_FILE_NOT_FOUND || + status == ERROR_PATH_NOT_FOUND) + { + std::wstring waitLogMesage = FileMonitorUtilities::_GetWaitLogMessage(logDirectory, waitInSeconds); + logWriter.TraceInfo(waitLogMesage.c_str()); + + // + // Log directory is not created yet. Keep retrying every + // 15 seconds for upto five minutes. Also start reading the + // log files from the beginning, instead of current end of + // file. + // + HANDLE timerEvent = CreateWaitableTimer(NULL, FALSE, NULL); + if (!timerEvent) + { + status = GetLastError(); + logWriter.TraceError( + Utility::FormatString( + L"Failed to create timer object. Log directory %ws will not be monitored for log entries. Error=%d", + logDirectory.c_str(), status).c_str()); + return INVALID_HANDLE_VALUE; + } + + logDirHandle = FileMonitorUtilities::_RetryOpenDirectoryWithInterval( + logDirectory, waitInSeconds, stopEvent, timerEvent); + + CancelWaitableTimer(timerEvent); + CloseHandle(timerEvent); + } + + return logDirHandle; +} + +void FileMonitorUtilities::ParseDirectoryValue(_Inout_ std::wstring &directory) +{ + while (!directory.empty() && directory[directory.size() - 1] == L'\\') + { + directory.resize(directory.size() - 1); + } +} + +bool FileMonitorUtilities::IsValidSourceFile(_In_ std::wstring directory, _In_ bool includeSubdirectories) +{ + bool isRootFolder = CheckIsRootFolder(directory); + + // The source file is invalid if the directory is a root folder and includeSubdirectories = true + // This is because we do not monitor subfolders in the root directory + return !(isRootFolder && includeSubdirectories); +} + +bool FileMonitorUtilities::CheckIsRootFolder(_In_ std::wstring dirPath) +{ + std::wregex pattern(L"^\\w:?$"); + + std::wsmatch matches; + return std::regex_search(dirPath, matches, pattern); +} + +HANDLE FileMonitorUtilities::_RetryOpenDirectoryWithInterval( + std::wstring logDirectory, + std::double_t waitInSeconds, + HANDLE stopEvent, + HANDLE timerEvent) +{ + HANDLE logDirHandle = INVALID_HANDLE_VALUE; + DWORD status = ERROR_FILE_NOT_FOUND; + int elapsedTime = 0; + + const int eventsCount = 2; + HANDLE dirOpenEvents[eventsCount] = {stopEvent, timerEvent}; + + while (FileMonitorUtilities::_IsFileErrorStatus(status) && elapsedTime < waitInSeconds) + { + int waitInterval = Utility::GetWaitInterval(waitInSeconds, elapsedTime); + LARGE_INTEGER timeToWait = Utility::ConvertWaitIntervalToLargeInt(waitInterval); + + BOOL waitableTimer = SetWaitableTimer(timerEvent, &timeToWait, 0, NULL, NULL, 0); + if (!waitableTimer) + { + status = GetLastError(); + logWriter.TraceError( + Utility::FormatString( + L"Failed to set timer object to monitor log file changes in directory %s. Error: %lu", + logDirectory.c_str(), + status) + .c_str()); + break; + } + + DWORD wait = WaitForMultipleObjects(eventsCount, dirOpenEvents, FALSE, INFINITE); + switch (wait) + { + case WAIT_OBJECT_0: + { + // + // The process is exiting. Stop the timer and return. + // + CancelWaitableTimer(timerEvent); + CloseHandle(timerEvent); + return INVALID_HANDLE_VALUE; + } + + case WAIT_OBJECT_0 + 1: + { + // + // Timer event. Retry opening directory handle. + // + break; + } + + default: + { + // + // Wait failed, return the failure. + // + status = GetLastError(); + + CancelWaitableTimer(timerEvent); + CloseHandle(timerEvent); + + return INVALID_HANDLE_VALUE; + } + } + + logDirHandle = CreateFileW( + logDirectory.c_str(), + FILE_LIST_DIRECTORY, + FILE_SHARE_READ | FILE_SHARE_WRITE, + nullptr, + OPEN_EXISTING, + FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OVERLAPPED, + nullptr); + + if (logDirHandle == INVALID_HANDLE_VALUE) + { + status = GetLastError(); + elapsedTime += Utility::WAIT_INTERVAL; + } + else + { + logWriter.TraceInfo( + Utility::FormatString( + L"Log directory %ws found after %d seconds.", logDirectory.c_str(), elapsedTime) + .c_str()); + status = ERROR_SUCCESS; + break; + } + } + + return logDirHandle; +} + +bool FileMonitorUtilities::_IsFileErrorStatus(DWORD status) +{ + return status == ERROR_FILE_NOT_FOUND || status == ERROR_PATH_NOT_FOUND; +} + +std::wstring FileMonitorUtilities::_GetWaitLogMessage(std::wstring logDirectory, std::double_t waitInSeconds) +{ + if (isinf(waitInSeconds)) + { + return Utility::FormatString( + L"Log directory %ws does not exist. LogMonitor will wait infinitely for the directory to be created.", + logDirectory.c_str()); + } + else + { + return Utility::FormatString( + L"Log directory %ws does not exist. LogMonitor will wait for %.0f seconds for the directory to be created.", + logDirectory.c_str(), + waitInSeconds); + } +} diff --git a/LogMonitor/src/LogMonitor/FileMonitor/FileMonitorUtilities.h b/LogMonitor/src/LogMonitor/FileMonitor/FileMonitorUtilities.h new file mode 100644 index 00000000..bb4cc4f4 --- /dev/null +++ b/LogMonitor/src/LogMonitor/FileMonitor/FileMonitorUtilities.h @@ -0,0 +1,39 @@ +// +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. +// + +#pragma once + +class FileMonitorUtilities final +{ + public: + + static HANDLE CreateFileMonitorEvent( + _In_ BOOL bManualReset, + _In_ BOOL bInitialState); + + static HANDLE GetLogDirHandle( + _In_ std::wstring logDirectory, + _In_ HANDLE stopEvent, + _In_ std::double_t waitInSeconds); + + static void ParseDirectoryValue(_Inout_ std::wstring &directory); + + static bool IsValidSourceFile(_In_ std::wstring directory, bool includeSubdirectories); + + static bool CheckIsRootFolder(_In_ std::wstring dirPath); + + private: + static HANDLE _RetryOpenDirectoryWithInterval( + std::wstring logDirectory, + std::double_t waitInSeconds, + HANDLE stopEvent, + HANDLE timerEvent); + + static bool _IsFileErrorStatus(DWORD status); + + static std::wstring _GetWaitLogMessage( + std::wstring logDirectory, + std::double_t waitInSeconds); +}; diff --git a/LogMonitor/src/LogMonitor/FileMonitor/Utilities.cpp b/LogMonitor/src/LogMonitor/FileMonitor/Utilities.cpp deleted file mode 100644 index e95491ee..00000000 --- a/LogMonitor/src/LogMonitor/FileMonitor/Utilities.cpp +++ /dev/null @@ -1,162 +0,0 @@ -// -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT license. -// - -#include "pch.h" - -/** - * Warapper around Create Event API - * - * @param bManualReset - * @param bInitialState - * - * return event handle -*/ -HANDLE CreateFileMonitorEvent( - _In_ BOOL bManualReset, - _In_ BOOL bInitialState -) { - HANDLE event = CreateEvent(nullptr, bManualReset, bInitialState, nullptr); - if(!event) - { - throw std::system_error(std::error_code(GetLastError(), std::system_category()), "CreateEvent"); - } - - return event; -} - - -/** - * @brief Get Log Directory Handle Object - * - * @param logDirectory - path to get handle - * @param stopEvent - pass an event to use when we want to stop waiting - * - * @return HANDLE - */ -HANDLE GetLogDirHandle(std::wstring logDirectory, HANDLE stopEvent) { - DWORD status = ERROR_SUCCESS; - const int eventsCount = 2; - - HANDLE logDirHandle = CreateFileW (logDirectory.c_str(), - FILE_LIST_DIRECTORY, - FILE_SHARE_READ | FILE_SHARE_WRITE, - nullptr, - OPEN_EXISTING, - FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OVERLAPPED, - nullptr); - - if (logDirHandle == INVALID_HANDLE_VALUE) - { - status = GetLastError(); - } - - if (status == ERROR_FILE_NOT_FOUND || - status == ERROR_PATH_NOT_FOUND) - { - // - // Log directory is not created yet. Keep retrying every - // 15 seconds for upto five minutes. Also start reading the - // log files from the begining, instead of current end of - // file. - // - const DWORD maxRetryCount = 20; - DWORD retryCount = 1; - LARGE_INTEGER liDueTime; - INT64 millisecondsToWait = 15000LL; - liDueTime.QuadPart = -millisecondsToWait*10000LL; // wait time in 100 nanoseconds - - HANDLE timerEvent = CreateWaitableTimer(NULL, FALSE, NULL); - if (!timerEvent) - { - status = GetLastError(); - logWriter.TraceError( - Utility::FormatString( - L"Failed to create timer object. Log directory %ws will not be monitored for log entries. Error=%d", - logDirectory.c_str(), - status - ).c_str() - ); - return INVALID_HANDLE_VALUE; - } - - HANDLE dirOpenEvents[eventsCount] = {stopEvent, timerEvent}; - - while (status == ERROR_FILE_NOT_FOUND && - retryCount < maxRetryCount) - { - if (!SetWaitableTimer(timerEvent, &liDueTime, 0, NULL, NULL, 0)) - { - status = GetLastError(); - logWriter.TraceError( - Utility::FormatString( - L"Failed to set timer object to monitor log file changes in directory %s. Error: %lu", - logDirectory.c_str(), - status - ).c_str() - ); - break; - } - - DWORD wait = WaitForMultipleObjects(eventsCount, dirOpenEvents, FALSE, INFINITE); - switch(wait) - { - case WAIT_OBJECT_0: - { - // - // The process is exiting. Stop the timer and return. - // - CancelWaitableTimer(timerEvent); - CloseHandle(timerEvent); - return INVALID_HANDLE_VALUE; - } - - case WAIT_OBJECT_0 + 1: - { - // - // Timer event. Retry opening directory handle. - // - break; - } - - default: - { - // - //Wait failed, return the failure. - // - status = GetLastError(); - - CancelWaitableTimer(timerEvent); - CloseHandle(timerEvent); - - return INVALID_HANDLE_VALUE; - } - } - - logDirHandle = CreateFileW (logDirectory.c_str(), - FILE_LIST_DIRECTORY, - FILE_SHARE_READ | FILE_SHARE_WRITE, - nullptr, - OPEN_EXISTING, - FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OVERLAPPED, - nullptr); - - if (logDirHandle == INVALID_HANDLE_VALUE) - { - status = GetLastError(); - ++retryCount; - } - else - { - status = ERROR_SUCCESS; - break; - } - } - - CancelWaitableTimer(timerEvent); - CloseHandle(timerEvent); - } - - return logDirHandle; -} diff --git a/LogMonitor/src/LogMonitor/FileMonitor/Utilities.h b/LogMonitor/src/LogMonitor/FileMonitor/Utilities.h deleted file mode 100644 index 9904cbda..00000000 --- a/LogMonitor/src/LogMonitor/FileMonitor/Utilities.h +++ /dev/null @@ -1,14 +0,0 @@ -// -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT license. -// - -#pragma once - -HANDLE CreateFileMonitorEvent( - _In_ BOOL bManualReset, - _In_ BOOL bInitialState); - -HANDLE GetLogDirHandle( - _In_ std::wstring logDirectory, - _In_ HANDLE stopEvent); diff --git a/LogMonitor/src/LogMonitor/JsonFileParser.cpp b/LogMonitor/src/LogMonitor/JsonFileParser.cpp index f9274140..54f4cde0 100644 --- a/LogMonitor/src/LogMonitor/JsonFileParser.cpp +++ b/LogMonitor/src/LogMonitor/JsonFileParser.cpp @@ -4,6 +4,7 @@ // #include "pch.h" +#include /// JsonFileParser.cpp /// @@ -74,7 +75,6 @@ JsonFileParser::ParseStringValue() } } - /// /// Parses a escape sequence control character. /// @@ -145,6 +145,122 @@ JsonFileParser::ParseSpecialCharacter(int Ch) return (static_cast(Ch)); } +/// +/// Parses a real number (positive or negative integers or decimals) passed as a number (eg. 10) or string (eg. "10"). +/// It also parses infinity values (eg.: Inf, -Inf, INFINITY, INFINITY) +/// +/// \return A double value. +/// +const std::double_t & +JsonFileParser::ParseNumericValue() +{ + size_t offset = 0; + int ch = PeekNextCharacter(offset); + + if (ch != '"') + { + return ParseNumber(); + } + else + { + // Convert string numerical value or infinity to double + std::wstring parsedStringValue = ParseStringValue(); + try + { + if (ContainsInfinitySymbol(parsedStringValue)) { + m_doubleValue = parsedStringValue[0] == '-' ? -INFINITY : INFINITY; + } + else + { + // convert string to double + m_doubleValue = std::stod(parsedStringValue.c_str()); + } + return m_doubleValue; + } + catch (...) + { + throw std::invalid_argument("JsonFileParser: Invalid value. Expected number or infinity"); + } + } +} + +/// +/// Parses a real number (positive or negative integers or decimals) passed as a number +/// +/// \return A double value. +/// +const std::double_t& +JsonFileParser::ParseNumber() +{ + bool negativeValue = false; + bool decimalFound = false; + bool infinitySymbolFound = false; + + double parsedValue = 0.0; + double decimalMultiplier = 0.1; + + size_t offset = 0; + int ch = PeekNextCharacter(offset); + + if (ch == '-' || ch == '+') + { + negativeValue = ch == '-'; + offset++; + } + + do + { + ch = PeekNextCharacter(offset); + if (ch >= '0' && ch <= '9') + { + if (decimalFound) + { + parsedValue += static_cast(ch - '0') * decimalMultiplier; + decimalMultiplier *= 0.1; + } + else + { + parsedValue = parsedValue * 10 + static_cast(ch - '0'); + } + offset++; + } + else if (ch == '.') + { + if (decimalFound) + { + break; + } + + decimalFound = true; + offset++; + } + else if (ch == 8734) + { + if (infinitySymbolFound || offset > 1) + { + break; + } + + infinitySymbolFound = true; + parsedValue = INFINITY; + offset++; + } + else + { + // + // End of string. + // + offset++; + AdvanceBufferPointer(offset); + + m_doubleValue = negativeValue ? -parsedValue : parsedValue; + return m_doubleValue; + } + } while (ch >= '0' && ch <= '9' || ch == '.' || ch == 8734); + + // Handle the case when the loop exits prematurely + throw std::invalid_argument("JsonFileParser: Invalid numeric value"); +} /// /// Skips a number at the current position of the buffer. @@ -632,3 +748,18 @@ JsonFileParser::IsWhiteSpace( return false; } } + +/// +/// Check if parsed text contains the infinity symbol ∞ +/// +/// \return true if contains infinity symbol, false otherwise +/// +inline +bool +JsonFileParser::ContainsInfinitySymbol(std::wstring text) +{ + std::wregex pattern(L"^[+|-]?\u221E$"); + + std::wsmatch matches; + return std::regex_search(text, matches, pattern); +} diff --git a/LogMonitor/src/LogMonitor/LogFileMonitor.cpp b/LogMonitor/src/LogMonitor/LogFileMonitor.cpp index 37f08122..9e86e62c 100644 --- a/LogMonitor/src/LogMonitor/LogFileMonitor.cpp +++ b/LogMonitor/src/LogMonitor/LogFileMonitor.cpp @@ -4,6 +4,7 @@ // #include "pch.h" +#include using namespace std; @@ -36,16 +37,19 @@ using namespace std; /// \param LogDirectory: The log directory to be monitored /// \param Filter: The filter to apply when looking fr log files /// \param IncludeSubfolders: TRUE if subdirectories also needs to be monitored +/// \param WaitInSeconds: Waiting time in seconds to retry if folder/file to be monitored does not exist /// LogFileMonitor::LogFileMonitor(_In_ const std::wstring& LogDirectory, _In_ const std::wstring& Filter, _In_ bool IncludeSubfolders, + _In_ const std::double_t& WaitInSeconds, _In_ std::wstring LogFormat, _In_ std::wstring CustomLogFormat = L"" ) : m_logDirectory(LogDirectory), m_filter(Filter), m_includeSubfolders(IncludeSubfolders), + m_waitInSeconds(WaitInSeconds), m_logFormat(LogFormat), m_customLogFormat(CustomLogFormat) { @@ -59,26 +63,26 @@ LogFileMonitor::LogFileMonitor(_In_ const std::wstring& LogDirectory, InitializeSRWLock(&m_eventQueueLock); - while (!m_logDirectory.empty() && m_logDirectory[ m_logDirectory.size() - 1 ] == L'\\') - { - m_logDirectory.resize(m_logDirectory.size() - 1); - } - m_logDirectory = PREFIX_EXTENDED_PATH + m_logDirectory; + // By default, the name is limited to MAX_PATH characters. To extend this limit to 32,767 wide characters, + // we prepend "\?" to the path. Prepending the string "\?" does not allow access to the root directory + // We, therefore, do not prepend for the root directory + bool isRootFolder = CheckIsRootFolder(m_logDirectory); + m_logDirectory = isRootFolder ? m_logDirectory : PREFIX_EXTENDED_PATH + m_logDirectory; if (m_filter.empty()) { m_filter = L"*"; } - m_stopEvent = CreateFileMonitorEvent(TRUE, FALSE); + m_stopEvent = FileMonitorUtilities::CreateFileMonitorEvent(TRUE, FALSE); - m_overlappedEvent = CreateFileMonitorEvent(TRUE, TRUE); + m_overlappedEvent = FileMonitorUtilities::CreateFileMonitorEvent(TRUE, TRUE); m_overlapped.hEvent = m_overlappedEvent; - m_workerThreadEvent = CreateFileMonitorEvent(TRUE, TRUE); + m_workerThreadEvent = FileMonitorUtilities::CreateFileMonitorEvent(TRUE, TRUE); - m_dirMonitorStartedEvent = CreateFileMonitorEvent(TRUE, FALSE); + m_dirMonitorStartedEvent = FileMonitorUtilities::CreateFileMonitorEvent(TRUE, FALSE); m_readLogFilesFromStart = false; @@ -318,7 +322,7 @@ LogFileMonitor::StartLogFileMonitor() dirMonitorStartedEventSignalled = true; // Get Log Dir Handle - HANDLE logDirHandle = GetLogDirHandle(m_logDirectory, m_stopEvent); + HANDLE logDirHandle = FileMonitorUtilities::GetLogDirHandle(m_logDirectory, m_stopEvent, m_waitInSeconds); if(logDirHandle == INVALID_HANDLE_VALUE) { status = GetLastError(); @@ -340,7 +344,7 @@ LogFileMonitor::StartLogFileMonitor() { m_readLogFilesFromStart = true; } - + m_logDirHandle = logDirHandle; // @@ -812,7 +816,7 @@ LogFileMonitor::LogFilesChangeHandler() const DWORD eventsCount = 3; LARGE_INTEGER liDueTime; - INT64 millisecondsToWait = 30000LL; + INT64 millisecondsToWait = 1000LL; liDueTime.QuadPart = -millisecondsToWait*10000LL; // wait time in 100 nanoseconds HANDLE timerEvent = CreateWaitableTimer(NULL, FALSE, NULL); @@ -1011,6 +1015,8 @@ LogFileMonitor::LogFilesChangeHandler() } } + CloseHandle(timerEvent); + return status; } @@ -2071,6 +2077,15 @@ LogFileMonitor::GetFileId( return status; } +bool +LogFileMonitor::CheckIsRootFolder(_In_ std::wstring dirPath) +{ + std::wregex pattern(L"^\\w:?$"); + + std::wsmatch matches; + return std::regex_search(dirPath, matches, pattern); +} + std::wstring LogFileMonitor::FileFieldsMapping(_In_ std::wstring fileFields, _In_ void* pLogEntryData) { std::wostringstream oss; diff --git a/LogMonitor/src/LogMonitor/LogFileMonitor.h b/LogMonitor/src/LogMonitor/LogFileMonitor.h index 4e329527..a96945ad 100644 --- a/LogMonitor/src/LogMonitor/LogFileMonitor.h +++ b/LogMonitor/src/LogMonitor/LogFileMonitor.h @@ -61,6 +61,7 @@ class LogFileMonitor final _In_ const std::wstring& LogDirectory, _In_ const std::wstring& Filter, _In_ bool IncludeSubfolders, + _In_ const std::double_t& WaitInSeconds, _In_ std::wstring LogFormat, _In_ std::wstring CustomLogFormat ); @@ -76,6 +77,7 @@ class LogFileMonitor final std::wstring m_logDirectory; std::wstring m_shortLogDirectory; std::wstring m_filter; + std::double_t m_waitInSeconds; bool m_includeSubfolders; std::wstring m_logFormat; std::wstring m_customLogFormat; @@ -235,4 +237,6 @@ class LogFileMonitor final _Out_ FILE_ID_INFO& FileId, _In_opt_ HANDLE Handle = INVALID_HANDLE_VALUE ); + + static bool CheckIsRootFolder(_In_ std::wstring dirPath); }; diff --git a/LogMonitor/src/LogMonitor/LogMonitor.vcxproj b/LogMonitor/src/LogMonitor/LogMonitor.vcxproj index 62b39755..92b3de94 100644 --- a/LogMonitor/src/LogMonitor/LogMonitor.vcxproj +++ b/LogMonitor/src/LogMonitor/LogMonitor.vcxproj @@ -159,7 +159,7 @@ - + @@ -176,7 +176,7 @@ - + diff --git a/LogMonitor/src/LogMonitor/LogMonitor.vcxproj.filters b/LogMonitor/src/LogMonitor/LogMonitor.vcxproj.filters index 713f9771..56eecf60 100644 --- a/LogMonitor/src/LogMonitor/LogMonitor.vcxproj.filters +++ b/LogMonitor/src/LogMonitor/LogMonitor.vcxproj.filters @@ -24,7 +24,7 @@ Header Files - + Header Files @@ -83,7 +83,7 @@ Source Files - + Source Files diff --git a/LogMonitor/src/LogMonitor/Main.cpp b/LogMonitor/src/LogMonitor/Main.cpp index 257ca252..e619f89f 100644 --- a/LogMonitor/src/LogMonitor/Main.cpp +++ b/LogMonitor/src/LogMonitor/Main.cpp @@ -117,6 +117,7 @@ void StartMonitors(_In_ LoggerSettings& settings) sourceFile->Directory, sourceFile->Filter, sourceFile->IncludeSubdirectories, + sourceFile->WaitInSeconds, logFormat, sourceFile->CustomLogFormat ); diff --git a/LogMonitor/src/LogMonitor/Parser/ConfigFileParser.h b/LogMonitor/src/LogMonitor/Parser/ConfigFileParser.h index 333b02e6..4c8d48d7 100644 --- a/LogMonitor/src/LogMonitor/Parser/ConfigFileParser.h +++ b/LogMonitor/src/LogMonitor/Parser/ConfigFileParser.h @@ -49,4 +49,6 @@ bool AddNewSource( _Inout_ std::vector >& Sources ); +bool ValidateDirectoryAttributes(_In_ AttributesMap& Attributes); + void _PrintSettings(_Out_ LoggerSettings& Config); diff --git a/LogMonitor/src/LogMonitor/Parser/JsonFileParser.h b/LogMonitor/src/LogMonitor/Parser/JsonFileParser.h index 46fed623..dfd420dd 100644 --- a/LogMonitor/src/LogMonitor/Parser/JsonFileParser.h +++ b/LogMonitor/src/LogMonitor/Parser/JsonFileParser.h @@ -36,6 +36,8 @@ class JsonFileParser DataType GetNextDataType(); const std::wstring& ParseStringValue(); + const std::double_t& ParseNumericValue(); + const std::double_t& ParseNumber(); bool ParseBooleanValue(); @@ -76,6 +78,11 @@ class JsonFileParser // std::wstring m_stringValue; + // + // Last parsed numeric value + // + std::double_t m_doubleValue; + static const int EndOfBuffer = -1; static inline int HexToInt( @@ -86,6 +93,10 @@ class JsonFileParser _In_ int Character ); + static inline bool ContainsInfinitySymbol( + _In_ std::wstring text + ); + static inline wchar_t ParseSpecialCharacter(int Character); wchar_t ParseControlCharacter(size_t Offset); diff --git a/LogMonitor/src/LogMonitor/Parser/LoggerSettings.h b/LogMonitor/src/LogMonitor/Parser/LoggerSettings.h index 463ec1fe..25025840 100644 --- a/LogMonitor/src/LogMonitor/Parser/LoggerSettings.h +++ b/LogMonitor/src/LogMonitor/Parser/LoggerSettings.h @@ -27,6 +27,7 @@ #define JSON_TAG_FILTER L"filter" #define JSON_TAG_INCLUDE_SUBDIRECTORIES L"includeSubdirectories" #define JSON_TAG_PROVIDERS L"providers" +#define JSON_TAG_WAITINSECONDS L"waitInSeconds" /// /// Valid channel attributes @@ -46,14 +47,6 @@ // Define the AttributesMap, that is a map with case // insensitive keys // -struct CaseInsensitiveWideString -{ - bool operator() (const std::wstring& c1, const std::wstring& c2) const { - return _wcsicmp(c1.c_str(), c2.c_str()) < 0; - } -}; - -typedef std::map AttributesMap; enum class EventChannelLogLevel { @@ -272,6 +265,9 @@ class SourceFile : LogSource bool IncludeSubdirectories = false; std::wstring CustomLogFormat = L"[%TimeStamp%] [%Source%] [%FileName%] %Message%"; + // Default wait time: 5minutes + std::double_t WaitInSeconds = 300; + static bool Unwrap( _In_ AttributesMap& Attributes, _Out_ SourceFile& NewSource) @@ -319,6 +315,15 @@ class SourceFile : LogSource NewSource.CustomLogFormat = *(std::wstring*)Attributes[JSON_TAG_CUSTOM_LOG_FORMAT]; } + // + // waitInSeconds is an optional value + // + if (Attributes.find(JSON_TAG_WAITINSECONDS) != Attributes.end() + && Attributes[JSON_TAG_WAITINSECONDS] != nullptr) + { + NewSource.WaitInSeconds = *(std::double_t*)Attributes[JSON_TAG_WAITINSECONDS]; + } + return true; } }; diff --git a/LogMonitor/src/LogMonitor/Utility.cpp b/LogMonitor/src/LogMonitor/Utility.cpp index c36ec270..1f1d5dc7 100644 --- a/LogMonitor/src/LogMonitor/Utility.cpp +++ b/LogMonitor/src/LogMonitor/Utility.cpp @@ -311,6 +311,12 @@ void Utility::SanitizeJson(_Inout_ std::wstring& str) } } +bool Utility::ConfigAttributeExists(AttributesMap& Attributes, std::wstring attributeName) +{ + auto it = Attributes.find(attributeName); + return it != Attributes.end() && it->second != nullptr; +} + /// /// Comparing wstrings with ignoring the case /// @@ -328,7 +334,7 @@ bool Utility::CompareWStrings(wstring stringA, wstring stringB) [](wstring::value_type l1, wstring::value_type r1) { return towupper(l1) == towupper(r1); } - ); + ); } std::wstring Utility::FormatEventLineLog(_In_ std::wstring customLogFormat, _In_ void* pLogEntry, _In_ std::wstring sourceType) @@ -342,7 +348,8 @@ std::wstring Utility::FormatEventLineLog(_In_ std::wstring customLogFormat, _In_ if (sub[0] != '%' && sub[sub_length - 1] != '%') { j++, i++; - } else if (sub[0] == '%' && sub[sub_length - 1] == '%' && sub_length != 1) { + } + else if (sub[0] == '%' && sub[sub_length - 1] == '%' && sub_length != 1) { //valid field name found in custom log format wstring fieldValue; if (sourceType == L"ETW") { @@ -358,17 +365,18 @@ std::wstring Utility::FormatEventLineLog(_In_ std::wstring customLogFormat, _In_ customLogFormat.replace(i, sub_length, fieldValue); i = i + fieldValue.length(), j = i + 1; - } else { + } + else { j++; } } - if(customJsonFormat) + if (customJsonFormat) SanitizeJson(customLogFormat); return customLogFormat; } - + /// /// check if custom format specified in config is JSON for sanitization purposes /// @@ -392,4 +400,35 @@ bool Utility::isCustomJsonFormat(_Inout_ std::wstring& customLogFormat) customLogFormat = customLogFormat.substr(0, customLogFormat.find_last_of(L"|")); } return isCustomJSONFormat; +} + +/// +// Converts the time to wait to a large integer +/// +LARGE_INTEGER Utility::ConvertWaitIntervalToLargeInt(_In_ int timeInterval) +{ + LARGE_INTEGER liDueTime{}; + + int millisecondsToWait = timeInterval * 1000; + liDueTime.QuadPart = -millisecondsToWait * 10000LL; // wait time in 100 nanoseconds + return liDueTime; +} + +/// +/// Returns the time (in seconds) to wait based on the specified waitInSeconds +/// +int Utility::GetWaitInterval(_In_ std::double_t waitInSeconds, _In_ int elapsedTime) +{ + if (isinf(waitInSeconds)) + { + return static_cast(WAIT_INTERVAL); + } + + if (waitInSeconds < WAIT_INTERVAL) + { + return static_cast(waitInSeconds); + } + + const auto remainingTime = static_cast(waitInSeconds - elapsedTime); + return remainingTime <= WAIT_INTERVAL ? remainingTime : WAIT_INTERVAL; } \ No newline at end of file diff --git a/LogMonitor/src/LogMonitor/Utility.h b/LogMonitor/src/LogMonitor/Utility.h index 3b52c794..e3a65a89 100644 --- a/LogMonitor/src/LogMonitor/Utility.h +++ b/LogMonitor/src/LogMonitor/Utility.h @@ -5,9 +5,25 @@ #pragma once +// +// Define the AttributesMap, that is a map with case +// insensitive keys +// +struct CaseInsensitiveWideString +{ + bool operator() (const std::wstring& c1, const std::wstring& c2) const { + return _wcsicmp(c1.c_str(), c2.c_str()) < 0; + } +}; + +typedef std::map AttributesMap; + class Utility final { public: + + static const int WAIT_INTERVAL = 15; + static std::wstring SystemTimeToString( SYSTEMTIME SystemTime ); @@ -45,9 +61,18 @@ class Utility final _In_ const std::wstring& To ); - static bool isJsonNumber(_In_ std::wstring& str); + static bool isJsonNumber( + _In_ std::wstring& str); + + static void SanitizeJson( + _Inout_ std::wstring &str); - static void SanitizeJson(_Inout_ std::wstring& str); + static bool ConfigAttributeExists( + _In_ AttributesMap& Attributes, + _In_ std::wstring attributeName); + + static LARGE_INTEGER ConvertWaitIntervalToLargeInt( + _In_ int timeInterval); static bool CompareWStrings( _In_ std::wstring stringA, @@ -57,4 +82,8 @@ class Utility final static std::wstring FormatEventLineLog(_In_ std::wstring customLogFormat, _In_ void* pLogEntry, _In_ std::wstring sourceType); static bool isCustomJsonFormat(_Inout_ std::wstring& customLogFormat); + + static int GetWaitInterval( + _In_ std::double_t waitInSeconds, + _In_ int elapsedTime); }; diff --git a/LogMonitor/src/LogMonitor/pch.h b/LogMonitor/src/LogMonitor/pch.h index 32fe349a..230c1d05 100644 --- a/LogMonitor/src/LogMonitor/pch.h +++ b/LogMonitor/src/LogMonitor/pch.h @@ -52,7 +52,7 @@ #include "LogWriter.h" #include "EtwMonitor.h" #include "EventMonitor.h" -#include "FileMonitor/Utilities.h" +#include "FileMonitor/FileMonitorUtilities.h" #include "LogFileMonitor.h" #include "ProcessMonitor.h" diff --git a/sdl-compliance-pipeline.yml b/sdl-compliance-pipeline.yml new file mode 100644 index 00000000..b71dd3d8 --- /dev/null +++ b/sdl-compliance-pipeline.yml @@ -0,0 +1,49 @@ +trigger: + batch: 'true' + branches: + include: + - main + paths: + exclude: + - '*README.md' + - 'docs/*' + +pool: + vmImage: 'windows-2022' + +variables: + LGTM.UploadSnapshot: true + solution: '**/*.sln' + msbuildPath: '"%ProgramFiles(x86)%\Microsoft Visual Studio\2022\Enterprise\MSBuild\Current\Bin\MSBuild.exe"' + BuildPlatform: x64 + BuildConfiguration: Release + +jobs: + - job: SDLCompliance + displayName: 'Running SDL Compliance Policy checks' + continueOnError: true + steps: + - task: Semmle@1 + displayName: 'Run CodeQL (Semmle)' + env: + SYSTEM_ACCESSTOKEN: $(System.AccessToken) + inputs: + sourceCodeDirectory: '$(Build.SourcesDirectory)\LogMonitor' + language: 'cpp' + buildCommands: '$(msbuildPath) $(BUILD.SourcesDirectory)\$(solution) /t:clean # + $(msbuildPath) $(BUILD.SourcesDirectory)\$(solution) /p:platform=$(BuildPlatform) /p:configuration=$(BuildConfiguration)' + querySuite: 'Recommended' + timeout: '1800' + ram: '8192' + addProjectDirToScanningExclusionList: true + + - task: PublishSecurityAnalysisLogs@3 + displayName: 'Publish Security Analysis Logs' + continueOnError: true + inputs: + ArtifactName: 'CodeAnalysisLogs' + ArtifactType: 'Container' + PublishProcessedResults: false + AllTools: false + Semmle: true + ToolLogsNotFoundAction: 'Standard'