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