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