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