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