1 /** 2 * Copyright © 2019 IBM Corporation 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 #include "repository.hpp" 17 18 #include "pel_values.hpp" 19 20 #include <fcntl.h> 21 #include <sys/stat.h> 22 23 #include <phosphor-logging/lg2.hpp> 24 #include <xyz/openbmc_project/Common/File/error.hpp> 25 26 #include <fstream> 27 28 namespace openpower 29 { 30 namespace pels 31 { 32 33 namespace fs = std::filesystem; 34 namespace file_error = sdbusplus::xyz::openbmc_project::Common::File::Error; 35 36 constexpr size_t warningPercentage = 95; 37 38 /** 39 * @brief Returns the amount of space the file uses on disk. 40 * 41 * This is different than just the regular size of the file. 42 * 43 * @param[in] file - The file to get the size of 44 * 45 * @return size_t The disk space the file uses 46 */ 47 size_t getFileDiskSize(const std::filesystem::path& file) 48 { 49 constexpr size_t statBlockSize = 512; 50 struct stat statData; 51 auto rc = stat(file.c_str(), &statData); 52 if (rc != 0) 53 { 54 auto e = errno; 55 lg2::error("Call to stat() failed on {FILE} with errno {ERRNO}", "FILE", 56 file.native(), "ERRNO", e); 57 abort(); 58 } 59 60 return statData.st_blocks * statBlockSize; 61 } 62 63 Repository::Repository(const std::filesystem::path& basePath, size_t repoSize, 64 size_t maxNumPELs) : 65 _logPath(basePath / "logs"), _maxRepoSize(repoSize), 66 _maxNumPELs(maxNumPELs), _archivePath(basePath / "logs" / "archive") 67 { 68 if (!fs::exists(_logPath)) 69 { 70 fs::create_directories(_logPath); 71 } 72 73 if (!fs::exists(_archivePath)) 74 { 75 fs::create_directories(_archivePath); 76 } 77 78 restore(); 79 } 80 81 void Repository::restore() 82 { 83 for (auto& dirEntry : fs::directory_iterator(_logPath)) 84 { 85 try 86 { 87 if (!fs::is_regular_file(dirEntry.path())) 88 { 89 continue; 90 } 91 92 std::ifstream file{dirEntry.path()}; 93 std::vector<uint8_t> data{std::istreambuf_iterator<char>(file), 94 std::istreambuf_iterator<char>()}; 95 file.close(); 96 97 PEL pel{data}; 98 if (pel.valid()) 99 { 100 // If the host hasn't acked it, reset the host state so 101 // it will get sent up again. 102 if (pel.hostTransmissionState() == TransmissionState::sent) 103 { 104 pel.setHostTransmissionState(TransmissionState::newPEL); 105 try 106 { 107 write(pel, dirEntry.path()); 108 } 109 catch (const std::exception& e) 110 { 111 lg2::error( 112 "Failed to save PEL after updating host state, PEL ID = {ID}", 113 "ID", lg2::hex, pel.id()); 114 } 115 } 116 117 PELAttributes attributes{ 118 dirEntry.path(), 119 getFileDiskSize(dirEntry.path()), 120 pel.privateHeader().creatorID(), 121 pel.userHeader().subsystem(), 122 pel.userHeader().severity(), 123 pel.userHeader().actionFlags(), 124 pel.hostTransmissionState(), 125 pel.hmcTransmissionState(), 126 pel.plid(), 127 pel.getDeconfigFlag(), 128 pel.getGuardFlag(), 129 getMillisecondsSinceEpoch( 130 pel.privateHeader().createTimestamp())}; 131 132 using pelID = LogID::Pel; 133 using obmcID = LogID::Obmc; 134 _pelAttributes.emplace( 135 LogID(pelID(pel.id()), obmcID(pel.obmcLogID())), 136 attributes); 137 138 updateRepoStats(attributes, true); 139 } 140 else 141 { 142 lg2::error( 143 "Found invalid PEL file {FILE} while restoring. Removing.", 144 "FILE", dirEntry.path()); 145 fs::remove(dirEntry.path()); 146 } 147 } 148 catch (const std::exception& e) 149 { 150 lg2::error("Hit exception while restoring PEL file {FILE}: {ERROR}", 151 "FILE", dirEntry.path(), "ERROR", e); 152 } 153 } 154 155 // Get size of archive folder 156 for (auto& dirEntry : fs::directory_iterator(_archivePath)) 157 { 158 _archiveSize += getFileDiskSize(dirEntry); 159 } 160 } 161 162 std::string Repository::getPELFilename(uint32_t pelID, const BCDTime& time) 163 { 164 char name[50]; 165 sprintf(name, "%.2X%.2X%.2X%.2X%.2X%.2X%.2X%.2X_%.8X", time.yearMSB, 166 time.yearLSB, time.month, time.day, time.hour, time.minutes, 167 time.seconds, time.hundredths, pelID); 168 return std::string{name}; 169 } 170 171 void Repository::add(std::unique_ptr<PEL>& pel) 172 { 173 pel->setHostTransmissionState(TransmissionState::newPEL); 174 pel->setHMCTransmissionState(TransmissionState::newPEL); 175 176 auto path = _logPath / getPELFilename(pel->id(), pel->commitTime()); 177 178 write(*(pel.get()), path); 179 180 PELAttributes attributes{ 181 path, 182 getFileDiskSize(path), 183 pel->privateHeader().creatorID(), 184 pel->userHeader().subsystem(), 185 pel->userHeader().severity(), 186 pel->userHeader().actionFlags(), 187 pel->hostTransmissionState(), 188 pel->hmcTransmissionState(), 189 pel->plid(), 190 pel->getDeconfigFlag(), 191 pel->getGuardFlag(), 192 getMillisecondsSinceEpoch(pel->privateHeader().createTimestamp())}; 193 194 using pelID = LogID::Pel; 195 using obmcID = LogID::Obmc; 196 _pelAttributes.emplace(LogID(pelID(pel->id()), obmcID(pel->obmcLogID())), 197 attributes); 198 199 _lastPelID = pel->id(); 200 201 updateRepoStats(attributes, true); 202 203 processAddCallbacks(*pel); 204 } 205 206 void Repository::write(const PEL& pel, const fs::path& path) 207 { 208 std::ofstream file{path, std::ios::binary}; 209 210 if (!file.good()) 211 { 212 // If this fails, the filesystem is probably full so it isn't like 213 // we could successfully create yet another error log here. 214 auto e = errno; 215 fs::remove(path); 216 lg2::error( 217 "Unable to open PEL file {FILE} for writing, errno = {ERRNO}", 218 "FILE", path, "ERRNO", e); 219 throw file_error::Open(); 220 } 221 222 auto data = pel.data(); 223 file.write(reinterpret_cast<const char*>(data.data()), data.size()); 224 225 if (file.fail()) 226 { 227 // Same note as above about not being able to create an error log 228 // for this case even if we wanted. 229 auto e = errno; 230 file.close(); 231 fs::remove(path); 232 lg2::error("Unable to write PEL file {FILE}, errno = {ERRNO}", "FILE", 233 path, "ERRNO", e); 234 throw file_error::Write(); 235 } 236 } 237 238 std::optional<Repository::LogID> Repository::remove(const LogID& id) 239 { 240 auto pel = findPEL(id); 241 if (pel == _pelAttributes.end()) 242 { 243 return std::nullopt; 244 } 245 246 LogID actualID = pel->first; 247 updateRepoStats(pel->second, false); 248 249 lg2::debug( 250 "Removing PEL from repository, PEL ID = {PEL_ID}, BMC log ID = {BMC_ID}", 251 "PEL_ID", lg2::hex, actualID.pelID.id, "BMC_ID", actualID.obmcID.id); 252 253 if (fs::exists(pel->second.path)) 254 { 255 // Check for existense of new archive folder 256 if (!fs::exists(_archivePath)) 257 { 258 fs::create_directories(_archivePath); 259 } 260 261 // Move log file to archive folder 262 auto fileName = _archivePath / pel->second.path.filename(); 263 fs::rename(pel->second.path, fileName); 264 265 // Update size of file 266 _archiveSize += getFileDiskSize(fileName); 267 } 268 269 _pelAttributes.erase(pel); 270 271 processDeleteCallbacks(actualID.pelID.id); 272 273 return actualID; 274 } 275 276 std::optional<std::vector<uint8_t>> Repository::getPELData(const LogID& id) 277 { 278 auto pel = findPEL(id); 279 if (pel != _pelAttributes.end()) 280 { 281 std::ifstream file{pel->second.path.c_str()}; 282 if (!file.good()) 283 { 284 auto e = errno; 285 lg2::error("Unable to open PEL file {FILE}, errno = {ERRNO}", 286 "FILE", pel->second.path, "ERRNO", e); 287 throw file_error::Open(); 288 } 289 290 std::vector<uint8_t> data{std::istreambuf_iterator<char>(file), 291 std::istreambuf_iterator<char>()}; 292 return data; 293 } 294 295 return std::nullopt; 296 } 297 298 std::optional<sdbusplus::message::unix_fd> Repository::getPELFD(const LogID& id) 299 { 300 auto pel = findPEL(id); 301 if (pel != _pelAttributes.end()) 302 { 303 int fd = open(pel->second.path.c_str(), O_RDONLY | O_NONBLOCK); 304 if (fd == -1) 305 { 306 auto e = errno; 307 lg2::error("Unable to open PEL file {FILE}, errno = {ERRNO}", 308 "FILE", pel->second.path, "ERRNO", e); 309 throw file_error::Open(); 310 } 311 312 // Must leave the file open here. It will be closed by sdbusplus 313 // when it sends it back over D-Bus. 314 return fd; 315 } 316 return std::nullopt; 317 } 318 319 void Repository::for_each(ForEachFunc func) const 320 { 321 for (const auto& [id, attributes] : _pelAttributes) 322 { 323 std::ifstream file{attributes.path}; 324 325 if (!file.good()) 326 { 327 auto e = errno; 328 lg2::error( 329 "Repository::for_each: Unable to open PEL file {FILE}, errno = {ERRNO}", 330 "FILE", attributes.path, "ERRNO", e); 331 continue; 332 } 333 334 std::vector<uint8_t> data{std::istreambuf_iterator<char>(file), 335 std::istreambuf_iterator<char>()}; 336 file.close(); 337 338 PEL pel{data}; 339 340 try 341 { 342 if (func(pel)) 343 { 344 break; 345 } 346 } 347 catch (const std::exception& e) 348 { 349 lg2::error("Repository::for_each function exception: {ERROR}", 350 "ERROR", e); 351 } 352 } 353 } 354 355 void Repository::processAddCallbacks(const PEL& pel) const 356 { 357 for (auto& [name, func] : _addSubscriptions) 358 { 359 try 360 { 361 func(pel); 362 } 363 catch (const std::exception& e) 364 { 365 lg2::error( 366 "PEL Repository add callback exception. Name = {NAME}, Error = {ERROR}", 367 "NAME", name, "ERROR", e); 368 } 369 } 370 } 371 372 void Repository::processDeleteCallbacks(uint32_t id) const 373 { 374 for (auto& [name, func] : _deleteSubscriptions) 375 { 376 try 377 { 378 func(id); 379 } 380 catch (const std::exception& e) 381 { 382 lg2::error( 383 "PEL Repository delete callback exception. Name = {NAME}, Error = {ERROR}", 384 "NAME", name, "ERROR", e); 385 } 386 } 387 } 388 389 std::optional<std::reference_wrapper<const Repository::PELAttributes>> 390 Repository::getPELAttributes(const LogID& id) const 391 { 392 auto pel = findPEL(id); 393 if (pel != _pelAttributes.end()) 394 { 395 return pel->second; 396 } 397 398 return std::nullopt; 399 } 400 401 void Repository::setPELHostTransState(uint32_t pelID, TransmissionState state) 402 { 403 LogID id{LogID::Pel{pelID}}; 404 auto attr = std::find_if(_pelAttributes.begin(), _pelAttributes.end(), 405 [&id](const auto& a) { return a.first == id; }); 406 407 if ((attr != _pelAttributes.end()) && (attr->second.hostState != state)) 408 { 409 PELUpdateFunc func = [state](PEL& pel) { 410 pel.setHostTransmissionState(state); 411 return true; 412 }; 413 414 try 415 { 416 updatePEL(attr->second.path, func); 417 } 418 catch (const std::exception& e) 419 { 420 lg2::error( 421 "Unable to update PEL host transmission state. Path = {PATH}, Error = {ERROR}", 422 "PATH", attr->second.path, "ERROR", e); 423 } 424 } 425 } 426 427 void Repository::setPELHMCTransState(uint32_t pelID, TransmissionState state) 428 { 429 LogID id{LogID::Pel{pelID}}; 430 auto attr = std::find_if(_pelAttributes.begin(), _pelAttributes.end(), 431 [&id](const auto& a) { return a.first == id; }); 432 433 if ((attr != _pelAttributes.end()) && (attr->second.hmcState != state)) 434 { 435 PELUpdateFunc func = [state](PEL& pel) { 436 pel.setHMCTransmissionState(state); 437 return true; 438 }; 439 440 try 441 { 442 updatePEL(attr->second.path, func); 443 } 444 catch (const std::exception& e) 445 { 446 lg2::error( 447 "Unable to update PEL HMC transmission state. Path = {PATH}, Error = {ERROR}", 448 "PATH", attr->second.path, "ERROR", e); 449 } 450 } 451 } 452 453 bool Repository::updatePEL(const fs::path& path, PELUpdateFunc updateFunc) 454 { 455 std::ifstream file{path}; 456 std::vector<uint8_t> data{std::istreambuf_iterator<char>(file), 457 std::istreambuf_iterator<char>()}; 458 file.close(); 459 460 PEL pel{data}; 461 462 if (pel.valid()) 463 { 464 if (updateFunc(pel)) 465 { 466 // Three attribute fields can change post creation from 467 // an updatePEL call: 468 // - hmcTransmissionState - When HMC acks a PEL 469 // - hostTransmissionState - When host acks a PEL 470 // - deconfig flag - Can be cleared for PELs that call out 471 // hotplugged FRUs. 472 // Make sure they're up to date. 473 LogID id{LogID::Pel(pel.id())}; 474 auto attr = 475 std::find_if(_pelAttributes.begin(), _pelAttributes.end(), 476 [&id](const auto& a) { return a.first == id; }); 477 if (attr != _pelAttributes.end()) 478 { 479 attr->second.hmcState = pel.hmcTransmissionState(); 480 attr->second.hostState = pel.hostTransmissionState(); 481 attr->second.deconfig = pel.getDeconfigFlag(); 482 } 483 484 write(pel, path); 485 return true; 486 } 487 } 488 else 489 { 490 throw std::runtime_error( 491 "Unable to read a valid PEL when trying to update it"); 492 } 493 return false; 494 } 495 496 bool Repository::isServiceableSev(const PELAttributes& pel) 497 { 498 auto sevType = static_cast<SeverityType>(pel.severity & 0xF0); 499 auto sevPVEntry = 500 pel_values::findByValue(pel.severity, pel_values::severityValues); 501 std::string sevName = std::get<pel_values::registryNamePos>(*sevPVEntry); 502 503 bool check1 = (sevType == SeverityType::predictive) || 504 (sevType == SeverityType::unrecoverable) || 505 (sevType == SeverityType::critical); 506 507 bool check2 = ((sevType == SeverityType::recovered) || 508 (sevName == "symptom_recovered")) && 509 !pel.actionFlags.test(hiddenFlagBit); 510 511 bool check3 = (sevName == "symptom_predictive") || 512 (sevName == "symptom_unrecoverable") || 513 (sevName == "symptom_critical"); 514 515 return check1 || check2 || check3; 516 } 517 518 void Repository::updateRepoStats(const PELAttributes& pel, bool pelAdded) 519 { 520 auto isServiceable = Repository::isServiceableSev(pel); 521 auto bmcPEL = CreatorID::openBMC == static_cast<CreatorID>(pel.creator); 522 523 auto adjustSize = [pelAdded, &pel](auto& runningSize) { 524 if (pelAdded) 525 { 526 runningSize += pel.sizeOnDisk; 527 } 528 else 529 { 530 runningSize = std::max(static_cast<int64_t>(runningSize) - 531 static_cast<int64_t>(pel.sizeOnDisk), 532 static_cast<int64_t>(0)); 533 } 534 }; 535 536 adjustSize(_sizes.total); 537 538 if (bmcPEL) 539 { 540 adjustSize(_sizes.bmc); 541 if (isServiceable) 542 { 543 adjustSize(_sizes.bmcServiceable); 544 } 545 else 546 { 547 adjustSize(_sizes.bmcInfo); 548 } 549 } 550 else 551 { 552 adjustSize(_sizes.nonBMC); 553 if (isServiceable) 554 { 555 adjustSize(_sizes.nonBMCServiceable); 556 } 557 else 558 { 559 adjustSize(_sizes.nonBMCInfo); 560 } 561 } 562 } 563 564 bool Repository::sizeWarning() 565 { 566 std::error_code ec; 567 568 if ((_archiveSize > 0) && ((_sizes.total + _archiveSize) > 569 ((_maxRepoSize * warningPercentage) / 100))) 570 { 571 lg2::info( 572 "Repository::sizeWarning function:Deleting the files in archive"); 573 574 for (const auto& dirEntry : fs::directory_iterator(_archivePath)) 575 { 576 fs::remove(dirEntry.path(), ec); 577 if (ec) 578 { 579 lg2::info("Repository::sizeWarning: Could not delete " 580 "file {FILE} in PEL archive", 581 "FILE", dirEntry.path()); 582 } 583 } 584 585 _archiveSize = 0; 586 } 587 588 return (_sizes.total > (_maxRepoSize * warningPercentage / 100)) || 589 (_pelAttributes.size() > _maxNumPELs); 590 } 591 592 std::vector<Repository::AttributesReference> Repository::getAllPELAttributes( 593 SortOrder order) const 594 { 595 std::vector<Repository::AttributesReference> attributes; 596 597 std::for_each(_pelAttributes.begin(), _pelAttributes.end(), 598 [&attributes](auto& pelEntry) { 599 attributes.push_back(pelEntry); 600 }); 601 602 std::sort(attributes.begin(), attributes.end(), 603 [order](const auto& left, const auto& right) { 604 if (order == SortOrder::ascending) 605 { 606 return left.get().second.path < right.get().second.path; 607 } 608 return left.get().second.path > right.get().second.path; 609 }); 610 611 return attributes; 612 } 613 614 std::vector<uint32_t> Repository::prune( 615 const std::vector<uint32_t>& idsWithHwIsoEntry) 616 { 617 std::vector<uint32_t> obmcLogIDs; 618 lg2::info("Pruning PEL repository that takes up {TOTAL} bytes and has " 619 "{NUM_PELS} PELs", 620 "TOTAL", _sizes.total, "NUM_PELS", _pelAttributes.size()); 621 622 // Set up the 5 functions to check if the PEL category 623 // is still over its limits. 624 625 // BMC informational PELs should only take up 15% 626 IsOverLimitFunc overBMCInfoLimit = [this]() { 627 return _sizes.bmcInfo > _maxRepoSize * 15 / 100; 628 }; 629 630 // BMC non informational PELs should only take up 30% 631 IsOverLimitFunc overBMCNonInfoLimit = [this]() { 632 return _sizes.bmcServiceable > _maxRepoSize * 30 / 100; 633 }; 634 635 // Non BMC informational PELs should only take up 15% 636 IsOverLimitFunc overNonBMCInfoLimit = [this]() { 637 return _sizes.nonBMCInfo > _maxRepoSize * 15 / 100; 638 }; 639 640 // Non BMC non informational PELs should only take up 15% 641 IsOverLimitFunc overNonBMCNonInfoLimit = [this]() { 642 return _sizes.nonBMCServiceable > _maxRepoSize * 30 / 100; 643 }; 644 645 // Bring the total number of PELs down to 80% of the max 646 IsOverLimitFunc tooManyPELsLimit = [this]() { 647 return _pelAttributes.size() > _maxNumPELs * 80 / 100; 648 }; 649 650 // Set up the functions to determine which category a PEL is in. 651 // TODO: Return false in these functions if a PEL caused a guard record. 652 653 // A BMC informational PEL 654 IsPELTypeFunc isBMCInfo = [](const PELAttributes& pel) { 655 return (CreatorID::openBMC == static_cast<CreatorID>(pel.creator)) && 656 !Repository::isServiceableSev(pel); 657 }; 658 659 // A BMC non informational PEL 660 IsPELTypeFunc isBMCNonInfo = [](const PELAttributes& pel) { 661 return (CreatorID::openBMC == static_cast<CreatorID>(pel.creator)) && 662 Repository::isServiceableSev(pel); 663 }; 664 665 // A non BMC informational PEL 666 IsPELTypeFunc isNonBMCInfo = [](const PELAttributes& pel) { 667 return (CreatorID::openBMC != static_cast<CreatorID>(pel.creator)) && 668 !Repository::isServiceableSev(pel); 669 }; 670 671 // A non BMC non informational PEL 672 IsPELTypeFunc isNonBMCNonInfo = [](const PELAttributes& pel) { 673 return (CreatorID::openBMC != static_cast<CreatorID>(pel.creator)) && 674 Repository::isServiceableSev(pel); 675 }; 676 677 // When counting PELs, count every PEL 678 IsPELTypeFunc isAnyPEL = [](const PELAttributes& /*pel*/) { return true; }; 679 680 // Check all 4 categories, which will result in at most 90% 681 // usage (15 + 30 + 15 + 30). 682 removePELs(overBMCInfoLimit, isBMCInfo, idsWithHwIsoEntry, obmcLogIDs); 683 removePELs(overBMCNonInfoLimit, isBMCNonInfo, idsWithHwIsoEntry, 684 obmcLogIDs); 685 removePELs(overNonBMCInfoLimit, isNonBMCInfo, idsWithHwIsoEntry, 686 obmcLogIDs); 687 removePELs(overNonBMCNonInfoLimit, isNonBMCNonInfo, idsWithHwIsoEntry, 688 obmcLogIDs); 689 690 // After the above pruning check if there are still too many PELs, 691 // which can happen depending on PEL sizes. 692 if (_pelAttributes.size() > _maxNumPELs) 693 { 694 removePELs(tooManyPELsLimit, isAnyPEL, idsWithHwIsoEntry, obmcLogIDs); 695 } 696 697 if (!obmcLogIDs.empty()) 698 { 699 lg2::info("Number of PELs removed to save space: {NUM_PELS}", 700 "NUM_PELS", obmcLogIDs.size()); 701 } 702 703 return obmcLogIDs; 704 } 705 706 void Repository::removePELs(const IsOverLimitFunc& isOverLimit, 707 const IsPELTypeFunc& isPELType, 708 const std::vector<uint32_t>& idsWithHwIsoEntry, 709 std::vector<uint32_t>& removedBMCLogIDs) 710 { 711 if (!isOverLimit()) 712 { 713 return; 714 } 715 716 auto attributes = getAllPELAttributes(SortOrder::ascending); 717 718 // Make 4 passes on the PELs, stopping as soon as isOverLimit 719 // returns false. 720 // Pass 1: only delete HMC acked PELs 721 // Pass 2: only delete OS acked PELs 722 // Pass 3: only delete PHYP sent PELs 723 // Pass 4: delete all PELs 724 static const std::vector<std::function<bool(const PELAttributes& pel)>> 725 stateChecks{[](const auto& pel) { 726 return pel.hmcState == TransmissionState::acked; 727 }, 728 729 [](const auto& pel) { 730 return pel.hostState == TransmissionState::acked; 731 }, 732 733 [](const auto& pel) { 734 return pel.hostState == TransmissionState::sent; 735 }, 736 737 [](const auto& /*pel*/) { return true; }}; 738 739 for (const auto& stateCheck : stateChecks) 740 { 741 for (auto it = attributes.begin(); it != attributes.end();) 742 { 743 const auto& pel = it->get(); 744 if (isPELType(pel.second) && stateCheck(pel.second)) 745 { 746 auto removedID = pel.first.obmcID.id; 747 748 auto idFound = std::find(idsWithHwIsoEntry.begin(), 749 idsWithHwIsoEntry.end(), removedID); 750 if (idFound != idsWithHwIsoEntry.end()) 751 { 752 ++it; 753 continue; 754 } 755 756 remove(pel.first); 757 758 removedBMCLogIDs.push_back(removedID); 759 760 attributes.erase(it); 761 762 if (!isOverLimit()) 763 { 764 break; 765 } 766 } 767 else 768 { 769 ++it; 770 } 771 } 772 773 if (!isOverLimit()) 774 { 775 break; 776 } 777 } 778 } 779 780 void Repository::archivePEL(const PEL& pel) 781 { 782 if (pel.valid()) 783 { 784 auto path = _archivePath / getPELFilename(pel.id(), pel.commitTime()); 785 786 write(pel, path); 787 788 _archiveSize += getFileDiskSize(path); 789 } 790 } 791 792 } // namespace pels 793 } // namespace openpower 794