| #pragma once | 
 |  | 
 | #include "logging.hpp" | 
 |  | 
 | #include <zlib.h> | 
 |  | 
 | #include <array> | 
 | #include <filesystem> | 
 | #include <string> | 
 | #include <vector> | 
 |  | 
 | class GzFileReader | 
 | { | 
 |   public: | 
 |     bool gzGetLines(const std::string& filename, uint64_t skip, uint64_t top, | 
 |                     std::vector<std::string>& logEntries, size_t& logCount) | 
 |     { | 
 |         gzFile logStream = gzopen(filename.c_str(), "r"); | 
 |         if (logStream == nullptr) | 
 |         { | 
 |             BMCWEB_LOG_ERROR("Can't open gz file: {}", filename); | 
 |             return false; | 
 |         } | 
 |  | 
 |         if (!readFile(logStream, skip, top, logEntries, logCount)) | 
 |         { | 
 |             gzclose(logStream); | 
 |             return false; | 
 |         } | 
 |         gzclose(logStream); | 
 |         return true; | 
 |     } | 
 |  | 
 |     std::string getLastMessage() | 
 |     { | 
 |         return lastMessage; | 
 |     } | 
 |  | 
 |   private: | 
 |     std::string lastMessage; | 
 |     std::string lastDelimiter; | 
 |     size_t totalFilesSize = 0; | 
 |  | 
 |     static void printErrorMessage(gzFile logStream) | 
 |     { | 
 |         int errNum = 0; | 
 |         const char* errMsg = gzerror(logStream, &errNum); | 
 |  | 
 |         BMCWEB_LOG_ERROR( | 
 |             "Error reading gz compressed data.\nError Message: {}\nError Number: {}", | 
 |             errMsg, errNum); | 
 |     } | 
 |  | 
 |     bool readFile(gzFile logStream, uint64_t skip, uint64_t top, | 
 |                   std::vector<std::string>& logEntries, size_t& logCount) | 
 |     { | 
 |         constexpr int bufferLimitSize = 1024; | 
 |         do | 
 |         { | 
 |             std::string bufferStr; | 
 |             bufferStr.resize(bufferLimitSize); | 
 |  | 
 |             int bytesRead = gzread(logStream, bufferStr.data(), | 
 |                                    static_cast<unsigned int>(bufferStr.size())); | 
 |             // On errors, gzread() shall return a value less than 0. | 
 |             if (bytesRead < 0) | 
 |             { | 
 |                 printErrorMessage(logStream); | 
 |                 return false; | 
 |             } | 
 |             bufferStr.resize(static_cast<size_t>(bytesRead)); | 
 |             if (!hostLogEntryParser(bufferStr, skip, top, logEntries, logCount)) | 
 |             { | 
 |                 BMCWEB_LOG_ERROR("Error occurs during parsing host log."); | 
 |                 return false; | 
 |             } | 
 |         } while (gzeof(logStream) != 1); | 
 |  | 
 |         return true; | 
 |     } | 
 |  | 
 |     bool hostLogEntryParser(const std::string& bufferStr, uint64_t skip, | 
 |                             uint64_t top, std::vector<std::string>& logEntries, | 
 |                             size_t& logCount) | 
 |     { | 
 |         // Assume we have 8 files, and the max size of each file is | 
 |         // 16k, so define the max size as 256kb (double of 8 files * | 
 |         // 16kb) | 
 |         constexpr size_t maxTotalFilesSize = 262144; | 
 |  | 
 |         // It may contain several log entry in one line, and | 
 |         // the end of each log entry will be '\r\n' or '\r'. | 
 |         // So we need to go through and split string by '\n' and '\r' | 
 |         size_t pos = bufferStr.find_first_of("\n\r"); | 
 |         size_t initialPos = 0; | 
 |         std::string newLastMessage; | 
 |  | 
 |         while (pos != std::string::npos) | 
 |         { | 
 |             std::string logEntry = | 
 |                 bufferStr.substr(initialPos, pos - initialPos); | 
 |             // Since there might be consecutive delimiters like "\r\n", we need | 
 |             // to filter empty strings. | 
 |             if (!logEntry.empty()) | 
 |             { | 
 |                 logCount++; | 
 |                 if (!lastMessage.empty()) | 
 |                 { | 
 |                     logEntry.insert(0, lastMessage); | 
 |                     lastMessage.clear(); | 
 |                 } | 
 |                 if (logCount > skip && logCount <= (skip + top)) | 
 |                 { | 
 |                     totalFilesSize += logEntry.size(); | 
 |                     if (totalFilesSize > maxTotalFilesSize) | 
 |                     { | 
 |                         BMCWEB_LOG_ERROR( | 
 |                             "File size exceeds maximum allowed size of {}", | 
 |                             maxTotalFilesSize); | 
 |                         return false; | 
 |                     } | 
 |                     logEntries.push_back(logEntry); | 
 |                 } | 
 |             } | 
 |             else | 
 |             { | 
 |                 // Handle consecutive delimiter. '\r\n' act as a single | 
 |                 // delimiter, the other case like '\n\n', '\n\r' or '\r\r' will | 
 |                 // push back a "\n" as a log. | 
 |                 std::string delimiters; | 
 |                 if (pos > 0) | 
 |                 { | 
 |                     delimiters = bufferStr.substr(pos - 1, 2); | 
 |                 } | 
 |                 // Handle consecutive delimiter but spilt between two files. | 
 |                 if (pos == 0 && !(lastDelimiter.empty())) | 
 |                 { | 
 |                     delimiters = lastDelimiter + bufferStr.substr(0, 1); | 
 |                 } | 
 |                 if (delimiters != "\r\n") | 
 |                 { | 
 |                     logCount++; | 
 |                     if (logCount > skip && logCount <= (skip + top)) | 
 |                     { | 
 |                         totalFilesSize++; | 
 |                         if (totalFilesSize > maxTotalFilesSize) | 
 |                         { | 
 |                             BMCWEB_LOG_ERROR( | 
 |                                 "File size exceeds maximum allowed size of {}", | 
 |                                 maxTotalFilesSize); | 
 |                             return false; | 
 |                         } | 
 |                         logEntries.emplace_back("\n"); | 
 |                     } | 
 |                 } | 
 |             } | 
 |             initialPos = pos + 1; | 
 |             pos = bufferStr.find_first_of("\n\r", initialPos); | 
 |         } | 
 |  | 
 |         // Store the last message | 
 |         if (initialPos < bufferStr.size()) | 
 |         { | 
 |             newLastMessage = bufferStr.substr(initialPos); | 
 |         } | 
 |         // If consecutive delimiter spilt by buffer or file, the last character | 
 |         // must be the delimiter. | 
 |         else if (initialPos == bufferStr.size()) | 
 |         { | 
 |             lastDelimiter = std::string(1, bufferStr.back()); | 
 |         } | 
 |         // If file doesn't contain any "\r" or "\n", initialPos should be zero | 
 |         if (initialPos == 0) | 
 |         { | 
 |             // Solved an edge case that the log doesn't in skip and top range, | 
 |             // but consecutive files don't contain a single delimiter, this | 
 |             // lastMessage becomes unnecessarily large. Since last message will | 
 |             // prepend to next log, logCount need to plus 1 | 
 |             if ((logCount + 1) > skip && (logCount + 1) <= (skip + top)) | 
 |             { | 
 |                 lastMessage.insert( | 
 |                     lastMessage.end(), | 
 |                     std::make_move_iterator(newLastMessage.begin()), | 
 |                     std::make_move_iterator(newLastMessage.end())); | 
 |  | 
 |                 // Following the previous question, protect lastMessage don't | 
 |                 // larger than max total files size | 
 |                 size_t tmpMessageSize = totalFilesSize + lastMessage.size(); | 
 |                 if (tmpMessageSize > maxTotalFilesSize) | 
 |                 { | 
 |                     BMCWEB_LOG_ERROR( | 
 |                         "File size exceeds maximum allowed size of {}", | 
 |                         maxTotalFilesSize); | 
 |                     return false; | 
 |                 } | 
 |             } | 
 |         } | 
 |         else | 
 |         { | 
 |             if (!newLastMessage.empty()) | 
 |             { | 
 |                 lastMessage = std::move(newLastMessage); | 
 |             } | 
 |         } | 
 |         return true; | 
 |     } | 
 |  | 
 |   public: | 
 |     GzFileReader() = default; | 
 |     ~GzFileReader() = default; | 
 |     GzFileReader(const GzFileReader&) = delete; | 
 |     GzFileReader& operator=(const GzFileReader&) = delete; | 
 |     GzFileReader(GzFileReader&&) = delete; | 
 |     GzFileReader& operator=(GzFileReader&&) = delete; | 
 | }; |