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