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