#pragma once #include "constants.hpp" #include "exceptions.hpp" #include "logger.hpp" #include "types.hpp" #include namespace vpd { /** * @brief The namespace defines utlity methods for generic D-Bus operations. */ namespace dbusUtility { /** * @brief An API to get Map of service and interfaces for an object path. * * The API returns a Map of service name and interfaces for a given pair of * object path and interface list. It can be used to determine service name * which implemets a particular object path and interface. * * Note: It will be caller's responsibility to check for empty map returned and * generate appropriate error. * * @param [in] objectPath - Object path under the service. * @param [in] interfaces - Vector of interface(s). * @return - A Map of service name to object to interface(s), if success. * If failed, empty map. */ inline types::MapperGetObject getObjectMap( const std::string& objectPath, const std::vector& interfaces) { types::MapperGetObject getObjectMap; // interface list is optional argument, hence no check required. if (objectPath.empty()) { logging::logMessage("Path value is empty, invalid call to GetObject"); return getObjectMap; } try { auto bus = sdbusplus::bus::new_default(); auto method = bus.new_method_call( "xyz.openbmc_project.ObjectMapper", "/xyz/openbmc_project/object_mapper", "xyz.openbmc_project.ObjectMapper", "GetObject"); method.append(objectPath, interfaces); auto result = bus.call(method); result.read(getObjectMap); } catch (const sdbusplus::exception::SdBusError& e) { // logging::logMessage(e.what()); return getObjectMap; } return getObjectMap; } /** * @brief An API to get property map for an interface. * * This API returns a map of property and its value with respect to a particular * interface. * * Note: It will be caller's responsibility to check for empty map returned and * generate appropriate error. * * @param[in] i_service - Service name. * @param[in] i_objectPath - object path. * @param[in] i_interface - Interface, for the properties to be listed. * * @return - A map of property and value of an interface, if success. * if failed, empty map. */ inline types::PropertyMap getPropertyMap(const std::string& i_service, const std::string& i_objectPath, const std::string& i_interface) { types::PropertyMap l_propertyValueMap; if (i_service.empty() || i_objectPath.empty() || i_interface.empty()) { logging::logMessage("Invalid parameters to get property map"); return l_propertyValueMap; } try { auto l_bus = sdbusplus::bus::new_default(); auto l_method = l_bus.new_method_call(i_service.c_str(), i_objectPath.c_str(), "org.freedesktop.DBus.Properties", "GetAll"); l_method.append(i_interface); auto l_result = l_bus.call(l_method); l_result.read(l_propertyValueMap); } catch (const sdbusplus::exception::SdBusError& l_ex) { logging::logMessage(l_ex.what()); } return l_propertyValueMap; } /** * @brief API to get object subtree from D-bus. * * The API returns the map of object, services and interfaces in the * subtree that implement a certain interface. If no interfaces are provided * then all the objects, services and interfaces under the subtree will * be returned. * * Note: Depth can be 0 and interfaces can be null. * It will be caller's responsibility to check for empty vector returned * and generate appropriate error. * * @param[in] i_objectPath - Path to search for an interface. * @param[in] i_depth - Maximum depth of the tree to search. * @param[in] i_interfaces - List of interfaces to search. * * @return - A map of object and its related services and interfaces, if * success. If failed, empty map. */ inline types::MapperGetSubTree getObjectSubTree( const std::string& i_objectPath, const int& i_depth, const std::vector& i_interfaces) { types::MapperGetSubTree l_subTreeMap; if (i_objectPath.empty()) { logging::logMessage("Object path is empty."); return l_subTreeMap; } try { auto l_bus = sdbusplus::bus::new_default(); auto l_method = l_bus.new_method_call( constants::objectMapperService, constants::objectMapperPath, constants::objectMapperInf, "GetSubTree"); l_method.append(i_objectPath, i_depth, i_interfaces); auto l_result = l_bus.call(l_method); l_result.read(l_subTreeMap); } catch (const sdbusplus::exception::SdBusError& l_ex) { logging::logMessage(l_ex.what()); } return l_subTreeMap; } /** * @brief An API to read property from Dbus. * * The caller of the API needs to validate the validatity and correctness of the * type and value of data returned. The API will just fetch and retun the data * without any data validation. * * Note: It will be caller's responsibility to check for empty value returned * and generate appropriate error if required. * * @param [in] serviceName - Name of the Dbus service. * @param [in] objectPath - Object path under the service. * @param [in] interface - Interface under which property exist. * @param [in] property - Property whose value is to be read. * @return - Value read from Dbus, if success. * If failed, empty variant. */ inline types::DbusVariantType readDbusProperty( const std::string& serviceName, const std::string& objectPath, const std::string& interface, const std::string& property) { types::DbusVariantType propertyValue; // Mandatory fields to make a read dbus call. if (serviceName.empty() || objectPath.empty() || interface.empty() || property.empty()) { logging::logMessage( "One of the parameter to make Dbus read call is empty."); return propertyValue; } try { auto bus = sdbusplus::bus::new_default(); auto method = bus.new_method_call(serviceName.c_str(), objectPath.c_str(), "org.freedesktop.DBus.Properties", "Get"); method.append(interface, property); auto result = bus.call(method); result.read(propertyValue); } catch (const sdbusplus::exception::SdBusError& e) { return propertyValue; } return propertyValue; } /** * @brief An API to write property on Dbus. * * The caller of this API needs to handle exception thrown by this method to * identify any write failure. The API in no other way indicate write failure * to the caller. * * @param [in] serviceName - Name of the Dbus service. * @param [in] objectPath - Object path under the service. * @param [in] interface - Interface under which property exist. * @param [in] property - Property whose value is to be written. * @param [in] propertyValue - The value to be written. * @return True if write on DBus is success, false otherwise. */ inline bool writeDbusProperty( const std::string& serviceName, const std::string& objectPath, const std::string& interface, const std::string& property, const types::DbusVariantType& propertyValue) { try { // Mandatory fields to make a write dbus call. if (serviceName.empty() || objectPath.empty() || interface.empty() || property.empty()) { throw std::runtime_error("Dbus write failed, Parameter empty"); } auto bus = sdbusplus::bus::new_default(); auto method = bus.new_method_call(serviceName.c_str(), objectPath.c_str(), "org.freedesktop.DBus.Properties", "Set"); method.append(interface, property, propertyValue); bus.call(method); return true; } catch (const std::exception& l_ex) { logging::logMessage( "DBus write failed, error: " + std::string(l_ex.what())); return false; } } /** * @brief API to publish data on PIM * * The API calls notify on PIM object to publlish VPD. * * @param[in] objectMap - Object, its interface and data. * @return bool - Status of call to PIM notify. */ inline bool callPIM(types::ObjectMap&& objectMap) { try { for (const auto& l_objectKeyValue : objectMap) { if (l_objectKeyValue.first.str.find(constants::pimPath, 0) != std::string::npos) { auto l_nodeHandle = objectMap.extract(l_objectKeyValue.first); l_nodeHandle.key() = l_nodeHandle.key().str.replace( 0, std::strlen(constants::pimPath), ""); objectMap.insert(std::move(l_nodeHandle)); } } auto bus = sdbusplus::bus::new_default(); auto pimMsg = bus.new_method_call(constants::pimServiceName, constants::pimPath, constants::pimIntf, "Notify"); pimMsg.append(std::move(objectMap)); bus.call(pimMsg); } catch (const sdbusplus::exception::SdBusError& e) { return false; } return true; } /** * @brief API to check if a D-Bus service is running or not. * * Any failure in calling the method "NameHasOwner" implies that the service is * not in a running state. Hence the API returns false in case of any exception * as well. * * @param[in] i_serviceName - D-Bus service name whose status is to be checked. * @return bool - True if the service is running, false otherwise. */ inline bool isServiceRunning(const std::string& i_serviceName) { bool l_retVal = false; try { auto l_bus = sdbusplus::bus::new_default(); auto l_method = l_bus.new_method_call( "org.freedesktop.DBus", "/org/freedesktop/DBus", "org.freedesktop.DBus", "NameHasOwner"); l_method.append(i_serviceName); l_bus.call(l_method).read(l_retVal); } catch (const sdbusplus::exception::SdBusError& l_ex) { logging::logMessage( "Call to check service status failed with exception: " + std::string(l_ex.what())); } return l_retVal; } /** * @brief API to call "GetAttribute" method uner BIOS manager. * * The API reads the given attribuute from BIOS and returns a tuple of both * current as well as pending value for that attribute. * The API return only the current attribute value if found. * API returns an empty variant of type BiosAttributeCurrentValue in case of any * error. * * @param[in] i_attributeName - Attribute to be read. * @return Tuple of PLDM attribute Type, current attribute value and pending * attribute value. */ inline types::BiosAttributeCurrentValue biosGetAttributeMethodCall( const std::string& i_attributeName) noexcept { types::BiosGetAttrRetType l_attributeVal; try { auto l_bus = sdbusplus::bus::new_default(); auto l_method = l_bus.new_method_call( constants::biosConfigMgrService, constants::biosConfigMgrObjPath, constants::biosConfigMgrInterface, "GetAttribute"); l_method.append(i_attributeName); auto l_result = l_bus.call(l_method); l_result.read(std::get<0>(l_attributeVal), std::get<1>(l_attributeVal), std::get<2>(l_attributeVal)); } catch (const sdbusplus::exception::SdBusError& l_ex) { logging::logMessage( "Failed to read BIOS Attribute: " + i_attributeName + " due to error " + std::string(l_ex.what())); // TODO: Log an informational PEL here. } return std::get<1>(l_attributeVal); } /** * @brief API to check if Chassis is powered on. * * This API queries Phosphor Chassis State Manager to know whether * Chassis is powered on. * * @return true if chassis is powered on, false otherwise */ inline bool isChassisPowerOn() { auto powerState = dbusUtility::readDbusProperty( "xyz.openbmc_project.State.Chassis", "/xyz/openbmc_project/state/chassis0", "xyz.openbmc_project.State.Chassis", "CurrentPowerState"); if (auto curPowerState = std::get_if(&powerState)) { if ("xyz.openbmc_project.State.Chassis.PowerState.On" == *curPowerState) { return true; } return false; } /* TODO: Add PEL. Callout: Firmware callout Type: Informational Description: Chassis state can't be determined, defaulting to chassis off. : e.what() */ return false; } /** * @brief API to check if host is in running state. * * This API reads the current host state from D-bus and returns true if the host * is running. * * @return true if host is in running state. false otherwise. */ inline bool isHostRunning() { const auto l_hostState = dbusUtility::readDbusProperty( constants::hostService, constants::hostObjectPath, constants::hostInterface, "CurrentHostState"); if (const auto l_currHostState = std::get_if(&l_hostState)) { if (*l_currHostState == constants::hostRunningState) { return true; } } return false; } /** * @brief API to check if BMC is in ready state. * * This API reads the current state of BMC from D-bus and returns true if BMC is * in ready state. * * @return true if BMC is ready, false otherwise. */ inline bool isBMCReady() { const auto l_bmcState = dbusUtility::readDbusProperty( constants::bmcStateService, constants::bmcZeroStateObject, constants::bmcStateInterface, constants::currentBMCStateProperty); if (const auto l_currBMCState = std::get_if(&l_bmcState)) { if (*l_currBMCState == constants::bmcReadyState) { return true; } } return false; } /** * @brief An API to enable BMC reboot guard * * This API does a D-Bus method call to enable BMC reboot guard. * * @return On success, returns 0, otherwise returns -1. */ inline int EnableRebootGuard() noexcept { int l_rc{constants::FAILURE}; try { auto l_bus = sdbusplus::bus::new_default(); auto l_method = l_bus.new_method_call( constants::systemdService, constants::systemdObjectPath, constants::systemdManagerInterface, "StartUnit"); l_method.append("reboot-guard-enable.service", "replace"); l_bus.call_noreply(l_method); l_rc = constants::SUCCESS; } catch (const sdbusplus::exception::SdBusError& l_ex) { std::string l_errMsg = "D-Bus call to enable BMC reboot guard failed for reason: "; l_errMsg += l_ex.what(); logging::logMessage(l_errMsg); } return l_rc; } /** * @brief An API to disable BMC reboot guard * * This API disables BMC reboot guard. This API has an inbuilt re-try mechanism. * If Disable Reboot Guard fails, this API attempts to Disable Reboot Guard for * 3 more times at an interval of 333ms. * * @return On success, returns 0, otherwise returns -1. */ inline int DisableRebootGuard() noexcept { int l_rc{constants::FAILURE}; // A lambda which executes the DBus call to disable BMC reboot guard. auto l_executeDisableRebootGuard = []() -> int { int l_dBusCallRc{constants::FAILURE}; try { auto l_bus = sdbusplus::bus::new_default(); auto l_method = l_bus.new_method_call( constants::systemdService, constants::systemdObjectPath, constants::systemdManagerInterface, "StartUnit"); l_method.append("reboot-guard-disable.service", "replace"); l_bus.call_noreply(l_method); l_dBusCallRc = constants::SUCCESS; } catch (const sdbusplus::exception::SdBusError& l_ex) {} return l_dBusCallRc; }; if (constants::FAILURE == l_executeDisableRebootGuard()) { std::function l_retryDisableRebootGuard; // A lambda which tries to disable BMC reboot guard for 3 times at an // interval of 333 ms. l_retryDisableRebootGuard = [&]() { constexpr int MAX_RETRIES{3}; static int l_numRetries{0}; if (l_numRetries < MAX_RETRIES) { l_numRetries++; if (constants::FAILURE == l_executeDisableRebootGuard()) { // sleep for 333ms before next retry. This is just a random // value so that 3 re-tries * 333ms takes ~1 second in the // worst case. const std::chrono::milliseconds l_sleepTime{333}; std::this_thread::sleep_for(l_sleepTime); l_retryDisableRebootGuard(); } else { l_numRetries = 0; l_rc = constants::SUCCESS; } } else { // Failed to Disable Reboot Guard even after 3 retries. logging::logMessage("Failed to Disable Reboot Guard after " + std::to_string(MAX_RETRIES) + " re-tries"); l_numRetries = 0; } }; l_retryDisableRebootGuard(); } else { l_rc = constants::SUCCESS; } return l_rc; } /** * @brief API to notify FRU VPD Collection status. * * This API uses PIM's Notify method to update the given FRU VPD collection * status on D-bus. * * @param[in] i_inventoryPath - D-bus inventory path * @param[in] i_fruCollectionStatus - FRU VPD collection status. * * @return true if update succeeds, false otherwise. */ inline bool notifyFRUCollectionStatus(const std::string& i_inventoryPath, const std::string& i_fruCollectionStatus) { types::ObjectMap l_objectMap; types::InterfaceMap l_interfaceMap; types::PropertyMap l_propertyMap; l_propertyMap.emplace("Status", i_fruCollectionStatus); l_interfaceMap.emplace(constants::vpdCollectionInterface, l_propertyMap); l_objectMap.emplace(i_inventoryPath, l_interfaceMap); if (!dbusUtility::callPIM(std::move(l_objectMap))) { return false; } return true; } /** * @brief API to read IM keyword from Dbus. * * @return IM value read from Dbus, Empty in case of any error. */ inline types::BinaryVector getImFromDbus() { const auto& l_retValue = dbusUtility::readDbusProperty( constants::pimServiceName, constants::systemVpdInvPath, constants::vsbpInf, constants::kwdIM); auto l_imValue = std::get_if(&l_retValue); if (!l_imValue || (*l_imValue).size() != constants::VALUE_4) { return types::BinaryVector{}; } return *l_imValue; } /** * @brief API to return prefix of functional firmware image. * * Every functional image belongs to a series which is denoted by the first two * characters of the image name. The API extracts that and returns it to the * caller. * * @return Two character string, empty string in case of any error. */ inline std::string getImagePrefix() { try { types::DbusVariantType l_retVal = readDbusProperty( constants::objectMapperService, constants::functionalImageObjPath, constants::associationInterface, "endpoints"); auto l_listOfFunctionalPath = std::get_if>(&l_retVal); if (!l_listOfFunctionalPath || (*l_listOfFunctionalPath).empty()) { throw DbusException("failed to get functional image path."); } for (const auto& l_imagePath : *l_listOfFunctionalPath) { types::DbusVariantType l_retValPriority = readDbusProperty(constants::imageUpdateService, l_imagePath, constants::imagePrirotyInf, "Priority"); auto l_imagePriority = std::get_if(&l_retValPriority); if (!l_imagePriority) { throw DbusException( "failed to read functional image priority for path [" + l_imagePath + "]"); } // only interested in running image. if (*l_imagePriority != constants::VALUE_0) { continue; } types::DbusVariantType l_retExVer = readDbusProperty( constants::imageUpdateService, l_imagePath, constants::imageExtendedVerInf, "ExtendedVersion"); auto l_imageExtendedVersion = std::get_if(&l_retExVer); if (!l_imageExtendedVersion) { throw DbusException( "Unable to read extended version for the functional image [" + l_imagePath + "]"); } if ((*l_imageExtendedVersion).empty() || (*l_imageExtendedVersion).length() <= constants::VALUE_2) { throw DbusException("Invalid extended version read for path [" + l_imagePath + "]"); } // return first two character from image name. return (*l_imageExtendedVersion) .substr(constants::VALUE_0, constants::VALUE_2); } throw std::runtime_error("No Image found with required priority."); } catch (const std::exception& l_ex) { logging::logMessage(l_ex.what()); return std::string{}; } } /** * @brief API to read DBus present property for the given inventory. * * @param[in] i_invObjPath - Inventory path. * @return Present property value, false in case of any error. */ inline bool isInventoryPresent(const std::string& i_invObjPath) { if (i_invObjPath.empty()) { return false; } const auto& l_retValue = dbusUtility::readDbusProperty(constants::pimServiceName, i_invObjPath, constants::inventoryItemInf, "Present"); auto l_ptrPresence = std::get_if(&l_retValue); if (!l_ptrPresence) { return false; } return (*l_ptrPresence); } /** * @brief API to get list of sub tree paths for a given object path * * Given a DBus object path, this API returns a list of object paths under that * object path in the DBus tree. This API calls DBus method GetSubTreePaths * hosted by ObjectMapper DBus service. * * @param[in] i_objectPath - DBus object path. * @param[in] i_depth - The maximum subtree depth for which results should be * fetched. For unconstrained fetches use a depth of zero. * @param[in] i_constrainingInterfaces - An array of result set constraining * interfaces. * * @return On success, returns a std::vector of object paths, * else returns an empty vector. * * Note: The caller of this API should check for empty vector. */ inline std::vector GetSubTreePaths( const std::string i_objectPath, const int i_depth = 0, const std::vector& i_constrainingInterfaces = {}) noexcept { std::vector l_objectPaths; try { auto l_bus = sdbusplus::bus::new_default(); auto l_method = l_bus.new_method_call( constants::objectMapperService, constants::objectMapperPath, constants::objectMapperInf, "GetSubTreePaths"); l_method.append(i_objectPath, i_depth, i_constrainingInterfaces); auto l_result = l_bus.call(l_method); l_result.read(l_objectPaths); } catch (const sdbusplus::exception::SdBusError& l_ex) { logging::logMessage( "Error while getting GetSubTreePaths for path [" + i_objectPath + "], error: " + std::string(l_ex.what())); } return l_objectPaths; } /** * @brief API to get Dbus service name for given connection identifier. * * @param[in] i_connectionId - Dbus connection ID. * * @return On success, returns the DBus service associated with given connection * ID, empty string otherwise. */ inline std::string getServiceNameFromConnectionId( const std::string& i_connectionId) noexcept { try { if (i_connectionId.empty()) { throw std::runtime_error("Empty connection ID"); } auto l_bus = sdbusplus::bus::new_default(); // get PID corresponding to the connection ID auto l_method = l_bus.new_method_call( "org.freedesktop.DBus", "/org/freedesktop/DBus", "org.freedesktop.DBus", "GetConnectionUnixProcessID"); l_method.append(i_connectionId); auto l_result = l_bus.call(l_method); unsigned l_pid; l_result.read(l_pid); // use PID to get corresponding unit object path l_method = l_bus.new_method_call( "org.freedesktop.systemd1", "/org/freedesktop/systemd1", "org.freedesktop.systemd1.Manager", "GetUnitByPID"); l_method.append(l_pid); l_result = l_bus.call(l_method); sdbusplus::message::object_path l_unitObjectPath; l_result.read(l_unitObjectPath); // use unit object path to get service name l_method = l_bus.new_method_call( "org.freedesktop.systemd1", std::string(l_unitObjectPath).c_str(), "org.freedesktop.DBus.Properties", "Get"); l_method.append("org.freedesktop.systemd1.Unit", "Id"); l_result = l_bus.call(l_method); types::DbusVariantType l_serviceNameVar; l_result.read(l_serviceNameVar); if (auto l_serviceNameStr = std::get_if(&l_serviceNameVar)) { return *l_serviceNameStr; } else { throw std::runtime_error( "Invalid type received while reading service name."); } } catch (const std::exception& l_ex) { logging::logMessage( "Failed to get service name from connection ID: [" + i_connectionId + "]. error: " + std::string(l_ex.what())); } return std::string{}; } } // namespace dbusUtility } // namespace vpd