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