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