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 
16 #include <cmath>
17 #include <ctime>
18 #include <regex>
19 
20 namespace phosphor
21 {
22 namespace dump
23 {
24 namespace bmc
25 {
26 
27 using namespace sdbusplus::xyz::openbmc_project::Common::Error;
28 using namespace phosphor::logging;
29 
30 namespace internal
31 {
32 
33 void Manager::create(Type type, std::vector<std::string> fullPaths)
34 {
35     dumpMgr.phosphor::dump::bmc::Manager::captureDump(type, fullPaths);
36 }
37 
38 } // namespace internal
39 
40 sdbusplus::message::object_path
41     Manager::createDump(std::map<std::string, std::string> params)
42 {
43     if (!params.empty())
44     {
45         log<level::WARNING>("BMC dump accepts no additional parameters");
46     }
47     std::vector<std::string> paths;
48     auto id = captureDump(Type::UserRequested, paths);
49 
50     // Entry Object path.
51     auto objPath = std::filesystem::path(baseEntryPath) / std::to_string(id);
52 
53     try
54     {
55         std::time_t timeStamp = std::time(nullptr);
56         entries.insert(std::make_pair(
57             id, std::make_unique<bmc::Entry>(
58                     bus, objPath.c_str(), id, timeStamp, 0, std::string(),
59                     phosphor::dump::OperationStatus::InProgress, *this)));
60     }
61     catch (const std::invalid_argument& e)
62     {
63         log<level::ERR>(e.what());
64         log<level::ERR>("Error in creating dump entry",
65                         entry("OBJECTPATH=%s", objPath.c_str()),
66                         entry("ID=%d", id));
67         elog<InternalFailure>();
68     }
69 
70     return objPath.string();
71 }
72 
73 uint32_t Manager::captureDump(Type type,
74                               const std::vector<std::string>& fullPaths)
75 {
76     // Get Dump size.
77     auto size = getAllowedSize();
78 
79     pid_t pid = fork();
80 
81     if (pid == 0)
82     {
83         std::filesystem::path dumpPath(dumpDir);
84         auto id = std::to_string(lastEntryId + 1);
85         dumpPath /= id;
86 
87         // get dreport type map entry
88         auto tempType = TypeMap.find(type);
89 
90         execl("/usr/bin/dreport", "dreport", "-d", dumpPath.c_str(), "-i",
91               id.c_str(), "-s", std::to_string(size).c_str(), "-q", "-v", "-p",
92               fullPaths.empty() ? "" : fullPaths.front().c_str(), "-t",
93               tempType->second.c_str(), nullptr);
94 
95         // dreport script execution is failed.
96         auto error = errno;
97         log<level::ERR>("Error occurred during dreport function execution",
98                         entry("ERRNO=%d", error));
99         elog<InternalFailure>();
100     }
101     else if (pid > 0)
102     {
103         auto rc = sd_event_add_child(eventLoop.get(), nullptr, pid,
104                                      WEXITED | WSTOPPED, callback, nullptr);
105         if (0 > rc)
106         {
107             // Failed to add to event loop
108             log<level::ERR>("Error occurred during the sd_event_add_child call",
109                             entry("RC=%d", rc));
110             elog<InternalFailure>();
111         }
112     }
113     else
114     {
115         auto error = errno;
116         log<level::ERR>("Error occurred during fork", entry("ERRNO=%d", error));
117         elog<InternalFailure>();
118     }
119 
120     return ++lastEntryId;
121 }
122 
123 void Manager::createEntry(const std::filesystem::path& file)
124 {
125     // Dump File Name format obmcdump_ID_EPOCHTIME.EXT
126     static constexpr auto ID_POS = 1;
127     static constexpr auto EPOCHTIME_POS = 2;
128     std::regex file_regex("obmcdump_([0-9]+)_([0-9]+).([a-zA-Z0-9]+)");
129 
130     std::smatch match;
131     std::string name = file.filename();
132 
133     if (!((std::regex_search(name, match, file_regex)) && (match.size() > 0)))
134     {
135         log<level::ERR>("Invalid Dump file name",
136                         entry("FILENAME=%s", file.filename().c_str()));
137         return;
138     }
139 
140     auto idString = match[ID_POS];
141     auto msString = match[EPOCHTIME_POS];
142 
143     auto id = stoul(idString);
144 
145     // If there is an existing entry update it and return.
146     auto dumpEntry = entries.find(id);
147     if (dumpEntry != entries.end())
148     {
149         dynamic_cast<phosphor::dump::bmc::Entry*>(dumpEntry->second.get())
150             ->update(stoull(msString), std::filesystem::file_size(file), file);
151         return;
152     }
153 
154     // Entry Object path.
155     auto objPath = std::filesystem::path(baseEntryPath) / std::to_string(id);
156 
157     try
158     {
159         entries.insert(std::make_pair(
160             id, std::make_unique<bmc::Entry>(
161                     bus, objPath.c_str(), id, stoull(msString),
162                     std::filesystem::file_size(file), file,
163                     phosphor::dump::OperationStatus::Completed, *this)));
164     }
165     catch (const std::invalid_argument& e)
166     {
167         log<level::ERR>(e.what());
168         log<level::ERR>("Error in creating dump entry",
169                         entry("OBJECTPATH=%s", objPath.c_str()),
170                         entry("ID=%d", id),
171                         entry("TIMESTAMP=%ull", stoull(msString)),
172                         entry("SIZE=%d", std::filesystem::file_size(file)),
173                         entry("FILENAME=%s", file.c_str()));
174         return;
175     }
176 }
177 
178 void Manager::watchCallback(const UserMap& fileInfo)
179 {
180     for (const auto& i : fileInfo)
181     {
182         // For any new dump file create dump entry object
183         // and associated inotify watch.
184         if (IN_CLOSE_WRITE == i.second)
185         {
186             removeWatch(i.first);
187 
188             createEntry(i.first);
189         }
190         // Start inotify watch on newly created directory.
191         else if ((IN_CREATE == i.second) &&
192                  std::filesystem::is_directory(i.first))
193         {
194             auto watchObj = std::make_unique<Watch>(
195                 eventLoop, IN_NONBLOCK, IN_CLOSE_WRITE, EPOLLIN, i.first,
196                 std::bind(
197                     std::mem_fn(&phosphor::dump::bmc::Manager::watchCallback),
198                     this, std::placeholders::_1));
199 
200             childWatchMap.emplace(i.first, std::move(watchObj));
201         }
202     }
203 }
204 
205 void Manager::removeWatch(const std::filesystem::path& path)
206 {
207     // Delete Watch entry from map.
208     childWatchMap.erase(path);
209 }
210 
211 void Manager::restore()
212 {
213     std::filesystem::path dir(dumpDir);
214     if (!std::filesystem::exists(dir) || std::filesystem::is_empty(dir))
215     {
216         return;
217     }
218 
219     // Dump file path: <DUMP_PATH>/<id>/<filename>
220     for (const auto& p : std::filesystem::directory_iterator(dir))
221     {
222         auto idStr = p.path().filename().string();
223 
224         // Consider only directory's with dump id as name.
225         // Note: As per design one file per directory.
226         if ((std::filesystem::is_directory(p.path())) &&
227             std::all_of(idStr.begin(), idStr.end(), ::isdigit))
228         {
229             lastEntryId =
230                 std::max(lastEntryId, static_cast<uint32_t>(std::stoul(idStr)));
231             auto fileIt = std::filesystem::directory_iterator(p.path());
232             // Create dump entry d-bus object.
233             if (fileIt != std::filesystem::end(fileIt))
234             {
235                 createEntry(fileIt->path());
236             }
237         }
238     }
239 }
240 
241 size_t Manager::getAllowedSize()
242 {
243     using namespace sdbusplus::xyz::openbmc_project::Dump::Create::Error;
244     using Reason = xyz::openbmc_project::Dump::Create::QuotaExceeded::REASON;
245 
246     auto size = 0;
247 
248     // Get current size of the dump directory.
249     for (const auto& p : std::filesystem::recursive_directory_iterator(dumpDir))
250     {
251         if (!std::filesystem::is_directory(p))
252         {
253             size += std::ceil(std::filesystem::file_size(p) / 1024.0);
254         }
255     }
256 
257     // Set the Dump size to Maximum  if the free space is greater than
258     // Dump max size otherwise return the available size.
259 
260     size = (size > BMC_DUMP_TOTAL_SIZE ? 0 : BMC_DUMP_TOTAL_SIZE - size);
261 
262     if (size < BMC_DUMP_MIN_SPACE_REQD)
263     {
264         // Reached to maximum limit
265         elog<QuotaExceeded>(Reason("Not enough space: Delete old dumps"));
266     }
267     if (size > BMC_DUMP_MAX_SIZE)
268     {
269         size = BMC_DUMP_MAX_SIZE;
270     }
271 
272     return size;
273 }
274 
275 } // namespace bmc
276 } // namespace dump
277 } // namespace phosphor
278