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