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