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