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