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