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