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