1b7028ebfSSpencer Ku #pragma once 2b7028ebfSSpencer Ku 3*3ccb3adbSEd Tanous #include "logging.hpp" 4*3ccb3adbSEd Tanous 5b7028ebfSSpencer Ku #include <zlib.h> 6b7028ebfSSpencer Ku 7b7028ebfSSpencer Ku #include <array> 8b7028ebfSSpencer Ku #include <filesystem> 9*3ccb3adbSEd Tanous #include <string> 10b7028ebfSSpencer Ku #include <vector> 11b7028ebfSSpencer Ku 12b7028ebfSSpencer Ku class GzFileReader 13b7028ebfSSpencer Ku { 14b7028ebfSSpencer Ku public: 159739de9aSNan Zhou bool gzGetLines(const std::string& filename, uint64_t skip, uint64_t top, 16b7028ebfSSpencer Ku std::vector<std::string>& logEntries, size_t& logCount) 17b7028ebfSSpencer Ku { 18b7028ebfSSpencer Ku gzFile logStream = gzopen(filename.c_str(), "r"); 19e662eae8SEd Tanous if (logStream == nullptr) 20b7028ebfSSpencer Ku { 21b7028ebfSSpencer Ku BMCWEB_LOG_ERROR << "Can't open gz file: " << filename << '\n'; 22b7028ebfSSpencer Ku return false; 23b7028ebfSSpencer Ku } 24b7028ebfSSpencer Ku 25b7028ebfSSpencer Ku if (!readFile(logStream, skip, top, logEntries, logCount)) 26b7028ebfSSpencer Ku { 27b7028ebfSSpencer Ku gzclose(logStream); 28b7028ebfSSpencer Ku return false; 29b7028ebfSSpencer Ku } 30b7028ebfSSpencer Ku gzclose(logStream); 31b7028ebfSSpencer Ku return true; 32b7028ebfSSpencer Ku } 33b7028ebfSSpencer Ku 34b7028ebfSSpencer Ku std::string getLastMessage() 35b7028ebfSSpencer Ku { 36b7028ebfSSpencer Ku return lastMessage; 37b7028ebfSSpencer Ku } 38b7028ebfSSpencer Ku 39b7028ebfSSpencer Ku private: 40b7028ebfSSpencer Ku std::string lastMessage; 41b7028ebfSSpencer Ku std::string lastDelimiter; 42b7028ebfSSpencer Ku size_t totalFilesSize = 0; 43b7028ebfSSpencer Ku 4456d2396dSEd Tanous static void printErrorMessage(gzFile logStream) 45b7028ebfSSpencer Ku { 46b7028ebfSSpencer Ku int errNum = 0; 47b7028ebfSSpencer Ku const char* errMsg = gzerror(logStream, &errNum); 48b7028ebfSSpencer Ku 49b7028ebfSSpencer Ku BMCWEB_LOG_ERROR << "Error reading gz compressed data.\n" 50b7028ebfSSpencer Ku << "Error Message: " << errMsg << '\n' 51b7028ebfSSpencer Ku << "Error Number: " << errNum; 52b7028ebfSSpencer Ku } 53b7028ebfSSpencer Ku 549739de9aSNan Zhou bool readFile(gzFile logStream, uint64_t skip, uint64_t top, 55b7028ebfSSpencer Ku std::vector<std::string>& logEntries, size_t& logCount) 56b7028ebfSSpencer Ku { 57b7028ebfSSpencer Ku constexpr int bufferLimitSize = 1024; 58b7028ebfSSpencer Ku do 59b7028ebfSSpencer Ku { 60b7028ebfSSpencer Ku std::string bufferStr; 61b7028ebfSSpencer Ku bufferStr.resize(bufferLimitSize); 62b7028ebfSSpencer Ku 63b7028ebfSSpencer Ku int bytesRead = gzread(logStream, bufferStr.data(), 64b7028ebfSSpencer Ku static_cast<unsigned int>(bufferStr.size())); 65b7028ebfSSpencer Ku // On errors, gzread() shall return a value less than 0. 66b7028ebfSSpencer Ku if (bytesRead < 0) 67b7028ebfSSpencer Ku { 68b7028ebfSSpencer Ku printErrorMessage(logStream); 69b7028ebfSSpencer Ku return false; 70b7028ebfSSpencer Ku } 71b7028ebfSSpencer Ku bufferStr.resize(static_cast<size_t>(bytesRead)); 72b7028ebfSSpencer Ku if (!hostLogEntryParser(bufferStr, skip, top, logEntries, logCount)) 73b7028ebfSSpencer Ku { 74b7028ebfSSpencer Ku BMCWEB_LOG_ERROR << "Error occurs during parsing host log.\n"; 75b7028ebfSSpencer Ku return false; 76b7028ebfSSpencer Ku } 77e662eae8SEd Tanous } while (gzeof(logStream) != 1); 78b7028ebfSSpencer Ku 79b7028ebfSSpencer Ku return true; 80b7028ebfSSpencer Ku } 81b7028ebfSSpencer Ku 829739de9aSNan Zhou bool hostLogEntryParser(const std::string& bufferStr, uint64_t skip, 839739de9aSNan Zhou uint64_t top, std::vector<std::string>& logEntries, 84b7028ebfSSpencer Ku size_t& logCount) 85b7028ebfSSpencer Ku { 86b7028ebfSSpencer Ku // Assume we have 8 files, and the max size of each file is 87b7028ebfSSpencer Ku // 16k, so define the max size as 256kb (double of 8 files * 88b7028ebfSSpencer Ku // 16kb) 89b7028ebfSSpencer Ku constexpr size_t maxTotalFilesSize = 262144; 90b7028ebfSSpencer Ku 91b7028ebfSSpencer Ku // It may contain several log entry in one line, and 92b7028ebfSSpencer Ku // the end of each log entry will be '\r\n' or '\r'. 93b7028ebfSSpencer Ku // So we need to go through and split string by '\n' and '\r' 94b7028ebfSSpencer Ku size_t pos = bufferStr.find_first_of("\n\r"); 95b7028ebfSSpencer Ku size_t initialPos = 0; 96b7028ebfSSpencer Ku std::string newLastMessage; 97b7028ebfSSpencer Ku 98b7028ebfSSpencer Ku while (pos != std::string::npos) 99b7028ebfSSpencer Ku { 100b7028ebfSSpencer Ku std::string logEntry = 101b7028ebfSSpencer Ku bufferStr.substr(initialPos, pos - initialPos); 102b7028ebfSSpencer Ku // Since there might be consecutive delimiters like "\r\n", we need 103b7028ebfSSpencer Ku // to filter empty strings. 104b7028ebfSSpencer Ku if (!logEntry.empty()) 105b7028ebfSSpencer Ku { 106b7028ebfSSpencer Ku logCount++; 107b7028ebfSSpencer Ku if (!lastMessage.empty()) 108b7028ebfSSpencer Ku { 109b7028ebfSSpencer Ku logEntry.insert(0, lastMessage); 110b7028ebfSSpencer Ku lastMessage.clear(); 111b7028ebfSSpencer Ku } 112b7028ebfSSpencer Ku if (logCount > skip && logCount <= (skip + top)) 113b7028ebfSSpencer Ku { 114b7028ebfSSpencer Ku totalFilesSize += logEntry.size(); 115b7028ebfSSpencer Ku if (totalFilesSize > maxTotalFilesSize) 116b7028ebfSSpencer Ku { 117b7028ebfSSpencer Ku BMCWEB_LOG_ERROR 118b7028ebfSSpencer Ku << "File size exceeds maximum allowed size of " 119b7028ebfSSpencer Ku << maxTotalFilesSize; 120b7028ebfSSpencer Ku return false; 121b7028ebfSSpencer Ku } 122b7028ebfSSpencer Ku logEntries.push_back(logEntry); 123b7028ebfSSpencer Ku } 124b7028ebfSSpencer Ku } 125b7028ebfSSpencer Ku else 126b7028ebfSSpencer Ku { 127b7028ebfSSpencer Ku // Handle consecutive delimiter. '\r\n' act as a single 128b7028ebfSSpencer Ku // delimiter, the other case like '\n\n', '\n\r' or '\r\r' will 129b7028ebfSSpencer Ku // push back a "\n" as a log. 130b7028ebfSSpencer Ku std::string delimiters; 131b7028ebfSSpencer Ku if (pos > 0) 132b7028ebfSSpencer Ku { 133b7028ebfSSpencer Ku delimiters = bufferStr.substr(pos - 1, 2); 134b7028ebfSSpencer Ku } 135b7028ebfSSpencer Ku // Handle consecutive delimiter but spilt between two files. 136b7028ebfSSpencer Ku if (pos == 0 && !(lastDelimiter.empty())) 137b7028ebfSSpencer Ku { 138b7028ebfSSpencer Ku delimiters = lastDelimiter + bufferStr.substr(0, 1); 139b7028ebfSSpencer Ku } 140b7028ebfSSpencer Ku if (delimiters != "\r\n") 141b7028ebfSSpencer Ku { 142b7028ebfSSpencer Ku logCount++; 143b7028ebfSSpencer Ku if (logCount > skip && logCount <= (skip + top)) 144b7028ebfSSpencer Ku { 145b7028ebfSSpencer Ku totalFilesSize++; 146b7028ebfSSpencer Ku if (totalFilesSize > maxTotalFilesSize) 147b7028ebfSSpencer Ku { 148b7028ebfSSpencer Ku BMCWEB_LOG_ERROR 149b7028ebfSSpencer Ku << "File size exceeds maximum allowed size of " 150b7028ebfSSpencer Ku << maxTotalFilesSize; 151b7028ebfSSpencer Ku return false; 152b7028ebfSSpencer Ku } 153b7028ebfSSpencer Ku logEntries.emplace_back("\n"); 154b7028ebfSSpencer Ku } 155b7028ebfSSpencer Ku } 156b7028ebfSSpencer Ku } 157b7028ebfSSpencer Ku initialPos = pos + 1; 158b7028ebfSSpencer Ku pos = bufferStr.find_first_of("\n\r", initialPos); 159b7028ebfSSpencer Ku } 160b7028ebfSSpencer Ku 161b7028ebfSSpencer Ku // Store the last message 162b7028ebfSSpencer Ku if (initialPos < bufferStr.size()) 163b7028ebfSSpencer Ku { 164b7028ebfSSpencer Ku newLastMessage = bufferStr.substr(initialPos); 165b7028ebfSSpencer Ku } 166b7028ebfSSpencer Ku // If consecutive delimiter spilt by buffer or file, the last character 167b7028ebfSSpencer Ku // must be the delimiter. 168b7028ebfSSpencer Ku else if (initialPos == bufferStr.size()) 169b7028ebfSSpencer Ku { 170b7028ebfSSpencer Ku lastDelimiter = std::string(1, bufferStr.back()); 171b7028ebfSSpencer Ku } 172b7028ebfSSpencer Ku // If file doesn't contain any "\r" or "\n", initialPos should be zero 173b7028ebfSSpencer Ku if (initialPos == 0) 174b7028ebfSSpencer Ku { 175b7028ebfSSpencer Ku // Solved an edge case that the log doesn't in skip and top range, 176b7028ebfSSpencer Ku // but consecutive files don't contain a single delimiter, this 177b7028ebfSSpencer Ku // lastMessage becomes unnecessarily large. Since last message will 178b7028ebfSSpencer Ku // prepend to next log, logCount need to plus 1 179b7028ebfSSpencer Ku if ((logCount + 1) > skip && (logCount + 1) <= (skip + top)) 180b7028ebfSSpencer Ku { 181b7028ebfSSpencer Ku lastMessage.insert( 182b7028ebfSSpencer Ku lastMessage.end(), 183b7028ebfSSpencer Ku std::make_move_iterator(newLastMessage.begin()), 184b7028ebfSSpencer Ku std::make_move_iterator(newLastMessage.end())); 185b7028ebfSSpencer Ku 186b7028ebfSSpencer Ku // Following the previous question, protect lastMessage don't 187b7028ebfSSpencer Ku // larger than max total files size 188b7028ebfSSpencer Ku size_t tmpMessageSize = totalFilesSize + lastMessage.size(); 189b7028ebfSSpencer Ku if (tmpMessageSize > maxTotalFilesSize) 190b7028ebfSSpencer Ku { 191b7028ebfSSpencer Ku BMCWEB_LOG_ERROR 192b7028ebfSSpencer Ku << "File size exceeds maximum allowed size of " 193b7028ebfSSpencer Ku << maxTotalFilesSize; 194b7028ebfSSpencer Ku return false; 195b7028ebfSSpencer Ku } 196b7028ebfSSpencer Ku } 197b7028ebfSSpencer Ku } 198b7028ebfSSpencer Ku else 199b7028ebfSSpencer Ku { 200b7028ebfSSpencer Ku if (!newLastMessage.empty()) 201b7028ebfSSpencer Ku { 202b7028ebfSSpencer Ku lastMessage = std::move(newLastMessage); 203b7028ebfSSpencer Ku } 204b7028ebfSSpencer Ku } 205b7028ebfSSpencer Ku return true; 206b7028ebfSSpencer Ku } 207b7028ebfSSpencer Ku 208b7028ebfSSpencer Ku public: 209b7028ebfSSpencer Ku GzFileReader() = default; 210b7028ebfSSpencer Ku ~GzFileReader() = default; 211b7028ebfSSpencer Ku GzFileReader(const GzFileReader&) = delete; 212b7028ebfSSpencer Ku GzFileReader& operator=(const GzFileReader&) = delete; 213ecd6a3a2SEd Tanous GzFileReader(GzFileReader&&) = delete; 214ecd6a3a2SEd Tanous GzFileReader& operator=(GzFileReader&&) = delete; 215b7028ebfSSpencer Ku }; 216