blob: 7b1923c8b9a3689f23014374d58ee3315d72355f [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
9#include <array>
10#include <filesystem>
Ed Tanous3ccb3ad2023-01-13 17:40:03 -080011#include <string>
Spencer Kub7028eb2021-10-26 15:27:35 +080012#include <vector>
13
14class GzFileReader
15{
16 public:
Nan Zhou9739de92022-04-06 11:07:27 -070017 bool gzGetLines(const std::string& filename, uint64_t skip, uint64_t top,
Spencer Kub7028eb2021-10-26 15:27:35 +080018 std::vector<std::string>& logEntries, size_t& logCount)
19 {
20 gzFile logStream = gzopen(filename.c_str(), "r");
Ed Tanouse662eae2022-01-25 10:39:19 -080021 if (logStream == nullptr)
Spencer Kub7028eb2021-10-26 15:27:35 +080022 {
Ed Tanous62598e32023-07-17 17:06:25 -070023 BMCWEB_LOG_ERROR("Can't open gz file: {}", filename);
Spencer Kub7028eb2021-10-26 15:27:35 +080024 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
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
Ed Tanous56d23962022-02-14 20:42:02 -080046 static void printErrorMessage(gzFile logStream)
Spencer Kub7028eb2021-10-26 15:27:35 +080047 {
48 int errNum = 0;
49 const char* errMsg = gzerror(logStream, &errNum);
50
Ed Tanous62598e32023-07-17 17:06:25 -070051 BMCWEB_LOG_ERROR(
52 "Error reading gz compressed data.\nError Message: {}\nError Number: {}",
53 errMsg, errNum);
Spencer Kub7028eb2021-10-26 15:27:35 +080054 }
55
Nan Zhou9739de92022-04-06 11:07:27 -070056 bool readFile(gzFile logStream, uint64_t skip, uint64_t top,
Spencer Kub7028eb2021-10-26 15:27:35 +080057 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 {
Ed Tanous62598e32023-07-17 17:06:25 -070076 BMCWEB_LOG_ERROR("Error occurs during parsing host log.");
Spencer Kub7028eb2021-10-26 15:27:35 +080077 return false;
78 }
Ed Tanouse662eae2022-01-25 10:39:19 -080079 } while (gzeof(logStream) != 1);
Spencer Kub7028eb2021-10-26 15:27:35 +080080
81 return true;
82 }
83
Nan Zhou9739de92022-04-06 11:07:27 -070084 bool hostLogEntryParser(const std::string& bufferStr, uint64_t skip,
85 uint64_t top, std::vector<std::string>& logEntries,
Spencer Kub7028eb2021-10-26 15:27:35 +080086 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 {
Patrick Williamsbd79bce2024-08-16 15:22:20 -0400102 std::string logEntry =
103 bufferStr.substr(initialPos, pos - initialPos);
Spencer Kub7028eb2021-10-26 15:27:35 +0800104 // 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 {
Ed Tanous62598e32023-07-17 17:06:25 -0700119 BMCWEB_LOG_ERROR(
120 "File size exceeds maximum allowed size of {}",
121 maxTotalFilesSize);
Spencer Kub7028eb2021-10-26 15:27:35 +0800122 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 {
Ed Tanous62598e32023-07-17 17:06:25 -0700150 BMCWEB_LOG_ERROR(
151 "File size exceeds maximum allowed size of {}",
152 maxTotalFilesSize);
Spencer Kub7028eb2021-10-26 15:27:35 +0800153 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 {
Ed Tanous62598e32023-07-17 17:06:25 -0700193 BMCWEB_LOG_ERROR(
194 "File size exceeds maximum allowed size of {}",
195 maxTotalFilesSize);
Spencer Kub7028eb2021-10-26 15:27:35 +0800196 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;
Ed Tanousecd6a3a2022-01-07 09:18:40 -0800215 GzFileReader(GzFileReader&&) = delete;
216 GzFileReader& operator=(GzFileReader&&) = delete;
Spencer Kub7028eb2021-10-26 15:27:35 +0800217};