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