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