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