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