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