blob: 1ca54d22a02e361d5a5f66a1c063d2d2feff875b [file] [log] [blame]
Ed Tanous40e9b922024-09-10 13:50:16 -07001// SPDX-License-Identifier: Apache-2.0
2// SPDX-FileCopyrightText: Copyright OpenBMC Authors
Spencer Kub7028eb2021-10-26 15:27:35 +08003#pragma once
4
Ed Tanous3ccb3ad2023-01-13 17:40:03 -08005#include "logging.hpp"
6
Spencer Kub7028eb2021-10-26 15:27:35 +08007#include <zlib.h>
8
Ed Tanousd7857202025-01-28 15:32:26 -08009#include <cstddef>
10#include <cstdint>
11#include <iterator>
Ed Tanous3ccb3ad2023-01-13 17:40:03 -080012#include <string>
Ed Tanousd7857202025-01-28 15:32:26 -080013#include <utility>
Spencer Kub7028eb2021-10-26 15:27:35 +080014#include <vector>
15
16class GzFileReader
17{
18 public:
Nan Zhou9739de92022-04-06 11:07:27 -070019 bool gzGetLines(const std::string& filename, uint64_t skip, uint64_t top,
Spencer Kub7028eb2021-10-26 15:27:35 +080020 std::vector<std::string>& logEntries, size_t& logCount)
21 {
22 gzFile logStream = gzopen(filename.c_str(), "r");
Ed Tanouse662eae2022-01-25 10:39:19 -080023 if (logStream == nullptr)
Spencer Kub7028eb2021-10-26 15:27:35 +080024 {
Ed Tanous62598e32023-07-17 17:06:25 -070025 BMCWEB_LOG_ERROR("Can't open gz file: {}", filename);
Spencer Kub7028eb2021-10-26 15:27:35 +080026 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
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
Ed Tanous56d23962022-02-14 20:42:02 -080048 static void printErrorMessage(gzFile logStream)
Spencer Kub7028eb2021-10-26 15:27:35 +080049 {
50 int errNum = 0;
51 const char* errMsg = gzerror(logStream, &errNum);
52
Ed Tanous62598e32023-07-17 17:06:25 -070053 BMCWEB_LOG_ERROR(
54 "Error reading gz compressed data.\nError Message: {}\nError Number: {}",
55 errMsg, errNum);
Spencer Kub7028eb2021-10-26 15:27:35 +080056 }
57
Nan Zhou9739de92022-04-06 11:07:27 -070058 bool readFile(gzFile logStream, uint64_t skip, uint64_t top,
Spencer Kub7028eb2021-10-26 15:27:35 +080059 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 {
Ed Tanous62598e32023-07-17 17:06:25 -070078 BMCWEB_LOG_ERROR("Error occurs during parsing host log.");
Spencer Kub7028eb2021-10-26 15:27:35 +080079 return false;
80 }
Ed Tanouse662eae2022-01-25 10:39:19 -080081 } while (gzeof(logStream) != 1);
Spencer Kub7028eb2021-10-26 15:27:35 +080082
83 return true;
84 }
85
Nan Zhou9739de92022-04-06 11:07:27 -070086 bool hostLogEntryParser(const std::string& bufferStr, uint64_t skip,
87 uint64_t top, std::vector<std::string>& logEntries,
Spencer Kub7028eb2021-10-26 15:27:35 +080088 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 {
Patrick Williamsbd79bce2024-08-16 15:22:20 -0400104 std::string logEntry =
105 bufferStr.substr(initialPos, pos - initialPos);
Spencer Kub7028eb2021-10-26 15:27:35 +0800106 // 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 {
Ed Tanous62598e32023-07-17 17:06:25 -0700121 BMCWEB_LOG_ERROR(
122 "File size exceeds maximum allowed size of {}",
123 maxTotalFilesSize);
Spencer Kub7028eb2021-10-26 15:27:35 +0800124 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 {
Ed Tanous62598e32023-07-17 17:06:25 -0700152 BMCWEB_LOG_ERROR(
153 "File size exceeds maximum allowed size of {}",
154 maxTotalFilesSize);
Spencer Kub7028eb2021-10-26 15:27:35 +0800155 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 {
Ed Tanous62598e32023-07-17 17:06:25 -0700195 BMCWEB_LOG_ERROR(
196 "File size exceeds maximum allowed size of {}",
197 maxTotalFilesSize);
Spencer Kub7028eb2021-10-26 15:27:35 +0800198 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;
Ed Tanousecd6a3a2022-01-07 09:18:40 -0800217 GzFileReader(GzFileReader&&) = delete;
218 GzFileReader& operator=(GzFileReader&&) = delete;
Spencer Kub7028eb2021-10-26 15:27:35 +0800219};