Line data Source code
1 : // Snap Websites Server -- plugin loader
2 : // Copyright (c) 2011-2019 Made to Order Software Corp. All Rights Reserved
3 : //
4 : // https://snapwebsites.org/
5 : // contact@m2osw.com
6 : //
7 : // This program is free software; you can redistribute it and/or modify
8 : // it under the terms of the GNU General Public License as published by
9 : // the Free Software Foundation; either version 2 of the License, or
10 : // (at your option) any later version.
11 : //
12 : // This program is distributed in the hope that it will be useful,
13 : // but WITHOUT ANY WARRANTY; without even the implied warranty of
14 : // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 : // GNU General Public License for more details.
16 : //
17 : // You should have received a copy of the GNU General Public License
18 : // along with this program; if not, write to the Free Software
19 : // Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
20 :
21 :
22 : // self
23 : //
24 : #include "snapwebsites/plugins.h"
25 :
26 :
27 : // snapwebsites lib
28 : //
29 : #include "snapwebsites/log.h"
30 : #include "snapwebsites/snapwebsites.h"
31 : #include "snapwebsites/qstring_stream.h"
32 :
33 :
34 : // snapdev lib
35 : //
36 : #include <snapdev/not_used.h>
37 :
38 :
39 : // Qt lib
40 : //
41 : #include <QDir>
42 : #include <QMap>
43 : #include <QFileInfo>
44 :
45 :
46 : // C++ lib
47 : //
48 : #include <sstream>
49 :
50 :
51 : // C lib
52 : //
53 : #include <dlfcn.h>
54 : #include <link.h>
55 : #include <sys/stat.h>
56 :
57 :
58 : // last include
59 : //
60 : #include <snapdev/poison.h>
61 :
62 :
63 :
64 :
65 : namespace snap
66 : {
67 : namespace plugins
68 : {
69 :
70 2 : plugin_map_t g_plugins;
71 2 : plugin_vector_t g_ordered_plugins;
72 2 : QString g_next_register_name;
73 2 : QString g_next_register_filename;
74 2 : QString g_next_register_introducer;
75 :
76 :
77 : /** \brief Load a complete list of available plugins.
78 : *
79 : * This is used in the administrator screen to offer users a complete list of
80 : * plugins that can be installed.
81 : *
82 : * \param[in] plugin_paths The paths to all the Snap plugins.
83 : *
84 : * \return A list of plugin names.
85 : */
86 0 : snap_string_list list_all(QString const & plugin_paths)
87 : {
88 : // note that we expect the plugin directory to be clean
89 : // (we may later check the validity of each directory to make 100% sure
90 : // that it includes a corresponding .so file)
91 0 : snap_string_list const paths(plugin_paths.split(':'));
92 0 : snap_string_list filters;
93 0 : filters << "*.so";
94 0 : snap_string_list result;
95 0 : for(auto const & p : paths)
96 : {
97 0 : QDir dir(p);
98 :
99 0 : dir.setSorting(QDir::Name | QDir::IgnoreCase);
100 :
101 : // TBD: while in development, plugins are in sub-directories
102 : // once installed, they are not...
103 : // maybe we should have some sort of flag to skip on the
104 : // sub-directories once building a package?
105 : //
106 0 : snap_string_list const sub_dirs(dir.entryList(QDir::Dirs | QDir::NoDotAndDotDot));
107 0 : for(auto const & s : sub_dirs)
108 : {
109 0 : QDir sdir(QString("%1/%2").arg(p).arg(s));
110 :
111 0 : sdir.setSorting(QDir::Name | QDir::IgnoreCase);
112 0 : sdir.setNameFilters(filters);
113 :
114 0 : result << sdir.entryList(QDir::Files);
115 : }
116 :
117 0 : dir.setNameFilters(filters);
118 :
119 0 : result << dir.entryList(QDir::Files);
120 : }
121 :
122 : // clean up the list
123 0 : for(int p(result.size() - 1); p >= 0; --p)
124 : {
125 0 : if(result[p].length() < 4
126 0 : || !result[p].endsWith(".so"))
127 : {
128 : // this should never happen
129 0 : result.removeAt(p);
130 : }
131 0 : else if(result[p].length() > 6
132 0 : && result[p].startsWith("lib"))
133 : {
134 : // remove the "lib" and ".so"
135 0 : result[p] = result[p].mid(3, result[p].length() - 6);
136 : }
137 : else
138 : {
139 : // remove the ".so"
140 0 : result[p] = result[p].left(result[p].length() - 3);
141 : }
142 : }
143 :
144 0 : result << "server";
145 :
146 0 : result.sort();
147 :
148 0 : return result;
149 : }
150 :
151 :
152 : /** \brief Load all the plugins.
153 : *
154 : * Someone who wants to remove a plugin simply deletes it or its
155 : * softlink at least.
156 : *
157 : * \warning
158 : * This function CANNOT use glob() to read all the plugins in a directory.
159 : * At this point we assume that each website will use more or less of
160 : * the installed plugins and thus loading them all is not the right way of
161 : * handling the loading. Thus we now get a \p list_of_plugins parameter
162 : * with the name of the plugins we want to dlopen().
163 : *
164 : * \todo
165 : * Look into the shared pointers and unloading plugins, if that ever
166 : * happens (I don't think it does.)
167 : *
168 : * \param[in] plugin_paths The colon (:) separated list of paths to
169 : * directories with plugins.
170 : * \param[in] snap A pointer to the child object loading these plugins.
171 : * This pointer gets passed to the bootstrap() signal.
172 : * \param[in] server A pointer to the server to register it as a plugin.
173 : * \param[in] list_of_plugins The list of plugins to load.
174 : *
175 : * \return true if all the modules were loaded.
176 : */
177 0 : bool load(QString const & plugin_paths, snap_child * snap, plugin_ptr_t server, snap_string_list const & list_of_plugins, QString const & introducer)
178 : {
179 0 : g_plugins.insert("server", server.get());
180 :
181 0 : snap_string_list const paths(plugin_paths.split(':'));
182 :
183 0 : bool good(true);
184 0 : for(snap_string_list::const_iterator it(list_of_plugins.begin());
185 0 : it != list_of_plugins.end();
186 : ++it)
187 : {
188 0 : QString const name(*it);
189 :
190 : // the Snap server is already added to the list under that name!
191 : //
192 0 : if(name == "server")
193 : {
194 0 : SNAP_LOG_ERROR("error: a plugin cannot be called \"server\".");
195 0 : good = false;
196 0 : continue;
197 : }
198 :
199 : // in case we get multiple calls to this function we must make sure that
200 : // all plugins have a distinct name (i.e. a plugin factory could call
201 : // this function to load sub-plugins!)
202 : //
203 0 : if(exists(name))
204 : {
205 0 : SNAP_LOG_ERROR("error: two plugins cannot be named the same, found \"")(name)("\" twice.");
206 0 : good = false;
207 0 : continue;
208 : }
209 :
210 : // make sure the name is one we consider valid; we may end up
211 : // using plugin names in scripts and thus want to only support
212 : // a small set of characters; any other name is refused by
213 : // the verify_plugin_name() function (which prints an error
214 : // message already so no need to another one here)
215 : //
216 0 : if(!verify_plugin_name(name))
217 : {
218 0 : good = false;
219 0 : continue;
220 : }
221 :
222 : // check that the file exists, if not we generate an error
223 : //
224 0 : QString const filename(find_plugin_filename(paths, name));
225 0 : if(filename.isEmpty())
226 : {
227 0 : SNAP_LOG_ERROR("plugin named \"")(name)("\" not found in the plugin directory. (paths: ")(plugin_paths)(")");
228 0 : good = false;
229 0 : continue;
230 : }
231 :
232 : // TBD: Use RTLD_NOW instead of RTLD_LAZY in DEBUG mode
233 : // so we discover missing symbols would be nice, only
234 : // that would require loading in the correct order...
235 : // (see dlopen() call below)
236 : //
237 :
238 : // load the plugin; the plugin will register itself
239 : //
240 : // use some really ugly globals because dlopen() does not give us
241 : // a way to pass parameters to the plugin factory constructor
242 : //
243 0 : g_next_register_name = name;
244 0 : g_next_register_filename = filename;
245 0 : g_next_register_introducer = introducer;
246 0 : void const * const h(dlopen(filename.toUtf8().data(), RTLD_LAZY | RTLD_GLOBAL));
247 0 : if(h == nullptr)
248 : {
249 0 : int const e(errno);
250 0 : SNAP_LOG_ERROR("error: cannot load plugin file \"")(filename)("\" (errno: ")(e)(", ")(dlerror())(")");
251 0 : good = false;
252 0 : continue;
253 : }
254 0 : g_next_register_name.clear();
255 0 : g_next_register_filename.clear();
256 0 : g_next_register_introducer.clear();
257 : //SNAP_LOG_ERROR("note: registering plugin: \"")(name)("\"");
258 : }
259 :
260 : // set the g_ordered_plugins with the default order as alphabetical,
261 : // although we check dependencies to properly reorder as expected
262 : // by what each plugin tells us what its dependencies are
263 : //
264 : // TODO: refactor this embedded raw loop to take advantage of
265 : // C++11's lambda functions. Specifically, we should use std::find_if(),
266 : // and remove the goto + label. My suggested code is in the #if 0 block
267 : // below.
268 : //
269 : // Alexis Wilke, [07.06.17 14:46]
270 : // "If you can guarantee that the order won't be affected, we can do it
271 : // either way. It's sorted alphabetically, then by priority. The priority
272 : // has precedence over the alphabetical order. But we always end up with a
273 : // strong order (i.e. if the priority and names do not change plugin A will
274 : // always be before plugin B if it were before on the previous load)."
275 : //
276 0 : for(auto const & p : g_plugins)
277 : {
278 0 : QString const column_name(QString("|%1|").arg(p->get_plugin_name()));
279 : #if 0
280 : // TODO: test this new code out to remove the raw loop below...
281 : auto found_iter = std::find_if( std::begin(g_ordered_plugins), std::end(g_ordered_plugins),
282 : [&column_name]( auto const & plugin )
283 : {
284 : return plugin->dependencies().indexOf(column_name) >= 0;
285 : });
286 : if( found_iter != std::end(g_ordered_plugins) )
287 : {
288 : g_ordered_plugins.insert( found_iter, p );
289 : }
290 : else
291 : {
292 : g_ordered_plugins.push_back(p);
293 : }
294 : #else
295 0 : for(plugin_vector_t::iterator sp(g_ordered_plugins.begin());
296 0 : sp != g_ordered_plugins.end();
297 : ++sp)
298 : {
299 0 : if((*sp)->dependencies().indexOf(column_name) >= 0)
300 : {
301 0 : g_ordered_plugins.insert(sp, p);
302 0 : goto inserted;
303 : }
304 : }
305 : // if not before another plugin, insert at the end by default
306 0 : g_ordered_plugins.push_back(p);
307 0 : inserted:;
308 : #endif
309 : }
310 :
311 : // bootstrap() functions have to be called in order to get all the
312 : // signals registered in order! (YES!!! This one for() loop makes
313 : // all the signals work as expected by making sure they are in a
314 : // very specific order)
315 : //
316 0 : for(auto const & p : g_ordered_plugins)
317 : {
318 0 : p->bootstrap(snap);
319 : }
320 :
321 0 : return good;
322 : }
323 :
324 :
325 : /** \brief Try to find the plugin using the list of paths.
326 : *
327 : * This function searches for a plugin in each one of the specified
328 : * paths and as:
329 : *
330 : * \code
331 : * <path>/<name>.so
332 : * <path>/lib<name>.so
333 : * <path>/<name>/<name>.so
334 : * <path>/<name>/lib<name>.so
335 : * \endcode
336 : *
337 : * \todo
338 : * We may change the naming convention to make use of the ${PROJECT_NAME}
339 : * in the CMakeLists.txt files. In that case we'd end up with names
340 : * that include the work plugin as in:
341 : *
342 : * \code
343 : * <path>/libplugin_<name>.so
344 : * \endcode
345 : *
346 : * \param[in] plugin_paths The list of paths to check with.
347 : * \param[in] name The name of the plugin being searched.
348 : *
349 : * \return The full path and filename of the plugin or empty if not found.
350 : */
351 0 : QString find_plugin_filename(snap_string_list const & plugin_paths, QString const & name)
352 : {
353 0 : int const max_paths(plugin_paths.size());
354 0 : for(int i(0); i < max_paths; ++i)
355 : {
356 0 : QString const path(plugin_paths[i]);
357 0 : QString filename(QString("%1/%2.so").arg(path).arg(name));
358 0 : if(QFile::exists(filename))
359 : {
360 0 : return filename;
361 : }
362 :
363 : //
364 : // If not found, see if it has a "lib" at the front of the file:
365 : //
366 0 : filename = QString("%1/lib%2.so").arg(path).arg(name);
367 0 : if(QFile::exists(filename))
368 : {
369 0 : return filename;
370 : }
371 :
372 : //
373 : // If not found, see if it lives under a named folder:
374 : //
375 0 : filename = QString("%1/%2/%2.so").arg(path).arg(name);
376 0 : if(QFile::exists(filename))
377 : {
378 0 : return filename;
379 : }
380 :
381 : //
382 : // Last test: check plugin names starting with "lib" in named folder:
383 : //
384 0 : filename = QString("%1/%2/lib%2.so").arg(path).arg(name);
385 0 : if(QFile::exists(filename))
386 : {
387 0 : return filename;
388 : }
389 : }
390 :
391 0 : return QString();
392 : }
393 :
394 :
395 : /** \brief Verify that a name is a valid plugin name.
396 : *
397 : * This function checks a string to know whether it is a valid plugin name.
398 : *
399 : * A valid plugin name is a string of letters (A-Z or a-z), digits (0-9),
400 : * and the underscore (_), dash (-), and period (.). Although the name
401 : * cannot start or end with a dash or a period.
402 : *
403 : * \todo
404 : * At this time this function prints errors out in stderr. This may
405 : * change later and errors will be sent to the logger. However, we
406 : * need to verify that the logger is ready when this function gets
407 : * called.
408 : *
409 : * \param[in] name The name to verify.
410 : *
411 : * \return true if the name is considered valid.
412 : */
413 0 : bool verify_plugin_name(QString const & name)
414 : {
415 0 : if(name.isEmpty())
416 : {
417 0 : SNAP_LOG_ERROR() << "error: an empty plugin name is not valid.";
418 0 : return false;
419 : }
420 0 : for(QString::const_iterator p(name.begin()); p != name.end(); ++p)
421 : {
422 0 : if((*p < 'a' || *p > 'z')
423 0 : && (*p < 'A' || *p > 'Z')
424 0 : && (*p < '0' || *p > '9')
425 0 : && *p != '_' && *p != '-' && *p != '.')
426 : {
427 0 : SNAP_LOG_ERROR("error: plugin name \"")(name)("\" includes forbidden characters.");
428 0 : return false;
429 : }
430 : }
431 : // Note: we know that name is not empty
432 0 : QChar const first(name[0]);
433 0 : if(first == '.'
434 0 : || first == '-'
435 0 : || (first >= '0' && first <= '9'))
436 : {
437 0 : SNAP_LOG_ERROR("error: plugin name \"")(name)("\" cannot start with a digit (0-9), a period (.), or dash (-).");
438 0 : return false;
439 : }
440 : // Note: we know that name is not empty
441 0 : QChar const last(name[name.length() - 1]);
442 0 : if(last == '.' || last == '-')
443 : {
444 0 : SNAP_LOG_ERROR("error: plugin name \"")(name)("\" cannot end with a period (.) or dash (-).");
445 0 : return false;
446 : }
447 :
448 0 : return true;
449 : }
450 :
451 :
452 : /** \brief Check whether a plugin was loaded.
453 : *
454 : * This function searches the list of loaded plugins and returns true if the
455 : * plugin with the speficied \p name exists.
456 : *
457 : * \param[in] name The name of the plugin to check for.
458 : *
459 : * \return true if the plugin is loaded, false otherwise.
460 : */
461 0 : bool exists(QString const & name)
462 : {
463 0 : return g_plugins.contains(name);
464 : }
465 :
466 :
467 : /** \brief Register a plugin in the list of plugins.
468 : *
469 : * This function is called by plugin factories to register new plugins.
470 : * Do not attempt to call this function directly or you'll get an
471 : * exception.
472 : *
473 : * \exception plugin_exception
474 : * If the name is empty, the name does not correspond to the plugin
475 : * being loaded, or the plugin is being loaded for the second time,
476 : * then this exception is raised.
477 : *
478 : * \param[in] name The name of the plugin being added.
479 : * \param[in] p A pointer to the plugin being added.
480 : */
481 0 : void register_plugin(QString const & name, plugin * p)
482 : {
483 0 : if(name.isEmpty())
484 : {
485 0 : throw plugin_exception("plugin name missing when registering... expected \"" + name + "\".");
486 : }
487 :
488 0 : QString const full_name(g_next_register_introducer.isEmpty()
489 : ? name
490 0 : : g_next_register_introducer + "_" + name);
491 :
492 0 : if(full_name != g_next_register_name)
493 : {
494 0 : throw plugin_exception("it is not possible to register a plugin (" + full_name + ") other than the one being loaded (" + g_next_register_name + ").");
495 : }
496 :
497 : #ifdef DEBUG
498 : // this is not possible if you use the macro, but in case you create
499 : // your own factory instance by hand, it is a requirement too
500 : //
501 0 : if(full_name != p->get_plugin_name())
502 : {
503 0 : throw plugin_exception("somehow your plugin factory name is \"" + p->get_plugin_name() + "\" when we were expecting \"" + name + "\".");
504 : }
505 : #endif
506 0 : if(exists(name))
507 : {
508 : // this should not happen except if the plugin factory was attempting
509 : // to register the same plugin many times in a row
510 : //
511 0 : throw plugin_exception("it is not possible to register a plugin more than once (" + name + ").");
512 : }
513 :
514 : // verify the server version the plugin was compiled with and this
515 : // server version
516 : //
517 : // TODO: should we not check the patch version? (i.e. assume that if only
518 : // the patch changes then the interface has not changed.)
519 : //
520 : // TODO: the #ifndef _DEBUG comes from the fact that SNAP-54 is not
521 : // compatible with debug, see SNAP-416 for additional details
522 : // about the versioning problem in the development environment
523 : //
524 : #ifndef _DEBUG
525 : if(p->get_server_major_version() != SNAPWEBSITES_VERSION_MAJOR
526 : || p->get_server_minor_version() != SNAPWEBSITES_VERSION_MINOR
527 : //|| p->get_server_patch_version() != SNAPWEBSITES_VERSION_PATCH -- for now, ignore the patch to avoid some potential problems
528 : )
529 : {
530 : // a mixed up plugin is like to cause problems so prevent the loading
531 : // of a plugin which does not correspond one to one to its server
532 : // version
533 : //
534 : // I ran in a problem with building finball and thus could not jump
535 : // from 1.6.x to 1.7.x (pbuilder would not install some packages
536 : // because of invalid versions.) So for now we just want to emit an
537 : // error. We'll have to see later how we want to deal with this
538 : // problem.
539 : //
540 : SNAP_LOG_ERROR(QString("incompatible server versions between this server (%1.%2.%3) and this plugin \"%7\" (%4.%5.%6) -- Note: we ignore the patch version")
541 : .arg(SNAPWEBSITES_VERSION_MAJOR)
542 : .arg(SNAPWEBSITES_VERSION_MINOR)
543 : .arg(SNAPWEBSITES_VERSION_PATCH)
544 : .arg(p->get_server_major_version())
545 : .arg(p->get_server_minor_version())
546 : .arg(p->get_server_patch_version())
547 : .arg(name)
548 : );
549 : //throw plugin_exception(QString("incompatible server versions between this server (%1.%2.%3) and plugin \"%7\" (%4.%5.%6) -- Note: we ignore the patch version")
550 : // .arg(SNAPWEBSITES_VERSION_MAJOR)
551 : // .arg(SNAPWEBSITES_VERSION_MINOR)
552 : // .arg(SNAPWEBSITES_VERSION_PATCH)
553 : // .arg(p->get_server_major_version())
554 : // .arg(p->get_server_minor_version())
555 : // .arg(p->get_server_patch_version())
556 : // .arg(name)
557 : // );
558 : }
559 : #endif
560 :
561 0 : g_plugins.insert(name, p);
562 0 : }
563 :
564 :
565 : /** \brief Initialize a plugin.
566 : *
567 : * This function initializes the plugin with its filename.
568 : */
569 0 : plugin::plugin()
570 : : f_name(g_next_register_name)
571 0 : , f_filename(g_next_register_filename)
572 : //, f_last_modification(0) -- auto-init
573 : //, f_version_major(0) -- auto-init
574 : //, f_version_minor(0) -- auto-init
575 : {
576 0 : }
577 :
578 :
579 : /** \brief Define the version of the plugin.
580 : *
581 : * This function saves the version of the plugin in the object.
582 : * This way other systems can access the version.
583 : *
584 : * In general you never call that function. It is automatically
585 : * called by the SNAP_PLUGIN_START() macro. Note that the
586 : * function cannot be called more than once and the version
587 : * cannot be zero or negative.
588 : *
589 : * \param[in] version_major The major version of the plugin.
590 : * \param[in] version_minor The minor version of the plugin.
591 : */
592 0 : void plugin::set_version(int version_major, int version_minor)
593 : {
594 0 : if(f_version_major != 0
595 0 : || f_version_minor != 0)
596 : {
597 : // version was already defined; it cannot be set again
598 0 : throw plugin_exception(QString("version of plugin \"%1\" already defined.").arg(f_name));
599 : }
600 :
601 0 : if(version_major < 0
602 0 : || version_minor < 0
603 0 : || (version_major == 0 && version_minor == 0))
604 : {
605 : // version cannot be negative or null
606 0 : throw plugin_exception(QString("version of plugin \"%1\" cannot be zero or negative (%2.%3).")
607 0 : .arg(f_name).arg(version_major).arg(version_minor));
608 : }
609 :
610 0 : f_version_major = version_major;
611 0 : f_version_minor = version_minor;
612 0 : }
613 :
614 :
615 : /** \brief Define the server version the plugin was compiled against.
616 : *
617 : * This function saves the server version of the plugin in the plugin object.
618 : * This way the registration function can verify that the plugin was compiled
619 : * with a compatible server.
620 : *
621 : * In general you never call that function. It is automatically
622 : * called by the SNAP_PLUGIN_START() macro. Note that the
623 : * function cannot be called more than once and the version
624 : * cannot be zero or negative.
625 : *
626 : * \param[in] version_major The major version of the server.
627 : * \param[in] version_minor The minor version of the server.
628 : * \param[in] version_patch The patch version of the server.
629 : */
630 0 : void plugin::set_server_version(int version_major, int version_minor, int version_patch)
631 : {
632 0 : if(f_server_version_major != 0
633 0 : || f_server_version_minor != 0
634 0 : || f_server_version_patch != 0)
635 : {
636 : // server version was already defined; it cannot be set again
637 : //
638 0 : throw plugin_exception(QString("server version of plugin \"%1\" already defined.").arg(f_name));
639 : }
640 :
641 0 : if(version_major < 0
642 0 : || version_minor < 0
643 0 : || version_patch < 0
644 0 : || (version_major == 0 && version_minor == 0 && version_patch == 0))
645 : {
646 : // server version cannot be negative or null
647 : //
648 0 : throw plugin_exception(QString("server version of plugin \"%1\" cannot be zero or negative (%2.%3.%4).")
649 0 : .arg(f_name)
650 0 : .arg(version_major)
651 0 : .arg(version_minor)
652 0 : .arg(version_patch));
653 : }
654 :
655 0 : f_server_version_major = version_major;
656 0 : f_server_version_minor = version_minor;
657 0 : f_server_version_patch = version_patch;
658 0 : }
659 :
660 :
661 : /** \brief Retrieve the major version of this plugin.
662 : *
663 : * This function returns the major version of this plugin. This is the
664 : * same version as defined in the plugin factory.
665 : *
666 : * \return The major version of the plugin.
667 : */
668 0 : int plugin::get_major_version() const
669 : {
670 0 : return f_version_major;
671 : }
672 :
673 :
674 : /** \brief Retrieve the minor version of this plugin.
675 : *
676 : * This function returns the minor version of this plugin. This is the
677 : * same version as defined in the plugin factory.
678 : *
679 : * \return The minor version of the plugin.
680 : */
681 0 : int plugin::get_minor_version() const
682 : {
683 0 : return f_version_minor;
684 : }
685 :
686 :
687 : /** \brief Retrieve the major version of the server of this plugin.
688 : *
689 : * This function returns the major version of the server this plugin was
690 : * compiled against. This is the version from the libsnapwebsites version.h
691 : * file.
692 : *
693 : * \return The minor version of the server of this plugin.
694 : */
695 0 : int plugin::get_server_major_version() const
696 : {
697 0 : return f_server_version_major;
698 : }
699 :
700 :
701 : /** \brief Retrieve the minor version of the server of this plugin.
702 : *
703 : * This function returns the minor version of the server this plugin was
704 : * compiled against. This is the version from the libsnapwebsites version.h
705 : * file.
706 : *
707 : * \return The minor version of the server of this plugin.
708 : */
709 0 : int plugin::get_server_minor_version() const
710 : {
711 0 : return f_server_version_minor;
712 : }
713 :
714 :
715 : /** \brief Retrieve the patch version of the server of this plugin.
716 : *
717 : * This function returns the patch version of the server this plugin was
718 : * compiled against. This is the version from the libsnapwebsites version.h
719 : * file.
720 : *
721 : * \return The patch version of the server of this plugin.
722 : */
723 0 : int plugin::get_server_patch_version() const
724 : {
725 0 : return f_server_version_patch;
726 : }
727 :
728 :
729 : /** \brief Retrieve the name of the plugin as defined on creation.
730 : *
731 : * This function returns the name of the plugin as defined on
732 : * creation of the plugin. It is not possible to modify the
733 : * name for safety.
734 : *
735 : * \return The name of the plugin as defined in your SNAP_PLUGIN_START() instantiation.
736 : */
737 0 : QString plugin::get_plugin_name() const
738 : {
739 0 : return f_name;
740 : }
741 :
742 :
743 : /** \brief Get the last modification date of the plugin.
744 : *
745 : * This function reads the modification date on the plugin file to determine
746 : * when it was last modified. This date can be used to determine whether the
747 : * plugin was modified since the last time we ran snap with this website.
748 : *
749 : * \return The last modification date and time in micro seconds.
750 : */
751 0 : int64_t plugin::last_modification() const
752 : {
753 0 : if(0 == f_last_modification)
754 : {
755 : // read the info only once
756 : struct stat s;
757 0 : if(stat(f_filename.toUtf8().data(), &s) == 0)
758 : {
759 : // should we make use of the usec mtime when available?
760 0 : f_last_modification = static_cast<int64_t>(s.st_mtime * 1000000LL);
761 : }
762 : // else TBD: should we throw here?
763 : }
764 :
765 0 : return f_last_modification;
766 : }
767 :
768 :
769 : /** \brief Return the URL to an icon representing your plugin.
770 : *
771 : * Each plugin can be assigned an icon. The path to that icon has
772 : * to be returned by this function. It us used whenever we build
773 : * lists representing plugins.
774 : *
775 : * The default function returns the path to a default plugin image.
776 : *
777 : * The image must be a 64x64 picture. The CSS will enforce the size
778 : * so it will possibly get stretched in weird ways if you did not
779 : * use the correct size to start withicon.
780 : *
781 : * \return The path to the default plugin icon.
782 : */
783 0 : QString plugin::icon() const
784 : {
785 0 : return "/images/snap/plugin-icon-64x64.png";
786 : }
787 :
788 :
789 : /** \fn QString plugin::description() const;
790 : * \brief Return a string describing this plugin.
791 : *
792 : * This function must be overloaded. It is expected to return a description
793 : * of the plugin. The string may include HTML. It should be limited to
794 : * inline HTML tags, although it can include header tags and paragraphs
795 : * too.
796 : *
797 : * The description should be relatively brief. The plugins also offer a
798 : * help URI which sends users to our snapwebsites.org website (or the
799 : * author of the plugin wbesite) so we do not need to go crazy in the
800 : * description. That external help page can go at length.
801 : *
802 : * \return A string representing the plugin description.
803 : */
804 :
805 :
806 : /** \brief Comma separated list of tags.
807 : *
808 : * This function returns a list of comma separated tags. It is used
809 : * to categorize a plugin so that way it makes it easier to find
810 : * in the large list presented to users under the Plugin Selector.
811 : *
812 : * For example, plugins that are used to test other plugins and
813 : * other parts of the system can be assigned the tag "test".
814 : *
815 : * By default plugins are not assigned any tags. This means they
816 : * will appear under the "others" special tag. You can always
817 : * make this explicit or offer the "others" tag along with
818 : * more useful tags, for example: "image,others".
819 : *
820 : * \return A list of tags, comma separated.
821 : */
822 0 : QString plugin::plugin_categorization_tags() const
823 : {
824 0 : return "";
825 : }
826 :
827 :
828 : /** \brief Return the URI to the help page for this plugin.
829 : *
830 : * Each plugin can be assigned a help page. By default, we
831 : * define the URL as:
832 : *
833 : * \code
834 : * https://snapwebsites.org/help/plugin/<plugin-name>
835 : * \endcode
836 : *
837 : * If you program your own plugin, you are expected to overload
838 : * this function and send users to your own website.
839 : *
840 : * \return The URI to the help page for this plugin.
841 : */
842 0 : QString plugin::help_uri() const
843 : {
844 0 : return QString("https://snapwebsites.org/help/plugin/%1").arg(f_name);
845 : }
846 :
847 :
848 : /** \brief Return the path to the settings page for this plugin.
849 : *
850 : * Each plugin can be assigned a settings page. By default, this
851 : * function returns an empty string meaning that no settings are
852 : * available.
853 : *
854 : * The function has to return an absolute path to a site borne
855 : * page that will allow an administrator to change various
856 : * settings that this plugin is handling.
857 : *
858 : * Some plugins may not themelves offer settings, but they may
859 : * still return a settings path to another plugin that is setup
860 : * to handle their settings (i.e. the info plugin handles many
861 : * other lower level plugins settings.)
862 : *
863 : * By default the function returns an empty path, meaning that the
864 : * settings button needs to be disabled in the plugin selector.
865 : *
866 : * \return The path to the settings page for this plugin.
867 : */
868 0 : QString plugin::settings_path() const
869 : {
870 0 : return QString();
871 : }
872 :
873 :
874 : /** \fn QString plugin::dependencies() const;
875 : * \brief Return a list of required dependencies.
876 : *
877 : * This function returns a list of dependencies, plugin names written
878 : * between pipes (|). All plugins have at least one dependency since
879 : * most plugins will not work without the base plugin (i.e. "|server|"
880 : * is the bottom most base you can use in your plugin).
881 : *
882 : * At this time, the "content" and "test_plugin_suite" plugins have no
883 : * dependencies.
884 : *
885 : * \note
886 : * Until "links" is merged with "content", it will depend on "content"
887 : * so that way "links" signals are registered after "content" signals.
888 : *
889 : * \return A list of plugin names representing all dependencies.
890 : */
891 :
892 :
893 : /** \fn void plugin::bootstrap(snap_child * snap)
894 : * \brief Bootstrap this plugin.
895 : *
896 : * The bootstrap virtual function is used to initialize the plugins. At
897 : * this point all the plugins are loaded, however, they are not yet
898 : * ready to receive signals because all plugins are not yet connected.
899 : * The bootstrap() function is actually used to get all the listeners
900 : * registered.
901 : *
902 : * Note that the plugin implementation loads all the plugins, sorts them,
903 : * then calls their bootstrap() function. Afterward, the init() function
904 : * is likely called. The bootstrap() registers signals and the server
905 : * init() signal can be used to send signals since at that point all the
906 : * plugins are properly installed and have all of their signals registered.
907 : *
908 : * \note
909 : * This is a pure virtual which is not implemented here so that way your
910 : * plugin will crash the server if you did not implement this function
911 : * which is considered mandatory.
912 : *
913 : * \param[in,out] snap The snap child process.
914 : */
915 :
916 :
917 : /** \brief Run an update.
918 : *
919 : * This function is a stub that does nothing. It is here so any plug in that
920 : * does not need an update does not need to define an "empty" function.
921 : *
922 : * At this time the function ignores the \p last_updated parameter and
923 : * it always returns the same date: Jan 1, 1990 at 00:00:00.
924 : *
925 : * \param[in] last_updated The UTC Unix date when this plugin was last updated (in micro seconds).
926 : *
927 : * \return The UTC Unix date of the last update of this plugin.
928 : */
929 0 : int64_t plugin::do_update(int64_t last_updated)
930 : {
931 0 : NOTUSED(last_updated);
932 :
933 0 : SNAP_PLUGIN_UPDATE_INIT();
934 :
935 : // in a complete implementation you have entries like this one:
936 : //SNAP_PLUGIN_UPDATE(2012, 1, 1, 0, 0, 0, initial_update);
937 :
938 0 : SNAP_PLUGIN_UPDATE_EXIT();
939 : }
940 :
941 :
942 : /** \brief Run a dynamic update.
943 : *
944 : * This function is called after the do_update(). This very version is
945 : * a stub that does nothing. It can be overloaded to create content in
946 : * the database after the content.xml was installed fully. In other
947 : * words, the dynamic update can make use of data that the content.xml
948 : * will be adding ahead of time.
949 : *
950 : * At this time the function ignores the \p last_updated parameter and
951 : * it always returns the same date: Jan 1, 1990 at 00:00:00.
952 : *
953 : * \param[in] last_updated The UTC Unix date when this plugin was last updated (in micro seconds).
954 : *
955 : * \return The UTC Unix date of the last update of this plugin.
956 : */
957 0 : int64_t plugin::do_dynamic_update(int64_t last_updated)
958 : {
959 0 : NOTUSED(last_updated);
960 :
961 0 : SNAP_PLUGIN_UPDATE_INIT();
962 :
963 : // in a complete implementation you have entries like this one:
964 : //SNAP_PLUGIN_UPDATE(2012, 1, 1, 0, 0, 0, dynamic_update);
965 :
966 0 : SNAP_PLUGIN_UPDATE_EXIT();
967 : }
968 :
969 :
970 : /** \brief Retrieve a pointer to an existing plugin.
971 : *
972 : * This function returns a pointer to a plugin that was previously loaded
973 : * with the load() function. If you only need to test whether a plugin
974 : * exists, then you should use exists() instead.
975 : *
976 : * \note
977 : * This function should not be called until your plugin bootstrap() function
978 : * is called. Before then, there are no guarantees that the plugin was already
979 : * loaded.
980 : *
981 : * \param[in] name The name of the plugin to retrieve.
982 : *
983 : * \return This function returns a pointer to the named plugin, or nullptr
984 : * if the plugin was not loaded.
985 : *
986 : * \sa load()
987 : * \sa exists()
988 : */
989 0 : plugin * get_plugin(QString const & name)
990 : {
991 0 : return g_plugins.value(name, nullptr);
992 : }
993 :
994 :
995 : /** \brief Retrieve the list of plugins.
996 : *
997 : * This function returns the list of plugins that were loaded in this
998 : * session. Remember that plugins are loaded each time a client accesses
999 : * the server.
1000 : *
1001 : * This means that the list is complete only once you are in the snap
1002 : * child and after the plugins were initialized. If you are in a plugin,
1003 : * this means the list is not complete in the constructor. It is complete
1004 : * anywhere else.
1005 : *
1006 : * \return List of plugins in a map indexed by plugin name
1007 : * (i.e. alphabetical order).
1008 : */
1009 0 : plugin_map_t const & get_plugin_list()
1010 : {
1011 0 : return g_plugins;
1012 : }
1013 :
1014 :
1015 : /** \brief Retrieve the list of plugins.
1016 : *
1017 : * This function returns the list of plugins that were sorted, once
1018 : * loaded, using their dependencies. This is a vector since we need
1019 : * to keep a very specific order of the plugins.
1020 : *
1021 : * This list is empty until all the plugins were loaded.
1022 : *
1023 : * This list should be empty when your plugins constructors are called.
1024 : *
1025 : * \return The sorted list of plugins in a vector.
1026 : */
1027 0 : plugin_vector_t const & get_plugin_vector()
1028 : {
1029 0 : return g_ordered_plugins;
1030 : }
1031 :
1032 :
1033 : /** \brief Read a plugin information.
1034 : *
1035 : * This constructor reads all the available information from the named
1036 : * plugin.
1037 : */
1038 0 : plugin_info::plugin_info(QString const & plugin_paths, QString const & name)
1039 : {
1040 0 : if(name == "server")
1041 : {
1042 : // this is a special case, the user is requesting information about
1043 : // the snapserver (snapwebsites.cpp) and not a plugin per-se.
1044 : //
1045 0 : f_name = name;
1046 0 : f_filename = "snapserver";
1047 0 : f_last_modification = 0;
1048 0 : f_icon = "/images/snap/snap-logo-64x64.png";
1049 0 : f_description = "The Snap! Websites server defines the base plugin used by the snap system.";
1050 0 : f_categorization_tags = "core";
1051 0 : f_help_uri = "https://snapwebsites.org/help/plugin/server";
1052 0 : f_settings_path = "/admin/plugins";
1053 0 : f_dependencies = "";
1054 0 : f_version_major = SNAPWEBSITES_VERSION_MAJOR;
1055 0 : f_version_minor = SNAPWEBSITES_VERSION_MINOR;
1056 :
1057 : // too bad that dlopen() returns opaque handles only...
1058 : // that being said, we can still walk the walk and
1059 : // find the full path to the currently running binary
1060 : //
1061 : struct unknown_structures // <- yuck
1062 : {
1063 : void * f_unused[3];
1064 : struct unknown_structures * f_pointer;
1065 : };
1066 0 : struct unknown_structures * p(reinterpret_cast<struct unknown_structures *>(dlopen(nullptr, RTLD_LAZY | RTLD_GLOBAL)));
1067 0 : if(p != nullptr)
1068 : {
1069 0 : p = p->f_pointer;
1070 0 : if(p != nullptr)
1071 : {
1072 0 : struct link_map * map(reinterpret_cast<struct link_map *>(p->f_pointer));
1073 0 : if(map != nullptr)
1074 : {
1075 0 : if(map->l_name != nullptr)
1076 : {
1077 : // full path to the snapwebsites.so library
1078 0 : f_filename = QString::fromUtf8(map->l_name);
1079 :
1080 0 : if(!f_filename.isEmpty())
1081 : {
1082 : struct stat s;
1083 0 : if(stat(f_filename.toUtf8().data(), &s) == 0)
1084 : {
1085 : // should we make use of the usec mtime when available?
1086 0 : f_last_modification = static_cast<int64_t>(s.st_mtime * 1000000LL);
1087 : }
1088 : }
1089 : }
1090 : }
1091 : }
1092 : }
1093 0 : return;
1094 : }
1095 :
1096 0 : snap_string_list const paths(plugin_paths.split(':'));
1097 0 : QString const filename(find_plugin_filename(paths, name));
1098 0 : if(filename.isEmpty())
1099 : {
1100 0 : throw plugin_exception(QString("plugin named \"%1\" not found.").arg(name));
1101 : }
1102 :
1103 : // "normal" load of the plugin... (We do not really have a choice)
1104 : //
1105 : // Note that this is the normal low level load, that means the plugin
1106 : // will not get its bootstrap() and other initialization functions
1107 : // called... we will be limited to a very small number of functions.
1108 : //
1109 : // XXX: allow for the non-adding to the global list?
1110 : // in general we should be fine since their signals will not
1111 : // be installed, however, the plugin_exists() function will
1112 : // return 'true', which is not correct
1113 : //
1114 0 : g_next_register_name = name;
1115 0 : g_next_register_filename = filename;
1116 0 : void const * const h(dlopen(filename.toUtf8().data(), RTLD_LAZY | RTLD_GLOBAL));
1117 0 : if(h == nullptr)
1118 : {
1119 0 : int const e(errno);
1120 0 : std::stringstream ss;
1121 0 : ss << "error: cannot load plugin file \"" << filename << "\" (errno: " << e << ", " << dlerror() << ")";
1122 0 : throw plugin_exception(ss.str());
1123 : }
1124 0 : g_next_register_name.clear();
1125 0 : g_next_register_filename.clear();
1126 :
1127 0 : plugin * p(get_plugin(name));
1128 0 : if(p == nullptr)
1129 : {
1130 0 : std::stringstream ss;
1131 0 : ss << "error: cannot find plugin \"" << name << "\", even though the loading was successful.";
1132 0 : throw plugin_exception(ss.str());
1133 : }
1134 :
1135 0 : f_name = name;
1136 0 : f_filename = filename;
1137 0 : f_last_modification = p->last_modification();
1138 0 : f_icon = p->icon();
1139 0 : f_description = p->description();
1140 0 : f_categorization_tags = p->plugin_categorization_tags();
1141 0 : f_help_uri = p->help_uri();
1142 0 : f_settings_path = p->settings_path();
1143 0 : f_dependencies = p->dependencies();
1144 0 : f_version_major = p->get_major_version();
1145 0 : f_version_minor = p->get_minor_version();
1146 : }
1147 :
1148 :
1149 : /** \brief Retrieve the name of the plugin.
1150 : *
1151 : * This function returns the name of this plugin.
1152 : *
1153 : * \return The name of this plugin.
1154 : */
1155 0 : QString const & plugin_info::get_name() const
1156 : {
1157 0 : return f_name;
1158 : }
1159 :
1160 :
1161 : /** \brief Get the filename of the plugin.
1162 : *
1163 : * Advanced informaton, the plugin path on the server. This is really
1164 : * only useful for developers and administrators to make sure things
1165 : * are in the right place.
1166 : *
1167 : * \return The path to this plugin.
1168 : */
1169 0 : QString const & plugin_info::get_filename() const
1170 : {
1171 0 : return f_filename;
1172 : }
1173 :
1174 :
1175 : /** \brief Get the last modification time of this plugin.
1176 : *
1177 : * This function reads the mtime in microseconds of the file attached
1178 : * to this plugin and returns that value.
1179 : *
1180 : * Assuming people do not temper with the modification of the file,
1181 : * this represents the last time the plugin was compiled and packaged.
1182 : *
1183 : * \return The time of the last modifications of this plugin in microseconds.
1184 : */
1185 0 : int64_t plugin_info::get_last_modification() const
1186 : {
1187 0 : return f_last_modification;
1188 : }
1189 :
1190 :
1191 : /** \brief Retrieve path to the icon.
1192 : *
1193 : * This function returns the local path to the icon representing this plugin.
1194 : *
1195 : * \return A path to the plugin icon.
1196 : */
1197 0 : QString const & plugin_info::get_icon() const
1198 : {
1199 0 : return f_icon;
1200 : }
1201 :
1202 :
1203 : /** \brief Retrieve URI to the help page for this plugin.
1204 : *
1205 : * This function returns the URI to the help page for this plugin.
1206 : * In most cases this is a URI to an external website that describes
1207 : * the plugin in details: what it does, what it does not do, what
1208 : * the settings are for, how to set it up in this way or that way, etc.
1209 : *
1210 : * \return The URI to the help page.
1211 : */
1212 0 : QString const & plugin_info::get_help_uri() const
1213 : {
1214 0 : return f_help_uri;
1215 : }
1216 :
1217 :
1218 : /** \brief Retrieve path to the settings page for this plugin.
1219 : *
1220 : * This function returns the path to the main settings of this plugin.
1221 : *
1222 : * \return The path to the settings page.
1223 : */
1224 0 : QString const & plugin_info::get_settings_path() const
1225 : {
1226 0 : return f_settings_path;
1227 : }
1228 :
1229 :
1230 : /** \brief Retrieve the plugin description.
1231 : *
1232 : * Each plugin has a small description defined in the code. This
1233 : * description is shown when you have to deal with the plugin
1234 : * in such places as the page allowing you to install / uninstall
1235 : * the plugin.
1236 : *
1237 : * \return The description of the plugin.
1238 : */
1239 0 : QString const & plugin_info::get_description() const
1240 : {
1241 0 : return f_description;
1242 : }
1243 :
1244 :
1245 : /** \brief Retrieve the plugin last of tags used for categorization.
1246 : *
1247 : * Each plugin can be given a set of tags, comma separated, to define
1248 : * its category. For example, core plugins are generally marked with
1249 : * the "core" tag. The "base" tag is generally used by the set of
1250 : * plugins that one cannot uninstall because otherwise the system
1251 : * would break. Etc.
1252 : *
1253 : * \return The list of comma separated tags.
1254 : */
1255 0 : QString const & plugin_info::get_plugin_categorization_tags() const
1256 : {
1257 0 : return f_categorization_tags;
1258 : }
1259 :
1260 :
1261 : /** \brief Retrieve the plugin dependencies.
1262 : *
1263 : * Most plugins have a set of dependencies which must be
1264 : * initialized before they themselves get initialized. This
1265 : * way they will receive events after those dependencies.
1266 : * In other words, we have a system which is well defined
1267 : * (deterministic in terms of who receives what and when.)
1268 : *
1269 : * This function returns the raw string defined in the plugins
1270 : * which means the list of plugin names are written between
1271 : * pipes (|) characters.
1272 : *
1273 : * \return The dependencies of the plugin.
1274 : */
1275 0 : QString const & plugin_info::get_dependencies() const
1276 : {
1277 0 : return f_dependencies;
1278 : }
1279 :
1280 :
1281 : /** \brief Retrieve the major version of the plugin.
1282 : *
1283 : * This function returns the major version of the plugin as a number.
1284 : *
1285 : * \return The major version of this plugin.
1286 : */
1287 0 : int32_t plugin_info::get_version_major() const
1288 : {
1289 0 : return f_version_major;
1290 : }
1291 :
1292 :
1293 : /** \brief Retrieve the minor version of the plugin.
1294 : *
1295 : * This function returns the minor version of the plugin as a number.
1296 : *
1297 : * \return The minor version of this plugin.
1298 : */
1299 0 : int32_t plugin_info::get_version_minor() const
1300 : {
1301 0 : return f_version_minor;
1302 : }
1303 :
1304 :
1305 :
1306 :
1307 : } // namespace plugins
1308 6 : } // namespace snap
1309 : // vim: ts=4 sw=4 et
|