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