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