xref: /openbmc/bmcweb/redfish-core/include/gzfile.hpp (revision d78572018fc2022091ff8b8eb5a7fef2172ba3d6)
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