xref: /freebsd/contrib/kyua/engine/config.cpp (revision 257e70f1d5ee61037c8c59b116538d3b6b1427a2)
1 // Copyright 2010 The Kyua Authors.
2 // All rights reserved.
3 //
4 // Redistribution and use in source and binary forms, with or without
5 // modification, are permitted provided that the following conditions are
6 // met:
7 //
8 // * Redistributions of source code must retain the above copyright
9 //   notice, this list of conditions and the following disclaimer.
10 // * Redistributions in binary form must reproduce the above copyright
11 //   notice, this list of conditions and the following disclaimer in the
12 //   documentation and/or other materials provided with the distribution.
13 // * Neither the name of Google Inc. nor the names of its contributors
14 //   may be used to endorse or promote products derived from this software
15 //   without specific prior written permission.
16 //
17 // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
18 // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
19 // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
20 // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
21 // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
22 // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
23 // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
24 // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
25 // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
26 // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
27 // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28 
29 #include "engine/config.hpp"
30 
31 #if defined(HAVE_CONFIG_H)
32 #   include "config.h"
33 #endif
34 
35 #include <stdexcept>
36 
37 #include "engine/exceptions.hpp"
38 #include "engine/execenv/execenv.hpp"
39 #include "utils/config/exceptions.hpp"
40 #include "utils/config/parser.hpp"
41 #include "utils/config/tree.ipp"
42 #include "utils/passwd.hpp"
43 #include "utils/text/exceptions.hpp"
44 #include "utils/text/operations.ipp"
45 
46 namespace config = utils::config;
47 namespace execenv = engine::execenv;
48 namespace fs = utils::fs;
49 namespace passwd = utils::passwd;
50 namespace text = utils::text;
51 
52 
53 namespace {
54 
55 
56 /// Defines the schema of a configuration tree.
57 ///
58 /// \param [in,out] tree The tree to populate.  The tree should be empty on
59 ///     entry to prevent collisions with the keys defined in here.
60 static void
init_tree(config::tree & tree)61 init_tree(config::tree& tree)
62 {
63     tree.define< config::string_node >("architecture");
64     tree.define< config::strings_set_node >("execenvs");
65     tree.define< config::positive_int_node >("parallelism");
66     tree.define< config::string_node >("platform");
67     tree.define< engine::user_node >("unprivileged_user");
68     tree.define_dynamic("test_suites");
69 }
70 
71 
72 /// Fills in a configuration tree with default values.
73 ///
74 /// \param [in,out] tree The tree to populate.  init_tree() must have been
75 ///     called on it beforehand.
76 static void
set_defaults(config::tree & tree)77 set_defaults(config::tree& tree)
78 {
79     tree.set< config::string_node >("architecture", KYUA_ARCHITECTURE);
80 
81     std::set< std::string > supported;
82     for (auto em : execenv::execenvs())
83         if (em->is_supported())
84             supported.insert(em->name());
85     supported.insert(execenv::default_execenv_name);
86     tree.set< config::strings_set_node >("execenvs", supported);
87 
88     // TODO(jmmv): Automatically derive this from the number of CPUs in the
89     // machine and forcibly set to a value greater than 1.  Still testing
90     // the new parallel implementation as of 2015-02-27 though.
91     tree.set< config::positive_int_node >("parallelism", 1);
92     tree.set< config::string_node >("platform", KYUA_PLATFORM);
93 }
94 
95 
96 /// Configuration parser specialization for Kyua configuration files.
97 class config_parser : public config::parser {
98     /// Initializes the configuration tree.
99     ///
100     /// This is a callback executed when the configuration script invokes the
101     /// syntax() method.  We populate the configuration tree from here with the
102     /// schema version requested by the file.
103     ///
104     /// \param [in,out] tree The tree to populate.
105     /// \param syntax_version The version of the file format as specified in the
106     ///     configuration file.
107     ///
108     /// \throw config::syntax_error If the syntax_format/syntax_version
109     /// combination is not supported.
110     void
setup(config::tree & tree,const int syntax_version)111     setup(config::tree& tree, const int syntax_version)
112     {
113         if (syntax_version < 1 || syntax_version > 2)
114             throw config::syntax_error(F("Unsupported config version %s") %
115                                        syntax_version);
116 
117         init_tree(tree);
118         set_defaults(tree);
119     }
120 
121 public:
122     /// Initializes the parser.
123     ///
124     /// \param [out] tree_ The tree in which the results of the parsing will be
125     ///     stored when parse() is called.  Should be empty on entry.  Because
126     ///     we grab a reference to this object, the tree must remain valid for
127     ///     the existence of the parser object.
config_parser(config::tree & tree_)128     explicit config_parser(config::tree& tree_) :
129         config::parser(tree_)
130     {
131     }
132 };
133 
134 
135 }  // anonymous namespace
136 
137 
138 /// Copies the node.
139 ///
140 /// \return A dynamically-allocated node.
141 config::detail::base_node*
deep_copy(void) const142 engine::user_node::deep_copy(void) const
143 {
144     std::auto_ptr< user_node > new_node(new user_node());
145     new_node->_value = _value;
146     return new_node.release();
147 }
148 
149 
150 /// Pushes the node's value onto the Lua stack.
151 ///
152 /// \param state The Lua state onto which to push the value.
153 void
push_lua(lutok::state & state) const154 engine::user_node::push_lua(lutok::state& state) const
155 {
156     state.push_string(value().name);
157 }
158 
159 
160 /// Sets the value of the node from an entry in the Lua stack.
161 ///
162 /// \param state The Lua state from which to get the value.
163 /// \param value_index The stack index in which the value resides.
164 ///
165 /// \throw value_error If the value in state(value_index) cannot be
166 ///     processed by this node.
167 void
set_lua(lutok::state & state,const int value_index)168 engine::user_node::set_lua(lutok::state& state, const int value_index)
169 {
170     if (state.is_number(value_index)) {
171         config::typed_leaf_node< passwd::user >::set(
172             passwd::find_user_by_uid(state.to_integer(-1)));
173     } else if (state.is_string(value_index)) {
174         config::typed_leaf_node< passwd::user >::set(
175             passwd::find_user_by_name(state.to_string(-1)));
176     } else
177         throw config::value_error("Invalid user identifier");
178 }
179 
180 
181 /// Sets the value of the node from a raw string representation.
182 ///
183 /// \param raw_value The value to set the node to.
184 ///
185 /// \throw value_error If the value is invalid.
186 void
set_string(const std::string & raw_value)187 engine::user_node::set_string(const std::string& raw_value)
188 {
189     try {
190         config::typed_leaf_node< passwd::user >::set(
191             passwd::find_user_by_name(raw_value));
192     } catch (const std::runtime_error& e) {
193         int uid;
194         try {
195             uid = text::to_type< int >(raw_value);
196         } catch (const text::value_error& e2) {
197             throw error(F("Cannot find user with name '%s'") % raw_value);
198         }
199 
200         try {
201             config::typed_leaf_node< passwd::user >::set(
202                 passwd::find_user_by_uid(uid));
203         } catch (const std::runtime_error& e2) {
204             throw error(F("Cannot find user with UID %s") % uid);
205         }
206     }
207 }
208 
209 
210 /// Converts the contents of the node to a string.
211 ///
212 /// \pre The node must have a value.
213 ///
214 /// \return A string representation of the value held by the node.
215 std::string
to_string(void) const216 engine::user_node::to_string(void) const
217 {
218     return config::typed_leaf_node< passwd::user >::value().name;
219 }
220 
221 
222 /// Constructs a config with the built-in settings.
223 ///
224 /// \return A default test suite configuration.
225 config::tree
default_config(void)226 engine::default_config(void)
227 {
228     config::tree tree(false);
229     init_tree(tree);
230     set_defaults(tree);
231     return tree;
232 }
233 
234 
235 /// Constructs a config with the built-in settings.
236 ///
237 /// \return An empty test suite configuration.
238 config::tree
empty_config(void)239 engine::empty_config(void)
240 {
241     config::tree tree(false);
242     init_tree(tree);
243 
244     // Tests of Kyua itself tend to use an empty config, i.e. default
245     // execution environment is used. Let's allow it.
246     std::set< std::string > supported;
247     supported.insert(engine::execenv::default_execenv_name);
248     tree.set< config::strings_set_node >("execenvs", supported);
249 
250     return tree;
251 }
252 
253 
254 /// Parses a test suite configuration file.
255 ///
256 /// \param file The file to parse.
257 ///
258 /// \return High-level representation of the configuration file.
259 ///
260 /// \throw load_error If there is any problem loading the file.  This includes
261 ///     file access errors and syntax errors.
262 config::tree
load_config(const utils::fs::path & file)263 engine::load_config(const utils::fs::path& file)
264 {
265     config::tree tree(false);
266     try {
267         config_parser(tree).parse(file);
268     } catch (const config::error& e) {
269         throw load_error(file, e.what());
270     }
271     return tree;
272 }
273