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