1 #include "config.h"
2 
3 #include "dump_manager_bmc.hpp"
4 
5 #include "bmc_dump_entry.hpp"
6 #include "dump_types.hpp"
7 #include "xyz/openbmc_project/Common/error.hpp"
8 #include "xyz/openbmc_project/Dump/Create/error.hpp"
9 
10 #include <sys/inotify.h>
11 #include <unistd.h>
12 
13 #include <phosphor-logging/elog-errors.hpp>
14 #include <phosphor-logging/elog.hpp>
15 #include <phosphor-logging/lg2.hpp>
16 #include <sdeventplus/exception.hpp>
17 #include <sdeventplus/source/base.hpp>
18 
19 #include <cmath>
20 #include <ctime>
21 #include <regex>
22 
23 namespace phosphor
24 {
25 namespace dump
26 {
27 namespace bmc
28 {
29 
30 using namespace sdbusplus::xyz::openbmc_project::Common::Error;
31 using namespace phosphor::logging;
32 
33 bool Manager::fUserDumpInProgress = false;
34 constexpr auto BMC_DUMP = "BMC_DUMP";
35 
36 sdbusplus::message::object_path
37     Manager::createDump(phosphor::dump::DumpCreateParams params)
38 {
39     if (params.size() > CREATE_DUMP_MAX_PARAMS)
40     {
41         lg2::warning("BMC dump accepts not more than 2 additional parameters");
42     }
43 
44     // Get the originator id and type from params
45     std::string originatorId;
46     originatorTypes originatorType;
47 
48     phosphor::dump::extractOriginatorProperties(params, originatorId,
49                                                 originatorType);
50     using CreateParameters =
51         sdbusplus::common::xyz::openbmc_project::dump::Create::CreateParameters;
52 
53     DumpTypes dumpType = DumpTypes::USER;
54     std::string type = extractParameter<std::string>(
55         convertCreateParametersToString(CreateParameters::DumpType), params);
56     if (!type.empty())
57     {
58         dumpType = validateDumpType(type, BMC_DUMP);
59     }
60 
61     if (dumpType == DumpTypes::ELOG)
62     {
63         dumpType = getErrorDumpType(params);
64     }
65     std::string path = extractParameter<std::string>(
66         convertCreateParametersToString(CreateParameters::FilePath), params);
67 
68     if ((Manager::fUserDumpInProgress == true) && (dumpType == DumpTypes::USER))
69     {
70         lg2::info("Another user initiated dump in progress");
71         elog<sdbusplus::xyz::openbmc_project::Common::Error::Unavailable>();
72     }
73 
74     lg2::info("Initiating new BMC dump with type: {TYPE} path: {PATH}", "TYPE",
75               dumpTypeToString(dumpType).value(), "PATH", path);
76 
77     auto id = captureDump(dumpType, path);
78 
79     // Entry Object path.
80     auto objPath = std::filesystem::path(baseEntryPath) / std::to_string(id);
81 
82     try
83     {
84         uint64_t timeStamp =
85             std::chrono::duration_cast<std::chrono::microseconds>(
86                 std::chrono::system_clock::now().time_since_epoch())
87                 .count();
88 
89         entries.insert(std::make_pair(
90             id, std::make_unique<bmc::Entry>(
91                     bus, objPath.c_str(), id, timeStamp, 0, std::string(),
92                     phosphor::dump::OperationStatus::InProgress, originatorId,
93                     originatorType, *this)));
94     }
95     catch (const std::invalid_argument& e)
96     {
97         lg2::error("Error in creating dump entry, errormsg: {ERROR}, "
98                    "OBJECTPATH: {OBJECT_PATH}, ID: {ID}",
99                    "ERROR", e, "OBJECT_PATH", objPath, "ID", id);
100         elog<InternalFailure>();
101     }
102 
103     if (dumpType == DumpTypes::USER)
104     {
105         Manager::fUserDumpInProgress = true;
106     }
107     return objPath.string();
108 }
109 
110 uint32_t Manager::captureDump(DumpTypes type, const std::string& path)
111 {
112     // Get Dump size.
113     auto size = getAllowedSize();
114 
115     pid_t pid = fork();
116 
117     if (pid == 0)
118     {
119         std::filesystem::path dumpPath(dumpDir);
120         auto id = std::to_string(lastEntryId + 1);
121         dumpPath /= id;
122 
123         auto strType = dumpTypeToString(type).value();
124         execl("/usr/bin/dreport", "dreport", "-d", dumpPath.c_str(), "-i",
125               id.c_str(), "-s", std::to_string(size).c_str(), "-q", "-v", "-p",
126               path.empty() ? "" : path.c_str(), "-t", strType.c_str(), nullptr);
127 
128         // dreport script execution is failed.
129         auto error = errno;
130         lg2::error("Error occurred during dreport function execution, "
131                    "errno: {ERRNO}",
132                    "ERRNO", error);
133         elog<InternalFailure>();
134     }
135     else if (pid > 0)
136     {
137         Child::Callback callback = [this, type, pid](Child&, const siginfo_t*) {
138             if (type == DumpTypes::USER)
139             {
140                 lg2::info("User initiated dump completed, resetting flag");
141                 Manager::fUserDumpInProgress = false;
142             }
143             this->childPtrMap.erase(pid);
144         };
145         try
146         {
147             childPtrMap.emplace(pid,
148                                 std::make_unique<Child>(eventLoop.get(), pid,
149                                                         WEXITED | WSTOPPED,
150                                                         std::move(callback)));
151         }
152         catch (const sdeventplus::SdEventError& ex)
153         {
154             // Failed to add to event loop
155             lg2::error(
156                 "Error occurred during the sdeventplus::source::Child creation "
157                 "ex: {ERROR}",
158                 "ERROR", ex);
159             elog<InternalFailure>();
160         }
161     }
162     else
163     {
164         auto error = errno;
165         lg2::error("Error occurred during fork, errno: {ERRNO}", "ERRNO",
166                    error);
167         elog<InternalFailure>();
168     }
169     return ++lastEntryId;
170 }
171 
172 void Manager::createEntry(const std::filesystem::path& file)
173 {
174     // Dump File Name format obmcdump_ID_EPOCHTIME.EXT
175     static constexpr auto ID_POS = 1;
176     static constexpr auto EPOCHTIME_POS = 2;
177     std::regex file_regex("obmcdump_([0-9]+)_([0-9]+).([a-zA-Z0-9]+)");
178 
179     std::smatch match;
180     std::string name = file.filename();
181 
182     if (!((std::regex_search(name, match, file_regex)) && (match.size() > 0)))
183     {
184         lg2::error("Invalid Dump file name, FILENAME: {FILENAME}", "FILENAME",
185                    file);
186         return;
187     }
188 
189     auto idString = match[ID_POS];
190     uint64_t timestamp = stoull(match[EPOCHTIME_POS]) * 1000 * 1000;
191 
192     auto id = stoul(idString);
193 
194     // If there is an existing entry update it and return.
195     auto dumpEntry = entries.find(id);
196     if (dumpEntry != entries.end())
197     {
198         dynamic_cast<phosphor::dump::bmc::Entry*>(dumpEntry->second.get())
199             ->update(timestamp, std::filesystem::file_size(file), file);
200         return;
201     }
202 
203     // Entry Object path.
204     auto objPath = std::filesystem::path(baseEntryPath) / std::to_string(id);
205 
206     // TODO: Get the persisted originator id & type
207     // For now, replacing it with null
208     try
209     {
210         entries.insert(std::make_pair(
211             id, std::make_unique<bmc::Entry>(
212                     bus, objPath.c_str(), id, timestamp,
213                     std::filesystem::file_size(file), file,
214                     phosphor::dump::OperationStatus::Completed, std::string(),
215                     originatorTypes::Internal, *this)));
216     }
217     catch (const std::invalid_argument& e)
218     {
219         lg2::error(
220             "Error in creating dump entry, errormsg: {ERROR}, "
221             "OBJECTPATH: {OBJECT_PATH}, ID: {ID}, TIMESTAMP: {TIMESTAMP}, "
222             "SIZE: {SIZE}, FILENAME: {FILENAME}",
223             "ERROR", e, "OBJECT_PATH", objPath, "ID", id, "TIMESTAMP",
224             timestamp, "SIZE", std::filesystem::file_size(file), "FILENAME",
225             file);
226         return;
227     }
228 }
229 
230 void Manager::watchCallback(const UserMap& fileInfo)
231 {
232     for (const auto& i : fileInfo)
233     {
234         // For any new dump file create dump entry object
235         // and associated inotify watch.
236         if (IN_CLOSE_WRITE == i.second)
237         {
238             if (!std::filesystem::is_directory(i.first))
239             {
240                 // Don't require filename to be passed, as the path
241                 // of dump directory is stored in the childWatchMap
242                 removeWatch(i.first.parent_path());
243 
244                 // dump file is written now create D-Bus entry
245                 createEntry(i.first);
246             }
247             else
248             {
249                 removeWatch(i.first);
250             }
251         }
252         // Start inotify watch on newly created directory.
253         else if ((IN_CREATE == i.second) &&
254                  std::filesystem::is_directory(i.first))
255         {
256             auto watchObj = std::make_unique<Watch>(
257                 eventLoop, IN_NONBLOCK, IN_CLOSE_WRITE, EPOLLIN, i.first,
258                 std::bind(
259                     std::mem_fn(&phosphor::dump::bmc::Manager::watchCallback),
260                     this, std::placeholders::_1));
261 
262             childWatchMap.emplace(i.first, std::move(watchObj));
263         }
264     }
265 }
266 
267 void Manager::removeWatch(const std::filesystem::path& path)
268 {
269     // Delete Watch entry from map.
270     childWatchMap.erase(path);
271 }
272 
273 void Manager::restore()
274 {
275     std::filesystem::path dir(dumpDir);
276     if (!std::filesystem::exists(dir) || std::filesystem::is_empty(dir))
277     {
278         return;
279     }
280 
281     // Dump file path: <DUMP_PATH>/<id>/<filename>
282     for (const auto& p : std::filesystem::directory_iterator(dir))
283     {
284         auto idStr = p.path().filename().string();
285 
286         // Consider only directory's with dump id as name.
287         // Note: As per design one file per directory.
288         if ((std::filesystem::is_directory(p.path())) &&
289             std::all_of(idStr.begin(), idStr.end(), ::isdigit))
290         {
291             lastEntryId = std::max(lastEntryId,
292                                    static_cast<uint32_t>(std::stoul(idStr)));
293             auto fileIt = std::filesystem::directory_iterator(p.path());
294             // Create dump entry d-bus object.
295             if (fileIt != std::filesystem::end(fileIt))
296             {
297                 createEntry(fileIt->path());
298             }
299         }
300     }
301 }
302 
303 size_t getDirectorySize(const std::string dir)
304 {
305     auto size = 0;
306     for (const auto& p : std::filesystem::recursive_directory_iterator(dir))
307     {
308         if (!std::filesystem::is_directory(p))
309         {
310             size += std::ceil(std::filesystem::file_size(p) / 1024.0);
311         }
312     }
313     return size;
314 }
315 
316 size_t Manager::getAllowedSize()
317 {
318     // Get current size of the dump directory.
319     auto size = getDirectorySize(dumpDir);
320 
321     // Set the Dump size to Maximum  if the free space is greater than
322     // Dump max size otherwise return the available size.
323 
324     size = (size > BMC_DUMP_TOTAL_SIZE ? 0 : BMC_DUMP_TOTAL_SIZE - size);
325 
326 #ifdef BMC_DUMP_ROTATE_CONFIG
327     // Delete the first existing file until the space is enough
328     while (size < BMC_DUMP_MIN_SPACE_REQD)
329     {
330         auto delEntry = min_element(
331             entries.begin(), entries.end(),
332             [](const auto& l, const auto& r) { return l.first < r.first; });
333         auto delPath = std::filesystem::path(dumpDir) /
334                        std::to_string(delEntry->first);
335 
336         size += getDirectorySize(delPath);
337 
338         delEntry->second->delete_();
339     }
340 #else
341     using namespace sdbusplus::xyz::openbmc_project::Dump::Create::Error;
342     using Reason = xyz::openbmc_project::Dump::Create::QuotaExceeded::REASON;
343 
344     if (size < BMC_DUMP_MIN_SPACE_REQD)
345     {
346         // Reached to maximum limit
347         elog<QuotaExceeded>(Reason("Not enough space: Delete old dumps"));
348     }
349 #endif
350 
351     if (size > BMC_DUMP_MAX_SIZE)
352     {
353         size = BMC_DUMP_MAX_SIZE;
354     }
355 
356     return size;
357 }
358 
359 } // namespace bmc
360 } // namespace dump
361 } // namespace phosphor
362