1 #pragma once 2 3 #include "tool_constants.hpp" 4 #include "tool_types.hpp" 5 6 #include <nlohmann/json.hpp> 7 #include <sdbusplus/bus.hpp> 8 #include <sdbusplus/exception.hpp> 9 10 #include <fstream> 11 #include <iostream> 12 13 namespace vpd 14 { 15 namespace utils 16 { 17 /** 18 * @brief An API to read property from Dbus. 19 * 20 * API reads the property value for the specified interface and object path from 21 * the given Dbus service. 22 * 23 * The caller of the API needs to validate the validity and correctness of the 24 * type and value of data returned. The API will just fetch and return the data 25 * without any data validation. 26 * 27 * Note: It will be caller's responsibility to check for empty value returned 28 * and generate appropriate error if required. 29 * 30 * @param[in] i_serviceName - Name of the Dbus service. 31 * @param[in] i_objectPath - Object path under the service. 32 * @param[in] i_interface - Interface under which property exist. 33 * @param[in] i_property - Property whose value is to be read. 34 * 35 * @return - Value read from Dbus. 36 * 37 * @throw std::runtime_error 38 */ 39 inline types::DbusVariantType readDbusProperty( 40 const std::string& i_serviceName, const std::string& i_objectPath, 41 const std::string& i_interface, const std::string& i_property) 42 { 43 types::DbusVariantType l_propertyValue; 44 45 // Mandatory fields to make a dbus call. 46 if (i_serviceName.empty() || i_objectPath.empty() || i_interface.empty() || 47 i_property.empty()) 48 { 49 // TODO: Enable logging when verbose is enabled. 50 /*std::cout << "One of the parameter to make Dbus read call is empty." 51 << std::endl;*/ 52 throw std::runtime_error("Empty Parameter"); 53 } 54 55 try 56 { 57 auto l_bus = sdbusplus::bus::new_default(); 58 auto l_method = 59 l_bus.new_method_call(i_serviceName.c_str(), i_objectPath.c_str(), 60 "org.freedesktop.DBus.Properties", "Get"); 61 l_method.append(i_interface, i_property); 62 63 auto result = l_bus.call(l_method); 64 result.read(l_propertyValue); 65 } 66 catch (const sdbusplus::exception::SdBusError& l_ex) 67 { 68 // TODO: Enable logging when verbose is enabled. 69 // std::cout << std::string(l_ex.what()) << std::endl; 70 throw std::runtime_error(std::string(l_ex.what())); 71 } 72 return l_propertyValue; 73 } 74 75 /** 76 * @brief An API to get property map for an interface. 77 * 78 * This API returns a map of property and its value with respect to a particular 79 * interface. 80 * 81 * Note: It will be caller's responsibility to check for empty map returned and 82 * generate appropriate error. 83 * 84 * @param[in] i_service - Service name. 85 * @param[in] i_objectPath - object path. 86 * @param[in] i_interface - Interface, for the properties to be listed. 87 * 88 * @return - A map of property and value of an interface, if success. 89 * if failed, empty map. 90 */ 91 inline types::PropertyMap getPropertyMap( 92 const std::string& i_service, const std::string& i_objectPath, 93 const std::string& i_interface) noexcept 94 { 95 types::PropertyMap l_propertyValueMap; 96 if (i_service.empty() || i_objectPath.empty() || i_interface.empty()) 97 { 98 // TODO: Enable logging when verbose is enabled. 99 // std::cout << "Invalid parameters to get property map" << std::endl; 100 return l_propertyValueMap; 101 } 102 103 try 104 { 105 auto l_bus = sdbusplus::bus::new_default(); 106 auto l_method = 107 l_bus.new_method_call(i_service.c_str(), i_objectPath.c_str(), 108 "org.freedesktop.DBus.Properties", "GetAll"); 109 l_method.append(i_interface); 110 auto l_result = l_bus.call(l_method); 111 l_result.read(l_propertyValueMap); 112 } 113 catch (const sdbusplus::exception::SdBusError& l_ex) 114 { 115 // TODO: Enable logging when verbose is enabled. 116 // std::cerr << "Failed to get property map for service: [" << i_service 117 // << "], object path: [" << i_objectPath 118 // << "] Error : " << l_ex.what() << std::endl; 119 } 120 121 return l_propertyValueMap; 122 } 123 124 /** 125 * @brief An API to print json data on stdout. 126 * 127 * @param[in] i_jsonData - JSON object. 128 */ 129 inline void printJson(const nlohmann::json& i_jsonData) 130 { 131 try 132 { 133 std::cout << i_jsonData.dump(constants::INDENTATION) << std::endl; 134 } 135 catch (const nlohmann::json::type_error& l_ex) 136 { 137 throw std::runtime_error( 138 "Failed to dump JSON data, error: " + std::string(l_ex.what())); 139 } 140 } 141 142 /** 143 * @brief An API to convert binary value into ascii/hex representation. 144 * 145 * If given data contains printable characters, ASCII formated string value of 146 * the input data will be returned. Otherwise if the data has any non-printable 147 * value, returns the hex represented value of the given data in string format. 148 * 149 * @param[in] i_keywordValue - Data in binary format. 150 * 151 * @throw - Throws std::bad_alloc or std::terminate in case of error. 152 * 153 * @return - Returns the converted string value. 154 */ 155 inline std::string getPrintableValue(const types::BinaryVector& i_keywordValue) 156 { 157 bool l_allPrintable = 158 std::all_of(i_keywordValue.begin(), i_keywordValue.end(), 159 [](const auto& l_byte) { return std::isprint(l_byte); }); 160 161 std::ostringstream l_oss; 162 if (l_allPrintable) 163 { 164 l_oss << std::string(i_keywordValue.begin(), i_keywordValue.end()); 165 } 166 else 167 { 168 l_oss << "0x"; 169 for (const auto& l_byte : i_keywordValue) 170 { 171 l_oss << std::setfill('0') << std::setw(2) << std::hex 172 << static_cast<int>(l_byte); 173 } 174 } 175 176 return l_oss.str(); 177 } 178 179 /** 180 * @brief API to read keyword's value from hardware. 181 * 182 * This API reads keyword's value by requesting DBus service(vpd-manager) who 183 * hosts the 'ReadKeyword' method to read keyword's value. 184 * 185 * @param[in] i_eepromPath - EEPROM file path. 186 * @param[in] i_paramsToReadData - Property whose value has to be read. 187 * 188 * @return - Value read from hardware 189 * 190 * @throw std::runtime_error, sdbusplus::exception::SdBusError 191 */ 192 inline types::DbusVariantType 193 readKeywordFromHardware(const std::string& i_eepromPath, 194 const types::ReadVpdParams i_paramsToReadData) 195 { 196 if (i_eepromPath.empty()) 197 { 198 throw std::runtime_error("Empty EEPROM path"); 199 } 200 201 try 202 { 203 types::DbusVariantType l_propertyValue; 204 205 auto l_bus = sdbusplus::bus::new_default(); 206 207 auto l_method = l_bus.new_method_call( 208 constants::vpdManagerService, constants::vpdManagerObjectPath, 209 constants::vpdManagerInfName, "ReadKeyword"); 210 211 l_method.append(i_eepromPath, i_paramsToReadData); 212 auto l_result = l_bus.call(l_method); 213 214 l_result.read(l_propertyValue); 215 216 return l_propertyValue; 217 } 218 catch (const sdbusplus::exception::SdBusError& l_error) 219 { 220 throw; 221 } 222 } 223 224 /** 225 * @brief API to save keyword's value on file. 226 * 227 * API writes keyword's value on the given file path. If the data is in hex 228 * format, API strips '0x' and saves the value on the given file. 229 * 230 * @param[in] i_filePath - File path. 231 * @param[in] i_keywordValue - Keyword's value. 232 * 233 * @return - true on successfully writing to file, false otherwise. 234 */ 235 inline bool saveToFile(const std::string& i_filePath, 236 const std::string& i_keywordValue) 237 { 238 bool l_returnStatus = false; 239 240 if (i_keywordValue.empty()) 241 { 242 // ToDo: log only when verbose is enabled 243 std::cerr << "Save to file[ " << i_filePath 244 << "] failed, reason: Empty keyword's value received" 245 << std::endl; 246 return l_returnStatus; 247 } 248 249 std::string l_keywordValue{i_keywordValue}; 250 if (i_keywordValue.substr(0, 2).compare("0x") == constants::STR_CMP_SUCCESS) 251 { 252 l_keywordValue = i_keywordValue.substr(2); 253 } 254 255 std::ofstream l_outPutFileStream; 256 l_outPutFileStream.exceptions( 257 std::ifstream::badbit | std::ifstream::failbit); 258 try 259 { 260 l_outPutFileStream.open(i_filePath); 261 262 if (l_outPutFileStream.is_open()) 263 { 264 l_outPutFileStream.write(l_keywordValue.c_str(), 265 l_keywordValue.size()); 266 l_returnStatus = true; 267 } 268 else 269 { 270 // ToDo: log only when verbose is enabled 271 std::cerr << "Error opening output file " << i_filePath 272 << std::endl; 273 } 274 } 275 catch (const std::ios_base::failure& l_ex) 276 { 277 // ToDo: log only when verbose is enabled 278 std::cerr 279 << "Failed to write to file: " << i_filePath 280 << ", either base folder path doesn't exist or internal error occured, error: " 281 << l_ex.what() << '\n'; 282 } 283 284 return l_returnStatus; 285 } 286 287 /** 288 * @brief API to print data in JSON format on console 289 * 290 * @param[in] i_fruPath - FRU path. 291 * @param[in] i_keywordName - Keyword name. 292 * @param[in] i_keywordStrValue - Keyword's value. 293 */ 294 inline void displayOnConsole(const std::string& i_fruPath, 295 const std::string& i_keywordName, 296 const std::string& i_keywordStrValue) 297 { 298 nlohmann::json l_resultInJson = nlohmann::json::object({}); 299 nlohmann::json l_keywordValInJson = nlohmann::json::object({}); 300 301 l_keywordValInJson.emplace(i_keywordName, i_keywordStrValue); 302 l_resultInJson.emplace(i_fruPath, l_keywordValInJson); 303 304 printJson(l_resultInJson); 305 } 306 307 /** 308 * @brief API to write keyword's value. 309 * 310 * This API writes keyword's value by requesting DBus service(vpd-manager) who 311 * hosts the 'UpdateKeyword' method to update keyword's value. 312 * 313 * @param[in] i_vpdPath - EEPROM or object path, where keyword is present. 314 * @param[in] i_paramsToWriteData - Data required to update keyword's value. 315 * 316 * @return - Number of bytes written on success, -1 on failure. 317 * 318 * @throw - std::runtime_error, sdbusplus::exception::SdBusError 319 */ 320 inline int writeKeyword(const std::string& i_vpdPath, 321 const types::WriteVpdParams& i_paramsToWriteData) 322 { 323 if (i_vpdPath.empty()) 324 { 325 throw std::runtime_error("Empty path"); 326 } 327 328 int l_rc = constants::FAILURE; 329 auto l_bus = sdbusplus::bus::new_default(); 330 331 auto l_method = l_bus.new_method_call( 332 constants::vpdManagerService, constants::vpdManagerObjectPath, 333 constants::vpdManagerInfName, "UpdateKeyword"); 334 335 l_method.append(i_vpdPath, i_paramsToWriteData); 336 auto l_result = l_bus.call(l_method); 337 338 l_result.read(l_rc); 339 return l_rc; 340 } 341 342 /** 343 * @brief API to write keyword's value on hardware. 344 * 345 * This API writes keyword's value by requesting DBus service(vpd-manager) who 346 * hosts the 'WriteKeywordOnHardware' method to update keyword's value. 347 * 348 * Note: This API updates keyword's value only on the given hardware path, any 349 * backup or redundant EEPROM (if exists) paths won't get updated. 350 * 351 * @param[in] i_eepromPath - EEPROM where keyword is present. 352 * @param[in] i_paramsToWriteData - Data required to update keyword's value. 353 * 354 * @return - Number of bytes written on success, -1 on failure. 355 * 356 * @throw - std::runtime_error, sdbusplus::exception::SdBusError 357 */ 358 inline int 359 writeKeywordOnHardware(const std::string& i_eepromPath, 360 const types::WriteVpdParams& i_paramsToWriteData) 361 { 362 if (i_eepromPath.empty()) 363 { 364 throw std::runtime_error("Empty path"); 365 } 366 367 int l_rc = constants::FAILURE; 368 auto l_bus = sdbusplus::bus::new_default(); 369 370 auto l_method = l_bus.new_method_call( 371 constants::vpdManagerService, constants::vpdManagerObjectPath, 372 constants::vpdManagerInfName, "WriteKeywordOnHardware"); 373 374 l_method.append(i_eepromPath, i_paramsToWriteData); 375 auto l_result = l_bus.call(l_method); 376 377 l_result.read(l_rc); 378 379 return l_rc; 380 } 381 382 /** 383 * @brief API to get data in binary format. 384 * 385 * This API converts given string value into array of binary data. 386 * 387 * @param[in] i_value - Input data. 388 * 389 * @return - Array of binary data on success, throws as exception in case 390 * of any error. 391 * 392 * @throw std::runtime_error, std::out_of_range, std::bad_alloc, 393 * std::invalid_argument 394 */ 395 inline types::BinaryVector convertToBinary(const std::string& i_value) 396 { 397 if (i_value.empty()) 398 { 399 throw std::runtime_error( 400 "Provide a valid hexadecimal input. (Ex. 0x30313233)"); 401 } 402 403 std::vector<uint8_t> l_binaryValue{}; 404 405 if (i_value.substr(0, 2).compare("0x") == constants::STR_CMP_SUCCESS) 406 { 407 if (i_value.length() % 2 != 0) 408 { 409 throw std::runtime_error( 410 "Write option accepts 2 digit hex numbers. (Ex. 0x1 " 411 "should be given as 0x01)."); 412 } 413 414 auto l_value = i_value.substr(2); 415 416 if (l_value.empty()) 417 { 418 throw std::runtime_error( 419 "Provide a valid hexadecimal input. (Ex. 0x30313233)"); 420 } 421 422 if (l_value.find_first_not_of("0123456789abcdefABCDEF") != 423 std::string::npos) 424 { 425 throw std::runtime_error("Provide a valid hexadecimal input."); 426 } 427 428 for (size_t l_pos = 0; l_pos < l_value.length(); l_pos += 2) 429 { 430 uint8_t l_byte = static_cast<uint8_t>( 431 std::stoi(l_value.substr(l_pos, 2), nullptr, 16)); 432 l_binaryValue.push_back(l_byte); 433 } 434 } 435 else 436 { 437 l_binaryValue.assign(i_value.begin(), i_value.end()); 438 } 439 return l_binaryValue; 440 } 441 442 /** 443 * @brief API to parse respective JSON. 444 * 445 * @param[in] i_pathToJson - Path to JSON. 446 * 447 * @return Parsed JSON, throws exception in case of error. 448 * 449 * @throw std::runtime_error 450 */ 451 inline nlohmann::json getParsedJson(const std::string& i_pathToJson) 452 { 453 if (i_pathToJson.empty()) 454 { 455 throw std::runtime_error("Path to JSON is missing"); 456 } 457 458 std::error_code l_ec; 459 if (!std::filesystem::exists(i_pathToJson, l_ec)) 460 { 461 std::string l_message{ 462 "file system call failed for file: " + i_pathToJson}; 463 464 if (l_ec) 465 { 466 l_message += ", error: " + l_ec.message(); 467 } 468 throw std::runtime_error(l_message); 469 } 470 471 if (std::filesystem::is_empty(i_pathToJson, l_ec)) 472 { 473 throw std::runtime_error("Empty file: " + i_pathToJson); 474 } 475 else if (l_ec) 476 { 477 throw std::runtime_error("is_empty file system call failed for file: " + 478 i_pathToJson + ", error: " + l_ec.message()); 479 } 480 481 std::ifstream l_jsonFile(i_pathToJson); 482 if (!l_jsonFile) 483 { 484 throw std::runtime_error("Failed to access Json path: " + i_pathToJson); 485 } 486 487 try 488 { 489 return nlohmann::json::parse(l_jsonFile); 490 } 491 catch (const nlohmann::json::parse_error& l_ex) 492 { 493 throw std::runtime_error("Failed to parse JSON file: " + i_pathToJson); 494 } 495 } 496 497 /** 498 * @brief API to get list of interfaces under a given object path. 499 * 500 * Given a DBus object path, this API returns a map of service -> implemented 501 * interface(s) under that object path. This API calls DBus method GetObject 502 * hosted by ObjectMapper DBus service. 503 * 504 * @param[in] i_objectPath - DBus object path. 505 * @param[in] i_constrainingInterfaces - An array of result set constraining 506 * interfaces. 507 * 508 * @return On success, returns a map of service -> implemented interface(s), 509 * else returns an empty map. The caller of this 510 * API should check for empty map. 511 */ 512 inline types::MapperGetObject GetServiceInterfacesForObject( 513 const std::string& i_objectPath, 514 const std::vector<std::string>& i_constrainingInterfaces) noexcept 515 { 516 types::MapperGetObject l_serviceInfMap; 517 if (i_objectPath.empty()) 518 { 519 // TODO: log only when verbose is enabled 520 std::cerr << "Object path is empty." << std::endl; 521 return l_serviceInfMap; 522 } 523 524 try 525 { 526 auto l_bus = sdbusplus::bus::new_default(); 527 auto l_method = l_bus.new_method_call( 528 constants::objectMapperService, constants::objectMapperObjectPath, 529 constants::objectMapperInfName, "GetObject"); 530 531 l_method.append(i_objectPath, i_constrainingInterfaces); 532 533 auto l_result = l_bus.call(l_method); 534 l_result.read(l_serviceInfMap); 535 } 536 catch (const sdbusplus::exception::SdBusError& l_ex) 537 { 538 // TODO: log only when verbose is enabled 539 std::cerr << std::string(l_ex.what()) << std::endl; 540 } 541 return l_serviceInfMap; 542 } 543 544 /** @brief API to get list of sub tree paths for a given object path 545 * 546 * Given a DBus object path, this API returns a list of object paths under that 547 * object path in the DBus tree. This API calls DBus method GetSubTreePaths 548 * hosted by ObjectMapper DBus service. 549 * 550 * @param[in] i_objectPath - DBus object path. 551 * @param[in] i_constrainingInterfaces - An array of result set constraining 552 * interfaces. 553 * @param[in] i_depth - The maximum subtree depth for which results should be 554 * fetched. For unconstrained fetches use a depth of zero. 555 * 556 * @return On success, returns a std::vector<std::string> of object paths in 557 * Phosphor Inventory Manager DBus service's tree, else returns an empty vector. 558 * The caller of this API should check for empty vector. 559 */ 560 inline std::vector<std::string> GetSubTreePaths( 561 const std::string i_objectPath, const int i_depth = 0, 562 const std::vector<std::string>& i_constrainingInterfaces = {}) noexcept 563 { 564 std::vector<std::string> l_objectPaths; 565 566 try 567 { 568 auto l_bus = sdbusplus::bus::new_default(); 569 auto l_method = l_bus.new_method_call( 570 constants::objectMapperService, constants::objectMapperObjectPath, 571 constants::objectMapperInfName, "GetSubTreePaths"); 572 573 l_method.append(i_objectPath, i_depth, i_constrainingInterfaces); 574 575 auto l_result = l_bus.call(l_method); 576 l_result.read(l_objectPaths); 577 } 578 catch (const sdbusplus::exception::SdBusError& l_ex) 579 { 580 // TODO: log only when verbose is enabled 581 std::cerr << std::string(l_ex.what()) << std::endl; 582 } 583 return l_objectPaths; 584 } 585 586 /** 587 * @brief A class to print data in tabular format 588 * 589 * This class implements methods to print data in a two dimensional tabular 590 * format. All entries in the table must be in string format. 591 * 592 */ 593 class Table 594 { 595 class Column : public types::TableColumnNameSizePair 596 { 597 public: 598 /** 599 * @brief API to get the name of the Column 600 * 601 * @return Name of the Column. 602 */ 603 const std::string& Name() const 604 { 605 return this->first; 606 } 607 608 /** 609 * @brief API to get the width of the Column 610 * 611 * @return Width of the Column. 612 */ 613 std::size_t Width() const 614 { 615 return this->second; 616 } 617 }; 618 619 // Current width of the table 620 std::size_t m_currentWidth; 621 622 // Character to be used as fill character between entries 623 char m_fillCharacter; 624 625 // Separator character to be used between columns 626 char m_separator; 627 628 // Array of columns 629 std::vector<Column> m_columns; 630 631 /** 632 * @brief API to Print Header 633 * 634 * Header line prints the names of the Column headers separated by the 635 * specified separator character and spaced accordingly. 636 * 637 * @throw std::out_of_range, std::length_error, std::bad_alloc 638 */ 639 void PrintHeader() const 640 { 641 for (const auto& l_column : m_columns) 642 { 643 PrintEntry(l_column.Name(), l_column.Width()); 644 } 645 std::cout << m_separator << std::endl; 646 } 647 648 /** 649 * @brief API to Print Horizontal Line 650 * 651 * A horizontal line is a sequence of '*'s. 652 * 653 * @throw std::out_of_range, std::length_error, std::bad_alloc 654 */ 655 void PrintHorizontalLine() const 656 { 657 std::cout << std::string(m_currentWidth, '*') << std::endl; 658 } 659 660 /** 661 * @brief API to print an entry in the table 662 * 663 * An entry is a separator character followed by the text to print. 664 * The text is centre-aligned. 665 * 666 * @param[in] i_text - text to print 667 * @param[in] i_columnWidth - width of the column 668 * 669 * @throw std::out_of_range, std::length_error, std::bad_alloc 670 */ 671 void PrintEntry(const std::string& i_text, std::size_t i_columnWidth) const 672 { 673 const std::size_t l_textLength{i_text.length()}; 674 675 constexpr std::size_t l_minFillChars{3}; 676 const std::size_t l_numFillChars = 677 ((l_textLength >= i_columnWidth ? l_minFillChars 678 : i_columnWidth - l_textLength)) - 679 1; // -1 for the separator character 680 681 const unsigned l_oddFill = l_numFillChars % 2; 682 683 std::cout << m_separator 684 << std::string((l_numFillChars / 2) + l_oddFill, 685 m_fillCharacter) 686 << i_text << std::string(l_numFillChars / 2, m_fillCharacter); 687 } 688 689 public: 690 /** 691 * @brief Table Constructor 692 * 693 * Parameterized constructor for a Table object 694 * 695 */ 696 constexpr explicit Table(const char i_fillCharacter = ' ', 697 const char i_separator = '|') noexcept : 698 m_currentWidth{0}, m_fillCharacter{i_fillCharacter}, 699 m_separator{i_separator} 700 {} 701 702 // deleted methods 703 Table(const Table&) = delete; 704 Table operator=(const Table&) = delete; 705 Table(const Table&&) = delete; 706 Table operator=(const Table&&) = delete; 707 708 ~Table() = default; 709 710 /** 711 * @brief API to add column to Table 712 * 713 * @param[in] i_name - Name of the column. 714 * 715 * @param[in] i_width - Width to allocate for the column. 716 * 717 * @return On success returns 0, otherwise returns -1. 718 */ 719 int AddColumn(const std::string& i_name, std::size_t i_width) 720 { 721 if (i_width < i_name.length()) 722 return constants::FAILURE; 723 m_columns.emplace_back(types::TableColumnNameSizePair(i_name, i_width)); 724 m_currentWidth += i_width; 725 return constants::SUCCESS; 726 } 727 728 /** 729 * @brief API to print the Table to console. 730 * 731 * This API prints the table data to console. 732 * 733 * @param[in] i_tableData - The data to be printed. 734 * 735 * @return On success returns 0, otherwise returns -1. 736 * 737 * @throw std::out_of_range, std::length_error, std::bad_alloc 738 */ 739 int Print(const types::TableInputData& i_tableData) const 740 { 741 PrintHorizontalLine(); 742 PrintHeader(); 743 PrintHorizontalLine(); 744 745 // print the table data 746 for (const auto& l_row : i_tableData) 747 { 748 unsigned l_columnNumber{0}; 749 750 // number of columns in input data is greater than the number of 751 // columns specified in Table 752 if (l_row.size() > m_columns.size()) 753 { 754 return constants::FAILURE; 755 } 756 757 for (const auto& l_entry : l_row) 758 { 759 PrintEntry(l_entry, m_columns[l_columnNumber].Width()); 760 761 ++l_columnNumber; 762 } 763 std::cout << m_separator << std::endl; 764 } 765 PrintHorizontalLine(); 766 return constants::SUCCESS; 767 } 768 }; 769 770 } // namespace utils 771 } // namespace vpd 772