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