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