Line data Source code
1 : // Copyright (c) 2012-2024 Made to Order Software Corp. All Rights Reserved
2 : //
3 : // https://snapwebsites.org/project/eventdispatcher
4 : // contact@m2osw.com
5 : //
6 : // This program is free software; you can redistribute it and/or modify
7 : // it under the terms of the GNU General Public License as published by
8 : // the Free Software Foundation; either version 2 of the License, or
9 : // (at your option) any later version.
10 : //
11 : // This program is distributed in the hope that it will be useful,
12 : // but WITHOUT ANY WARRANTY; without even the implied warranty of
13 : // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 : // GNU General Public License for more details.
15 : //
16 : // You should have received a copy of the GNU General Public License
17 : // along with this program; if not, write to the Free Software
18 : // Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
19 :
20 : /** \file
21 : * \brief Handle message definitions.
22 : *
23 : * The library supports loading message definitions from files. Those
24 : * definitions are useful to make sure a message includes all the
25 : * required parameters, does not include certain forbidden parameters,
26 : * and that parameters have a value that matches its type.
27 : *
28 : * This implements the get_message_definition() and with time probably
29 : * many other functions. For now, the check is done in the message.cpp
30 : * which is probably not the right place...
31 : */
32 :
33 :
34 : // self
35 : //
36 : #include "eventdispatcher/message_definition.h"
37 :
38 : #include "eventdispatcher/exception.h"
39 : #include "eventdispatcher/message.h"
40 :
41 :
42 : // advgetopt
43 : //
44 : #include <advgetopt/conf_file.h>
45 :
46 :
47 : // cppthread
48 : //
49 : #include <cppthread/guard.h>
50 : #include <cppthread/mutex.h>
51 :
52 :
53 : // snapdev
54 : //
55 : #include <snapdev/pathinfo.h>
56 : #include <snapdev/tokenize_string.h>
57 :
58 :
59 : // last include
60 : //
61 : #include <snapdev/poison.h>
62 :
63 :
64 :
65 : namespace ed
66 : {
67 :
68 :
69 :
70 : namespace
71 : {
72 :
73 :
74 :
75 : /** \brief The extension used to check for message definitions.
76 : *
77 : * This prefix is appened to the filename before we try to read
78 : * message definitions for a given message.
79 : */
80 : char const * const g_message_definition_suffix = ".conf";
81 :
82 :
83 : /** \brief The paths to the list of message definitions.
84 : *
85 : * This variable is dynamically set when you call the
86 : * process_message_definition_options() function.
87 : *
88 : * The variable supports any number of paths separated by
89 : * a colon (:). In most cases, you don't need more than one
90 : * path, but tests usually require at least two: the folder
91 : * where the message definitions get installed
92 : * (`BUILD/Debug/dist/eventdispatcher/messages`) and the location
93 : * of the messages this very project defines.
94 : */
95 : std::string g_message_definition_paths = std::string();
96 :
97 :
98 : /** \brief The list of loaded message definitions.
99 : *
100 : * We load message definitions the first time we receive a message.
101 : * The definitions are then stored in this map until the application
102 : * is done.
103 : */
104 : message_definition::map_t g_message_definitions;
105 :
106 :
107 : /** \brief Options to handle the message definition.
108 : *
109 : * At the moment, this gives the user the ability to define the
110 : * path to the definitions. This is really useful for the programmers
111 : * since the definitions are under the BUILD folder rather than
112 : * `/usr/share/...`
113 : */
114 : advgetopt::option const g_options[] =
115 : {
116 : // MESSAGE DEFINITION OPTIONS
117 : //
118 : advgetopt::define_option(
119 : advgetopt::Name("path-to-message-definitions")
120 : , advgetopt::Flags(advgetopt::all_flags<
121 : advgetopt::GETOPT_FLAG_GROUP_OPTIONS
122 : , advgetopt::GETOPT_FLAG_COMMAND_LINE
123 : , advgetopt::GETOPT_FLAG_ENVIRONMENT_VARIABLE
124 : , advgetopt::GETOPT_FLAG_CONFIGURATION_FILE
125 : , advgetopt::GETOPT_FLAG_REQUIRED>())
126 : , advgetopt::Help("the path to the message definitions used to verify message validity before dispatching them.")
127 : , advgetopt::DefaultValue("/usr/share/eventdispatcher/messages")
128 : ),
129 :
130 : // END
131 : //
132 : advgetopt::end_options()
133 : };
134 :
135 :
136 :
137 : } // no name namespace
138 :
139 :
140 :
141 : /** \brief Add message definition specific command line options.
142 : *
143 : * This function adds the message definition command line options
144 : * to your \p opts variable.
145 : *
146 : * This needs to be called before you parse the argv array of command
147 : * line options.
148 : *
149 : * \param[in,out] opts The option array to dynamically update with the
150 : * message definition command line options.
151 : */
152 1 : void add_message_definition_options(advgetopt::getopt & opts)
153 : {
154 1 : opts.parse_options_info(g_options, true);
155 1 : }
156 :
157 :
158 : /** \brief Retrieve the command line parameters.
159 : *
160 : * This function needs to be called before the get_message_definition()
161 : * function gets called in order to properly setup the path to
162 : * the list of message definitions.
163 : *
164 : * \param[in] opts The advgetopt options.
165 : */
166 1 : void process_message_definition_options(advgetopt::getopt const & opts)
167 : {
168 3 : g_message_definition_paths = opts.get_string("path-to-message-definitions");
169 1 : }
170 :
171 :
172 : /** \brief Set the list of paths to message definitions.
173 : *
174 : * In most cases, you want to use the add_message_definition_options()
175 : * and the process_message_definition_options() functions. Those handle
176 : * command line options so the user can use the --path-to-message-definitions
177 : * option on their command line.
178 : *
179 : * When working on tests, however, it happens that this is not a good
180 : * option and having direct access to the variable is much easier.
181 : * To make the test more robuts, it is better to call this function
182 : * again once done in order to clear the paths:
183 : *
184 : * \code
185 : * ed::set_message_definition_paths("my:paths:here");
186 : * ... run test ...
187 : * ed::set_message_definition_paths(std::string());
188 : * \endcode
189 : *
190 : * Or use the RAII clas proposed:
191 : *
192 : * \code
193 : * ed::manage_message_definition_paths mdp("my:paths:here");
194 : * ... run test ...
195 : * \endcode
196 : */
197 1 : void set_message_definition_paths(std::string const & paths)
198 : {
199 1 : g_message_definition_paths = paths;
200 1 : }
201 :
202 :
203 : /** \brief Read definitions of a message.
204 : *
205 : * This function searches for a file named \p command plus the .conf
206 : * suffix in the message definition directory
207 : * (`/usr/share/eventdispatcher/messages`).
208 : *
209 : * \note
210 : * If a package gets installed later with a command definition which we
211 : * missed loading here, then that command will continue to not be tested
212 : * (as in the newly instaleld definition will be ignored). These definitions
213 : * will be taken in account on a restart of any services making use of them.
214 : *
215 : * \warning
216 : * This function is expected to be called with the real message command
217 : * and not the f_match.f_expr which could be a regular expression.
218 : *
219 : * \todo
220 : * Add support for includes.
221 : *
222 : * \param[in] command Name of the message definition to load.
223 : *
224 : * \return A pointer to the loaded message definition.
225 : */
226 20 : message_definition::pointer_t get_message_definition(std::string const & command)
227 : {
228 20 : if(g_message_definition_paths.empty())
229 : {
230 : throw invalid_parameter(
231 : "message definition:"
232 0 : + command
233 0 : + ": no paths defined to message definitions. (i.e. did you call the add_message_definition_options() and process_message_definition_options() functions?)");
234 : }
235 :
236 20 : verify_message_name(command);
237 :
238 20 : cppthread::guard lock(*cppthread::g_system_mutex);
239 :
240 20 : auto it(g_message_definitions.find(command));
241 20 : if(it != g_message_definitions.end())
242 : {
243 3 : return it->second;
244 : }
245 :
246 17 : message_definition::pointer_t def(std::make_shared<message_definition>());
247 17 : def->f_command = command;
248 17 : g_message_definitions[command] = def;
249 :
250 17 : std::list<std::string> paths;
251 51 : snapdev::tokenize_string(
252 : paths
253 : , g_message_definition_paths
254 : , ":"
255 : , true);
256 :
257 17 : bool found(false);
258 30 : for(auto const & p : paths)
259 : {
260 30 : std::string filename(p);
261 30 : filename += '/';
262 30 : filename += command;
263 30 : filename += g_message_definition_suffix;
264 :
265 30 : if(!snapdev::pathinfo::file_exists(filename))
266 : {
267 : // no such file, just return the default message definition
268 : //
269 13 : continue;
270 : }
271 17 : found = true;
272 :
273 17 : advgetopt::conf_file_setup setup(filename);
274 17 : advgetopt::conf_file::pointer_t config(advgetopt::conf_file::get_conf_file(setup));
275 17 : advgetopt::conf_file::sections_t sections(config->get_sections());
276 41 : for(auto const & s : sections)
277 : {
278 : // section names use dashes between words
279 : // our messages use underscores
280 : //
281 24 : message_parameter param = {
282 : .f_name = advgetopt::option_with_underscores(s),
283 24 : };
284 :
285 24 : std::string param_name(s + "::type");
286 24 : if(config->has_parameter(param_name))
287 : {
288 5 : std::string const & type(config->get_parameter(param_name));
289 5 : if(type == "default"
290 5 : || type == "string")
291 : {
292 0 : param.f_type = parameter_type_t::PARAMETER_TYPE_STRING;
293 : }
294 5 : else if(type == "integer")
295 : {
296 0 : param.f_type = parameter_type_t::PARAMETER_TYPE_INTEGER;
297 : }
298 5 : else if(type == "address")
299 : {
300 1 : param.f_type = parameter_type_t::PARAMETER_TYPE_ADDRESS;
301 : }
302 4 : else if(type == "timespec")
303 : {
304 4 : param.f_type = parameter_type_t::PARAMETER_TYPE_TIMESPEC;
305 : }
306 : else
307 : {
308 : throw invalid_parameter(
309 : "message definition:"
310 0 : + command
311 0 : + ": parameter type \""
312 0 : + type
313 0 : + "\" is not a supported type.");
314 : }
315 5 : }
316 :
317 24 : param_name = s + "::flags";
318 24 : if(config->has_parameter(param_name))
319 : {
320 : // start from a clean slate
321 : //
322 22 : param.f_flags = 0;
323 :
324 22 : std::string const & flags(config->get_parameter(param_name));
325 22 : std::list<std::string> flag_names;
326 66 : snapdev::tokenize_string(flag_names, flags, ",", true);
327 44 : for(auto const & f : flag_names)
328 : {
329 22 : if(f == "required")
330 : {
331 4 : param.f_flags |= PARAMETER_FLAG_REQUIRED;
332 : }
333 18 : else if(f == "empty")
334 : {
335 0 : param.f_flags |= PARAMETER_FLAG_EMPTY;
336 : }
337 18 : else if(f == "forbidden")
338 : {
339 0 : param.f_flags |= PARAMETER_FLAG_FORBIDDEN;
340 : }
341 18 : else if(f != "optional"
342 0 : && f != "defined"
343 18 : && f != "allowed")
344 : {
345 : throw invalid_parameter(
346 : "message definition:"
347 0 : + command
348 0 : + ": parameter flag \""
349 0 : + f
350 0 : + "\" not supported.");
351 : }
352 : }
353 22 : }
354 :
355 24 : def->f_parameters.push_back(param);
356 24 : }
357 :
358 : // we only read one config file; other copies in the
359 : // other folders are ignored
360 : //
361 : break;
362 47 : }
363 :
364 : #ifdef _DEBUG
365 17 : if(!found)
366 : {
367 : throw invalid_parameter(
368 : "message definition for \""
369 0 : + command
370 0 : + "\" not found.");
371 : }
372 : #endif
373 :
374 17 : return def;
375 20 : }
376 :
377 :
378 :
379 : } // namespace ed
380 : // vim: ts=4 sw=4 et
|