1## This file is a template. The comment below is emitted 2## into the rendered file; feel free to edit this file. 3// WARNING: Generated header. Do not edit! 4<% 5import re 6from collections import defaultdict 7from sdbusplus.namedelement import NamedElement 8objects = settingsDict.keys() 9sdbusplus_includes = [] 10props = defaultdict(list) 11validators = defaultdict(tuple) 12 13def get_setting_sdbusplus_type(setting_intf): 14 setting = "sdbusplus::" + setting_intf.replace('.', '::') 15 i = setting.rfind('::') 16 setting = setting[:i] + '::server::' + setting[i+2:] 17 return setting 18 19def get_setting_type(path): 20 path = path[1:] 21 path = path.replace('/', '::') 22 return path 23 24def get_default_value(object, interface, prop): 25 default_value = None 26 for item in settingsDict[object]: 27 if item['Interface'] == interface: 28 default_value = item['Properties'][prop]['Default'] 29 break 30 31 if isinstance(default_value, str) and not \ 32 default_value.startswith('"') and '::' in default_value: 33 ns = get_setting_sdbusplus_type(interface) 34 i = ns.rfind('::') 35 default_value = "{}::{}".format(ns[:i], default_value) 36 return default_value 37 38 # Handle default values for properties of type array[enum]. 39 if isinstance(default_value, list): 40 ns = get_setting_sdbusplus_type(interface) 41 i = ns.rfind('::') 42 elems = [] 43 for val in default_value: 44 if isinstance(val, str) and not val.startswith('"') and '::' in val: 45 elems.append("{}::{}".format(ns[:i], val)) 46 default_value = '{' + ', '.join(elems) + '}' 47 return default_value 48 49 return default_value 50%>\ 51#pragma once 52 53% for object in objects: 54 % for item in settingsDict[object]: 55<% 56 include = item['Interface'] 57 include = include.replace('.', '/') 58 include = include + "/server.hpp" 59 sdbusplus_includes.append(include) 60%>\ 61 % endfor 62% endfor 63#include <cereal/archives/json.hpp> 64#include <cereal/types/vector.hpp> 65#include <fstream> 66#include <utility> 67#include <filesystem> 68#include <regex> 69#include <phosphor-logging/elog.hpp> 70#include <phosphor-logging/elog-errors.hpp> 71#include <phosphor-logging/lg2.hpp> 72#include <xyz/openbmc_project/Common/error.hpp> 73 74/* The DBus busname to own */ 75#define SETTINGS_BUSNAME "xyz.openbmc_project.Settings" 76/* Path of directory housing persisted settings */ 77#define SETTINGS_PERSIST_PATH "/var/lib/phosphor-settings-manager/settings" 78 79/* Class version to register with Cereal */ 80static constexpr size_t CLASS_VERSION = 2; 81static constexpr size_t CLASS_VERSION_WITH_NVP = 2; 82 83% for i in set(sdbusplus_includes): 84#include "${i}" 85% endfor 86 87namespace phosphor 88{ 89namespace settings 90{ 91 92namespace fs = std::filesystem; 93 94namespace persistent 95{ 96 97// A setting d-bus object /foo/bar/baz is persisted in the filesystem with the 98// same path. This eases re-construction of settings objects when we restore 99// from the filesystem. This can be a problem though when you have two objects 100// such as - /foo/bar and /foo/bar/baz. This is because 'bar' will be treated as 101// a file in the first case, and a subdir in the second. To solve this, suffix 102// files with a trailing __. The __ is a safe character sequence to use, because 103// we won't have d-bus object paths ending with this. 104// With this the objects would be persisted as - /foo/bar__ and /foo/bar/baz__. 105constexpr auto fileSuffix = "__"; 106 107} 108 109static fs::path getFilePath(const fs::path& objectPath) 110{ 111 fs::path p(SETTINGS_PERSIST_PATH); 112 p /= objectPath.relative_path(); 113 p += persistent::fileSuffix; 114 return p; 115} 116 117% for object in objects: 118<% 119 ns = object.split('/') 120 ns.pop(0) 121%>\ 122% for n in ns: 123namespace ${n} 124{ 125% endfor 126<% 127 interfaces = [] 128 aliases = [] 129 for item in settingsDict[object]: 130 interfaces.append(item['Interface']) 131 for name, meta in item['Properties'].items(): 132 if 'Validation' in meta: 133 dict = meta['Validation'] 134 if dict['Type'] == "range": 135 validators[name] = (dict['Type'], dict['Validator'], dict['Unit']) 136 else: 137 validators[name] = (dict['Type'], dict['Validator']) 138%> 139% for index, intf in enumerate(interfaces): 140using Iface${index} = ${get_setting_sdbusplus_type(intf)}; 141<% aliases.append("Iface" + str(index)) %>\ 142% endfor 143<% 144 parent = "sdbusplus::server::object_t" + "<" + ", ".join(aliases) + ">" 145%>\ 146using Parent = ${parent}; 147 148class Impl : public Parent 149{ 150 public: 151 Impl(sdbusplus::bus_t& bus, const char* path): 152 Parent(bus, path, Parent::action::defer_emit), 153 path(path) 154 { 155 } 156 virtual ~Impl() = default; 157 158 void setInitialVersion(std::uint32_t v) 159 { 160 initialVersion = v; 161 } 162 163 std::uint32_t getInitialVersion() const 164 { 165 return initialVersion; 166 } 167 168 bool deserialize() 169 { 170 auto p = getFilePath(path); 171 if (fs::exists(p)) 172 { 173 std::ifstream is(p.c_str(), std::ios::in); 174 cereal::JSONInputArchive iarchive(is); 175 iarchive(*this); 176 return true; 177 } 178 return false; 179 } 180 181 void serialize() 182 { 183 auto p = getFilePath(path); 184 if (!fs::exists(p.parent_path())) 185 { 186 fs::create_directories(p.parent_path()); 187 } 188 std::ofstream os(p.c_str(), std::ios::binary); 189 cereal::JSONOutputArchive oarchive(os); 190 oarchive(*this); 191 } 192 193 void removeFile() const 194 { 195 std::error_code ec; 196 fs::remove(getFilePath(path), ec); 197 } 198 199% for index, item in enumerate(settingsDict[object]): 200 % for propName, metaDict in item['Properties'].items(): 201<% t = NamedElement(name=propName).camelCase %>\ 202<% fname = "validate" + propName %>\ 203 decltype(std::declval<Iface${index}>().${t}()) ${t}(decltype(std::declval<Iface${index}>().${t}()) value) override 204 { 205 auto result = Iface${index}::${t}(); 206 if (value != result) 207 { 208 % if propName in validators: 209 if (!${fname}(value)) 210 { 211 namespace error = 212 sdbusplus::xyz::openbmc_project::Common::Error; 213 namespace metadata = 214 phosphor::logging::xyz::openbmc_project::Common; 215 phosphor::logging::report<error::InvalidArgument>( 216 metadata::InvalidArgument::ARGUMENT_NAME("${t}"), 217 % if validators[propName][0] != "regex": 218 metadata::InvalidArgument::ARGUMENT_VALUE(std::to_string(value).c_str())); 219 % else: 220 metadata::InvalidArgument::ARGUMENT_VALUE(value.c_str())); 221 % endif 222 return result; 223 } 224 % endif 225 result = Iface${index}::${t}(value); 226 serialize(); 227 } 228 return result; 229 } 230 using Iface${index}::${t}; 231 232 % endfor 233% endfor 234 private: 235 fs::path path; 236 std::uint32_t initialVersion = 0; 237% for index, item in enumerate(settingsDict[object]): 238 % for propName, metaDict in item['Properties'].items(): 239<% t = NamedElement(name=propName).camelCase %>\ 240<% fname = "validate" + propName %>\ 241 % if propName in validators: 242 243 bool ${fname}(decltype(std::declval<Iface${index}>().${t}()) value) 244 { 245 bool matched = false; 246 % if (validators[propName][0] == 'regex'): 247 std::regex regexToCheck("${validators[propName][1]}"); 248 matched = std::regex_search(value, regexToCheck); 249 if (!matched) 250 { 251 lg2::error("Input parameter for ${propName} is invalid. " 252 "Input '{VALUE}' not in the format of this regex: " 253 "${validators[propName][1]}", "VALUE", value); 254 } 255 % elif (validators[propName][0] == 'range'): 256<% lowhigh = re.split('\.\.', validators[propName][1]) %>\ 257 % if lowhigh[0] == '0': 258 if (value <= ${lowhigh[1]}) 259 % else: 260 if ((value <= ${lowhigh[1]}) && (value >= ${lowhigh[0]})) 261 % endif 262 { 263 matched = true; 264 } 265 else 266 { 267 lg2::error("Input parameter for ${propName} is invalid. " 268 "Input '{VALUE}' with unit '${validators[propName][2]}' " 269 "is not in range ${validators[propName][1]}", 270 "VALUE", std::to_string(value)); 271 } 272 % else: 273 <% assert("Unknown validation type: propName") %>\ 274 % endif 275 return matched; 276 } 277 % endif 278 % endfor 279% endfor 280}; 281 282template<class Archive> 283void save(Archive& a, 284 const Impl& setting, 285 [[maybe_unused]] const std::uint32_t version) 286{ 287<% 288props = [] 289for index, item in enumerate(settingsDict[object]): 290 props.extend(item['Properties'].keys()) 291%>\ 292## Since the iface isn't saved, property names need to be unique on 293## the object path. This could be supported by providing unique 294## field names to make_nvp() if ever necessary. 295% if len(set(props)) != len(props): 296#error Duplicate property names on object path ${object} 297%endif 298<% 299args = [] 300for prop in props: 301 t = "setting." + NamedElement(name=prop).camelCase + "()" 302 args.append(f"cereal::make_nvp(\"{prop}\", {t})") 303args = ", ".join(args) 304%>\ 305 a(${args}); 306} 307 308template<class Archive> 309void load(Archive& a, 310 Impl& setting, 311 const std::uint32_t version) 312{ 313 setting.setInitialVersion(version); 314<% 315props = [] 316for index, item in enumerate(settingsDict[object]): 317 for prop in item['Properties'].keys(): 318 t = "setting." + NamedElement(name=prop).camelCase + "()" 319 props.append({'prop' : prop, 'iface': item['Interface'], 'func': t}) 320%>\ 321% for p in props: 322 decltype(${p['func']}) ${p['prop']}{}; 323% endfor 324<% propList = ', '.join([p['prop'] for p in props]) %> 325% if propList: 326 if (version < CLASS_VERSION_WITH_NVP) 327 { 328 a(${propList}); 329 } 330 else 331 { 332 % for p in props: 333 try 334 { 335 a(CEREAL_NVP(${p['prop']})); 336 } 337 catch (const cereal::Exception& e) 338 { 339 lg2::info("Could not restore property ${p['prop']} on ${object}, setting to default value"); 340 ${p['prop']} = ${get_default_value(object, p['iface'], p['prop'])}; 341 } 342 % endfor 343 } 344% endif 345<% props = [] %> 346% for index, item in enumerate(settingsDict[object]): 347 % for prop, metaDict in item['Properties'].items(): 348<% 349 t = "setting." + NamedElement(name=prop).camelCase + "(" + prop + ")" 350%>\ 351 ${t}; 352 % endfor 353% endfor 354} 355 356% for n in reversed(ns): 357} // namespace ${n} 358% endfor 359 360% endfor 361 362/** @class Manager 363 * 364 * @brief Compose settings objects and put them on the bus. 365 */ 366class Manager 367{ 368 public: 369 Manager() = delete; 370 Manager(const Manager&) = delete; 371 Manager& operator=(const Manager&) = delete; 372 Manager(Manager&&) = delete; 373 Manager& operator=(Manager&&) = delete; 374 virtual ~Manager() = default; 375 376 /** @brief Constructor to put settings objects on to the bus. 377 * @param[in] bus - Bus to attach to. 378 */ 379 explicit Manager(sdbusplus::bus_t& bus) : 380 settings( 381 std::make_tuple( 382% for index, path in enumerate(objects): 383<% type = get_setting_type(path) + "::Impl" %>\ 384 std::make_unique<${type}>( 385 bus, 386 % if index < len(settingsDict) - 1: 387 "${path}"), 388 % else: 389 "${path}") 390 % endif 391% endfor 392 ) 393 ) 394 { 395% for index, path in enumerate(objects): 396 auto initSetting${index} = [&]() 397 { 398 % for item in settingsDict[path]: 399 % for propName, metaDict in item['Properties'].items(): 400<% p = NamedElement(name=propName).camelCase %>\ 401<% defaultValue = get_default_value(path, item['Interface'], propName) %>\ 402 std::get<${index}>(settings)-> 403 ${get_setting_sdbusplus_type(item['Interface'])}::${p}(${defaultValue}); 404 % endfor 405% endfor 406 }; 407 408 try 409 { 410 if (std::get<${index}>(settings)->deserialize()) 411 { 412 /* Update the archive to use name/value pairs if it isn't. */ 413 if (std::get<${index}>(settings)->getInitialVersion() < CLASS_VERSION_WITH_NVP) 414 { 415 std::get<${index}>(settings)->serialize(); 416 } 417 } 418 else 419 { 420 initSetting${index}(); 421 } 422 } 423 catch (const cereal::Exception& e) 424 { 425 lg2::error("Cereal exception on ${path}: {ERROR}", "ERROR", e); 426 std::get<${index}>(settings)->removeFile(); 427 initSetting${index}(); 428 } 429 std::get<${index}>(settings)->emit_object_added(); 430 431% endfor 432 } 433 434 private: 435 /* @brief Composition of settings objects. */ 436 std::tuple< 437% for index, path in enumerate(objects): 438<% type = get_setting_type(path) + "::Impl" %>\ 439 % if index < len(settingsDict) - 1: 440 std::unique_ptr<${type}>, 441 % else: 442 std::unique_ptr<${type}>> settings; 443 % endif 444% endfor 445}; 446 447} // namespace settings 448} // namespace phosphor 449 450// Now register the class version with Cereal 451% for object in objects: 452<% 453 classname = "phosphor::settings" 454 ns = object.split('/') 455 ns.pop(0) 456%>\ 457% for n in ns: 458<% 459 classname += "::" + n 460%>\ 461% endfor 462CEREAL_CLASS_VERSION(${classname + "::Impl"}, CLASS_VERSION); 463% endfor 464