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