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