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