| ## This file is a template. The comment below is emitted |
| ## into the rendered file; feel free to edit this file. |
| // WARNING: Generated header. Do not edit! |
| <% |
| import re |
| from collections import defaultdict |
| from sdbusplus.namedelement import NamedElement |
| objects = settingsDict.keys() |
| sdbusplus_includes = [] |
| props = defaultdict(list) |
| validators = defaultdict(tuple) |
| |
| def get_setting_sdbusplus_type(setting_intf): |
| setting = "sdbusplus::" + setting_intf.replace('.', '::') |
| i = setting.rfind('::') |
| setting = setting[:i] + '::server::' + setting[i+2:] |
| return setting |
| |
| def get_setting_type(path): |
| path = path[1:] |
| path = path.replace('/', '::') |
| return path |
| |
| def get_default_value(object, interface, prop): |
| default_value = None |
| for item in settingsDict[object]: |
| if item['Interface'] == interface: |
| default_value = item['Properties'][prop]['Default'] |
| break |
| |
| if isinstance(default_value, str) and not \ |
| default_value.startswith('"') and '::' in default_value: |
| ns = get_setting_sdbusplus_type(interface) |
| i = ns.rfind('::') |
| default_value = "{}::{}".format(ns[:i], default_value) |
| |
| return default_value |
| %>\ |
| #pragma once |
| |
| % for object in objects: |
| % for item in settingsDict[object]: |
| <% |
| include = item['Interface'] |
| include = include.replace('.', '/') |
| include = include + "/server.hpp" |
| sdbusplus_includes.append(include) |
| %>\ |
| % endfor |
| % endfor |
| #include <cereal/archives/json.hpp> |
| #include <cereal/types/vector.hpp> |
| #include <fstream> |
| #include <utility> |
| #include <filesystem> |
| #include <regex> |
| #include <phosphor-logging/elog.hpp> |
| #include <phosphor-logging/elog-errors.hpp> |
| #include <phosphor-logging/lg2.hpp> |
| #include <xyz/openbmc_project/Common/error.hpp> |
| |
| /* The DBus busname to own */ |
| #define SETTINGS_BUSNAME "xyz.openbmc_project.Settings" |
| /* Path of directory housing persisted settings */ |
| #define SETTINGS_PERSIST_PATH "/var/lib/phosphor-settings-manager/settings" |
| |
| /* Class version to register with Cereal */ |
| static constexpr size_t CLASS_VERSION = 2; |
| static constexpr size_t CLASS_VERSION_WITH_NVP = 2; |
| |
| % for i in set(sdbusplus_includes): |
| #include "${i}" |
| % endfor |
| |
| namespace phosphor |
| { |
| namespace settings |
| { |
| |
| namespace fs = std::filesystem; |
| |
| namespace persistent |
| { |
| |
| // A setting d-bus object /foo/bar/baz is persisted in the filesystem with the |
| // same path. This eases re-construction of settings objects when we restore |
| // from the filesystem. This can be a problem though when you have two objects |
| // such as - /foo/bar and /foo/bar/baz. This is because 'bar' will be treated as |
| // a file in the first case, and a subdir in the second. To solve this, suffix |
| // files with a trailing __. The __ is a safe character sequence to use, because |
| // we won't have d-bus object paths ending with this. |
| // With this the objects would be persisted as - /foo/bar__ and /foo/bar/baz__. |
| constexpr auto fileSuffix = "__"; |
| |
| } |
| |
| static fs::path getFilePath(const fs::path& objectPath) |
| { |
| fs::path p(SETTINGS_PERSIST_PATH); |
| p /= objectPath.relative_path(); |
| p += persistent::fileSuffix; |
| return p; |
| } |
| |
| % for object in objects: |
| <% |
| ns = object.split('/') |
| ns.pop(0) |
| %>\ |
| % for n in ns: |
| namespace ${n} |
| { |
| % endfor |
| <% |
| interfaces = [] |
| aliases = [] |
| for item in settingsDict[object]: |
| interfaces.append(item['Interface']) |
| for name, meta in item['Properties'].items(): |
| if 'Validation' in meta: |
| dict = meta['Validation'] |
| if dict['Type'] == "range": |
| validators[name] = (dict['Type'], dict['Validator'], dict['Unit']) |
| else: |
| validators[name] = (dict['Type'], dict['Validator']) |
| %> |
| % for index, intf in enumerate(interfaces): |
| using Iface${index} = ${get_setting_sdbusplus_type(intf)}; |
| <% aliases.append("Iface" + str(index)) %>\ |
| % endfor |
| <% |
| parent = "sdbusplus::server::object_t" + "<" + ", ".join(aliases) + ">" |
| %>\ |
| using Parent = ${parent}; |
| |
| class Impl : public Parent |
| { |
| public: |
| Impl(sdbusplus::bus_t& bus, const char* path): |
| Parent(bus, path, Parent::action::defer_emit), |
| path(path) |
| { |
| } |
| virtual ~Impl() = default; |
| |
| void setInitialVersion(std::uint32_t v) |
| { |
| initialVersion = v; |
| } |
| |
| std::uint32_t getInitialVersion() const |
| { |
| return initialVersion; |
| } |
| |
| bool deserialize() |
| { |
| auto p = getFilePath(path); |
| if (fs::exists(p)) |
| { |
| std::ifstream is(p.c_str(), std::ios::in); |
| cereal::JSONInputArchive iarchive(is); |
| iarchive(*this); |
| return true; |
| } |
| return false; |
| } |
| |
| void serialize() |
| { |
| auto p = getFilePath(path); |
| if (!fs::exists(p.parent_path())) |
| { |
| fs::create_directories(p.parent_path()); |
| } |
| std::ofstream os(p.c_str(), std::ios::binary); |
| cereal::JSONOutputArchive oarchive(os); |
| oarchive(*this); |
| } |
| |
| void removeFile() const |
| { |
| std::error_code ec; |
| fs::remove(getFilePath(path), ec); |
| } |
| |
| % for index, item in enumerate(settingsDict[object]): |
| % for propName, metaDict in item['Properties'].items(): |
| <% t = NamedElement(name=propName).camelCase %>\ |
| <% fname = "validate" + propName %>\ |
| decltype(std::declval<Iface${index}>().${t}()) ${t}(decltype(std::declval<Iface${index}>().${t}()) value) override |
| { |
| auto result = Iface${index}::${t}(); |
| if (value != result) |
| { |
| % if propName in validators: |
| if (!${fname}(value)) |
| { |
| namespace error = |
| sdbusplus::xyz::openbmc_project::Common::Error; |
| namespace metadata = |
| phosphor::logging::xyz::openbmc_project::Common; |
| phosphor::logging::report<error::InvalidArgument>( |
| metadata::InvalidArgument::ARGUMENT_NAME("${t}"), |
| % if validators[propName][0] != "regex": |
| metadata::InvalidArgument::ARGUMENT_VALUE(std::to_string(value).c_str())); |
| % else: |
| metadata::InvalidArgument::ARGUMENT_VALUE(value.c_str())); |
| % endif |
| return result; |
| } |
| % endif |
| result = Iface${index}::${t}(value); |
| serialize(); |
| } |
| return result; |
| } |
| using Iface${index}::${t}; |
| |
| % endfor |
| % endfor |
| private: |
| fs::path path; |
| std::uint32_t initialVersion = 0; |
| % for index, item in enumerate(settingsDict[object]): |
| % for propName, metaDict in item['Properties'].items(): |
| <% t = NamedElement(name=propName).camelCase %>\ |
| <% fname = "validate" + propName %>\ |
| % if propName in validators: |
| |
| bool ${fname}(decltype(std::declval<Iface${index}>().${t}()) value) |
| { |
| bool matched = false; |
| % if (validators[propName][0] == 'regex'): |
| std::regex regexToCheck("${validators[propName][1]}"); |
| matched = std::regex_search(value, regexToCheck); |
| if (!matched) |
| { |
| lg2::error("Input parameter for ${propName} is invalid. " |
| "Input '{VALUE}' not in the format of this regex: " |
| "${validators[propName][1]}", "VALUE", value); |
| } |
| % elif (validators[propName][0] == 'range'): |
| <% lowhigh = re.split('\.\.', validators[propName][1]) %>\ |
| % if lowhigh[0] == '0': |
| if (value <= ${lowhigh[1]}) |
| % else: |
| if ((value <= ${lowhigh[1]}) && (value >= ${lowhigh[0]})) |
| % endif |
| { |
| matched = true; |
| } |
| else |
| { |
| lg2::error("Input parameter for ${propName} is invalid. " |
| "Input '{VALUE}' with unit '${validators[propName][2]}' " |
| "is not in range ${validators[propName][1]}", |
| "VALUE", std::to_string(value)); |
| } |
| % else: |
| <% assert("Unknown validation type: propName") %>\ |
| % endif |
| return matched; |
| } |
| % endif |
| % endfor |
| % endfor |
| }; |
| |
| template<class Archive> |
| void save(Archive& a, |
| const Impl& setting, |
| [[maybe_unused]] const std::uint32_t version) |
| { |
| <% |
| props = [] |
| for index, item in enumerate(settingsDict[object]): |
| props.extend(item['Properties'].keys()) |
| %>\ |
| ## Since the iface isn't saved, property names need to be unique on |
| ## the object path. This could be supported by providing unique |
| ## field names to make_nvp() if ever necessary. |
| % if len(set(props)) != len(props): |
| #error Duplicate property names on object path ${object} |
| %endif |
| <% |
| args = [] |
| for prop in props: |
| t = "setting." + NamedElement(name=prop).camelCase + "()" |
| args.append(f"cereal::make_nvp(\"{prop}\", {t})") |
| args = ", ".join(args) |
| %>\ |
| a(${args}); |
| } |
| |
| template<class Archive> |
| void load(Archive& a, |
| Impl& setting, |
| const std::uint32_t version) |
| { |
| setting.setInitialVersion(version); |
| <% |
| props = [] |
| for index, item in enumerate(settingsDict[object]): |
| for prop in item['Properties'].keys(): |
| t = "setting." + NamedElement(name=prop).camelCase + "()" |
| props.append({'prop' : prop, 'iface': item['Interface'], 'func': t}) |
| %>\ |
| % for p in props: |
| decltype(${p['func']}) ${p['prop']}{}; |
| % endfor |
| <% propList = ', '.join([p['prop'] for p in props]) %> |
| % if propList: |
| if (version < CLASS_VERSION_WITH_NVP) |
| { |
| a(${propList}); |
| } |
| else |
| { |
| % for p in props: |
| try |
| { |
| a(CEREAL_NVP(${p['prop']})); |
| } |
| catch (const cereal::Exception& e) |
| { |
| lg2::info("Could not restore property ${p['prop']} on ${object}, setting to default value"); |
| ${p['prop']} = ${get_default_value(object, p['iface'], p['prop'])}; |
| } |
| % endfor |
| } |
| % endif |
| <% props = [] %> |
| % for index, item in enumerate(settingsDict[object]): |
| % for prop, metaDict in item['Properties'].items(): |
| <% |
| t = "setting." + NamedElement(name=prop).camelCase + "(" + prop + ")" |
| %>\ |
| ${t}; |
| % endfor |
| % endfor |
| } |
| |
| % for n in reversed(ns): |
| } // namespace ${n} |
| % endfor |
| |
| % endfor |
| |
| /** @class Manager |
| * |
| * @brief Compose settings objects and put them on the bus. |
| */ |
| class Manager |
| { |
| public: |
| Manager() = delete; |
| Manager(const Manager&) = delete; |
| Manager& operator=(const Manager&) = delete; |
| Manager(Manager&&) = delete; |
| Manager& operator=(Manager&&) = delete; |
| virtual ~Manager() = default; |
| |
| /** @brief Constructor to put settings objects on to the bus. |
| * @param[in] bus - Bus to attach to. |
| */ |
| explicit Manager(sdbusplus::bus_t& bus) : |
| settings( |
| std::make_tuple( |
| % for index, path in enumerate(objects): |
| <% type = get_setting_type(path) + "::Impl" %>\ |
| std::make_unique<${type}>( |
| bus, |
| % if index < len(settingsDict) - 1: |
| "${path}"), |
| % else: |
| "${path}") |
| % endif |
| % endfor |
| ) |
| ) |
| { |
| % for index, path in enumerate(objects): |
| auto initSetting${index} = [&]() |
| { |
| % for item in settingsDict[path]: |
| % for propName, metaDict in item['Properties'].items(): |
| <% p = NamedElement(name=propName).camelCase %>\ |
| <% defaultValue = get_default_value(path, item['Interface'], propName) %>\ |
| std::get<${index}>(settings)-> |
| ${get_setting_sdbusplus_type(item['Interface'])}::${p}(${defaultValue}); |
| % endfor |
| % endfor |
| }; |
| |
| try |
| { |
| if (std::get<${index}>(settings)->deserialize()) |
| { |
| /* Update the archive to use name/value pairs if it isn't. */ |
| if (std::get<${index}>(settings)->getInitialVersion() < CLASS_VERSION_WITH_NVP) |
| { |
| std::get<${index}>(settings)->serialize(); |
| } |
| } |
| else |
| { |
| initSetting${index}(); |
| } |
| } |
| catch (const cereal::Exception& e) |
| { |
| lg2::error("Cereal exception on ${path}: {ERROR}", "ERROR", e); |
| std::get<${index}>(settings)->removeFile(); |
| initSetting${index}(); |
| } |
| std::get<${index}>(settings)->emit_object_added(); |
| |
| % endfor |
| } |
| |
| private: |
| /* @brief Composition of settings objects. */ |
| std::tuple< |
| % for index, path in enumerate(objects): |
| <% type = get_setting_type(path) + "::Impl" %>\ |
| % if index < len(settingsDict) - 1: |
| std::unique_ptr<${type}>, |
| % else: |
| std::unique_ptr<${type}>> settings; |
| % endif |
| % endfor |
| }; |
| |
| } // namespace settings |
| } // namespace phosphor |
| |
| // Now register the class version with Cereal |
| % for object in objects: |
| <% |
| classname = "phosphor::settings" |
| ns = object.split('/') |
| ns.pop(0) |
| %>\ |
| % for n in ns: |
| <% |
| classname += "::" + n |
| %>\ |
| % endfor |
| CEREAL_CLASS_VERSION(${classname + "::Impl"}, CLASS_VERSION); |
| % endfor |