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