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