#include "config.h" #include "dump_manager_bmc.hpp" #include "bmc_dump_entry.hpp" #include "dump_types.hpp" #include "xyz/openbmc_project/Common/error.hpp" #include "xyz/openbmc_project/Dump/Create/error.hpp" #include #include #include #include #include #include #include #include namespace phosphor { namespace dump { namespace bmc { using namespace sdbusplus::xyz::openbmc_project::Common::Error; using namespace phosphor::logging; bool Manager::fUserDumpInProgress = false; constexpr auto BMC_DUMP = "BMC_DUMP"; sdbusplus::message::object_path Manager::createDump(phosphor::dump::DumpCreateParams params) { if (params.size() > CREATE_DUMP_MAX_PARAMS) { lg2::warning("BMC dump accepts not more than 2 additional parameters"); } // Get the originator id and type from params std::string originatorId; originatorTypes originatorType; phosphor::dump::extractOriginatorProperties(params, originatorId, originatorType); using CreateParameters = sdbusplus::common::xyz::openbmc_project::dump::Create::CreateParameters; DumpTypes dumpType = DumpTypes::USER; std::string type = extractParameter( convertCreateParametersToString(CreateParameters::DumpType), params); if (!type.empty()) { dumpType = validateDumpType(type, BMC_DUMP); } if (dumpType == DumpTypes::ELOG) { dumpType = getErrorDumpType(params); } std::string path = extractParameter( convertCreateParametersToString(CreateParameters::FilePath), params); if ((Manager::fUserDumpInProgress == true) && (dumpType == DumpTypes::USER)) { lg2::info("Another user initiated dump in progress"); elog(); } lg2::info("Initiating new BMC dump with type: {TYPE} path: {PATH}", "TYPE", dumpTypeToString(dumpType).value(), "PATH", path); auto id = captureDump(dumpType, path); // Entry Object path. auto objPath = std::filesystem::path(baseEntryPath) / std::to_string(id); try { uint64_t timeStamp = std::chrono::duration_cast( std::chrono::system_clock::now().time_since_epoch()) .count(); entries.insert(std::make_pair( id, std::make_unique( bus, objPath.c_str(), id, timeStamp, 0, std::string(), phosphor::dump::OperationStatus::InProgress, originatorId, originatorType, *this))); } catch (const std::invalid_argument& e) { lg2::error("Error in creating dump entry, errormsg: {ERROR}, " "OBJECTPATH: {OBJECT_PATH}, ID: {ID}", "ERROR", e, "OBJECT_PATH", objPath, "ID", id); elog(); } if (dumpType == DumpTypes::USER) { Manager::fUserDumpInProgress = true; } return objPath.string(); } uint32_t Manager::captureDump(DumpTypes type, const std::string& path) { // Get Dump size. auto size = getAllowedSize(); pid_t pid = fork(); if (pid == 0) { std::filesystem::path dumpPath(dumpDir); auto id = std::to_string(lastEntryId + 1); dumpPath /= id; auto strType = dumpTypeToString(type).value(); execl("/usr/bin/dreport", "dreport", "-d", dumpPath.c_str(), "-i", id.c_str(), "-s", std::to_string(size).c_str(), "-q", "-v", "-p", path.empty() ? "" : path.c_str(), "-t", strType.c_str(), nullptr); // dreport script execution is failed. auto error = errno; lg2::error("Error occurred during dreport function execution, " "errno: {ERRNO}", "ERRNO", error); elog(); } else if (pid > 0) { Child::Callback callback = [this, type, pid](Child&, const siginfo_t*) { if (type == DumpTypes::USER) { lg2::info("User initiated dump completed, resetting flag"); Manager::fUserDumpInProgress = false; } this->childPtrMap.erase(pid); }; try { childPtrMap.emplace(pid, std::make_unique(eventLoop.get(), pid, WEXITED | WSTOPPED, std::move(callback))); } catch (const sdeventplus::SdEventError& ex) { // Failed to add to event loop lg2::error( "Error occurred during the sdeventplus::source::Child creation " "ex: {ERROR}", "ERROR", ex); elog(); } } else { auto error = errno; lg2::error("Error occurred during fork, errno: {ERRNO}", "ERRNO", error); elog(); } return ++lastEntryId; } void Manager::createEntry(const std::filesystem::path& file) { auto dumpDetails = extractDumpDetails(file); if (!dumpDetails) { lg2::error("Failed to extract dump details from file name: {PATH}", "PATH", file); return; } auto [id, timestamp, size] = *dumpDetails; // If there is an existing entry update it and return. auto dumpEntry = entries.find(id); if (dumpEntry != entries.end()) { dynamic_cast(dumpEntry->second.get()) ->update(timestamp, std::filesystem::file_size(file), file); return; } // Entry Object path. auto objPath = std::filesystem::path(baseEntryPath) / std::to_string(id); // TODO: Get the persisted originator id & type // For now, replacing it with null try { entries.insert(std::make_pair( id, std::make_unique( bus, objPath.c_str(), id, timestamp, std::filesystem::file_size(file), file, phosphor::dump::OperationStatus::Completed, std::string(), originatorTypes::Internal, *this))); } catch (const std::invalid_argument& e) { lg2::error( "Error in creating dump entry, errormsg: {ERROR}, " "OBJECTPATH: {OBJECT_PATH}, ID: {ID}, TIMESTAMP: {TIMESTAMP}, " "SIZE: {SIZE}, FILENAME: {FILENAME}", "ERROR", e, "OBJECT_PATH", objPath, "ID", id, "TIMESTAMP", timestamp, "SIZE", std::filesystem::file_size(file), "FILENAME", file); } } void Manager::watchCallback(const UserMap& fileInfo) { for (const auto& i : fileInfo) { // For any new dump file create dump entry object // and associated inotify watch. if (IN_CLOSE_WRITE == i.second) { if (!std::filesystem::is_directory(i.first)) { // Don't require filename to be passed, as the path // of dump directory is stored in the childWatchMap removeWatch(i.first.parent_path()); // dump file is written now create D-Bus entry createEntry(i.first); } else { removeWatch(i.first); } } // Start inotify watch on newly created directory. else if ((IN_CREATE == i.second) && std::filesystem::is_directory(i.first)) { auto watchObj = std::make_unique( eventLoop, IN_NONBLOCK, IN_CLOSE_WRITE, EPOLLIN, i.first, std::bind( std::mem_fn(&phosphor::dump::bmc::Manager::watchCallback), this, std::placeholders::_1)); childWatchMap.emplace(i.first, std::move(watchObj)); } } } void Manager::removeWatch(const std::filesystem::path& path) { // Delete Watch entry from map. childWatchMap.erase(path); } void Manager::restore() { std::filesystem::path dir(dumpDir); if (!std::filesystem::exists(dir) || std::filesystem::is_empty(dir)) { return; } // Dump file path: // for (const auto& p : std::filesystem::directory_iterator(dir)) { auto idStr = p.path().filename().string(); // Consider only directories with dump id as name. // Note: As per design one file per directory. if ((std::filesystem::is_directory(p.path())) && std::all_of(idStr.begin(), idStr.end(), ::isdigit)) { lastEntryId = std::max(lastEntryId, static_cast(std::stoul(idStr))); for (const auto& file : std::filesystem::directory_iterator(p.path())) { // Skip .preserve directory if (file.path().filename() == PRESERVE) { continue; } // Entry Object path. auto objPath = std::filesystem::path(baseEntryPath) / idStr; auto entry = Entry::deserializeEntry( bus, std::stoul(idStr), objPath.string(), file.path(), *this); if (entry != nullptr) { entries.insert( std::make_pair(entry->getDumpId(), std::move(entry))); } } } } } size_t getDirectorySize(const std::string dir) { auto size = 0; for (const auto& p : std::filesystem::recursive_directory_iterator(dir)) { if (!std::filesystem::is_directory(p)) { size += std::ceil(std::filesystem::file_size(p) / 1024.0); } } return size; } size_t Manager::getAllowedSize() { // Get current size of the dump directory. auto size = getDirectorySize(dumpDir); // Set the Dump size to Maximum if the free space is greater than // Dump max size otherwise return the available size. size = (size > BMC_DUMP_TOTAL_SIZE ? 0 : BMC_DUMP_TOTAL_SIZE - size); #ifdef BMC_DUMP_ROTATE_CONFIG // Delete the first existing file until the space is enough while (size < BMC_DUMP_MIN_SPACE_REQD) { auto delEntry = min_element( entries.begin(), entries.end(), [](const auto& l, const auto& r) { return l.first < r.first; }); auto delPath = std::filesystem::path(dumpDir) / std::to_string(delEntry->first); size += getDirectorySize(delPath); delEntry->second->delete_(); } #else using namespace sdbusplus::xyz::openbmc_project::Dump::Create::Error; using Reason = xyz::openbmc_project::Dump::Create::QuotaExceeded::REASON; if (size < BMC_DUMP_MIN_SPACE_REQD) { // Reached to maximum limit elog(Reason("Not enough space: Delete old dumps")); } #endif if (size > BMC_DUMP_MAX_SIZE) { size = BMC_DUMP_MAX_SIZE; } return size; } } // namespace bmc } // namespace dump } // namespace phosphor