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