1 /*
2 *
3 ***** BEGIN LICENSE BLOCK *****
4
5 Copyright (C) 2009-2016 Olof Hagsand and Benny Holmgren
6 Copyright (C) 2017-2019 Olof Hagsand
7 Copyright (C) 2020 Olof Hagsand and Rubicon Communications, LLC(Netgate)
8
9 This file is part of CLIXON.
10
11 Licensed under the Apache License, Version 2.0 (the "License");
12 you may not use this file except in compliance with the License.
13 You may obtain a copy of the License at
14
15 http://www.apache.org/licenses/LICENSE-2.0
16
17 Unless required by applicable law or agreed to in writing, software
18 distributed under the License is distributed on an "AS IS" BASIS,
19 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
20 See the License for the specific language governing permissions and
21 limitations under the License.
22
23 Alternatively, the contents of this file may be used under the terms of
24 the GNU General Public License Version 3 or later (the "GPL"),
25 in which case the provisions of the GPL are applicable instead
26 of those above. If you wish to allow use of your version of this file only
27 under the terms of the GPL, and not to allow others to
28 use your version of this file under the terms of Apache License version 2,
29 indicate your decision by deleting the provisions above and replace them with
30 the notice and other provisions required by the GPL. If you do not delete
31 the provisions above, a recipient may use your version of this file under
32 the terms of any one of the Apache License version 2 or the GPL.
33
34 ***** END LICENSE BLOCK *****
35 */
36
37 #ifdef HAVE_CONFIG_H
38 #include "clixon_config.h" /* generated by config & autoconf */
39 #endif
40
41 #include <stdio.h>
42 #include <stdlib.h>
43 #include <stdint.h>
44 #include <string.h>
45 #include <errno.h>
46 #include <dlfcn.h>
47 #include <dirent.h>
48 #include <syslog.h>
49
50 #include <sys/stat.h>
51 #include <sys/param.h>
52
53 /* cligen */
54 #include <cligen/cligen.h>
55
56 #include "clixon_err.h"
57 #include "clixon_queue.h"
58 #include "clixon_hash.h"
59 #include "clixon_log.h"
60 #include "clixon_file.h"
61 #include "clixon_handle.h"
62 #include "clixon_yang.h"
63 #include "clixon_xml.h"
64 #include "clixon_xml_nsctx.h"
65 #include "clixon_yang_module.h"
66 #include "clixon_plugin.h"
67
68 /* List of plugins XXX
69 * 1. Place in clixon handle not global variables
70 * 2. Use qelem circular lists
71 */
72 static clixon_plugin *_clixon_plugins = NULL; /* List of plugins (of client) */
73 static int _clixon_nplugins = 0; /* Number of plugins */
74
75 /*! Iterator over clixon plugins
76 *
77 * @note Never manipulate the plugin during operation or using the
78 * same object recursively
79 *
80 * @param[in] h Clicon handle
81 * @param[in] plugin previous plugin, or NULL on init
82 * @code
83 * clicon_plugin *cp = NULL;
84 * while ((cp = clixon_plugin_each(h, cp)) != NULL) {
85 * ...
86 * }
87 * @endcode
88 * @note Not optimized, alwasy iterates from the start of the list
89 */
90 clixon_plugin *
clixon_plugin_each(clicon_handle h,clixon_plugin * cpprev)91 clixon_plugin_each(clicon_handle h,
92 clixon_plugin *cpprev)
93 {
94 int i;
95 clixon_plugin *cp;
96 clixon_plugin *cpnext = NULL;
97
98 if (cpprev == NULL)
99 cpnext = _clixon_plugins;
100 else{
101 for (i = 0; i < _clixon_nplugins; i++) {
102 cp = &_clixon_plugins[i];
103 if (cp == cpprev)
104 break;
105 cp = NULL;
106 }
107 if (cp && i < _clixon_nplugins-1)
108 cpnext = &_clixon_plugins[i+1];
109 }
110 return cpnext;
111 }
112
113 /*! Reverse iterator over clixon plugins, iterater from nr to 0
114 *
115 * @note Never manipulate the plugin during operation or using the
116 * same object recursively
117 *
118 * @param[in] h Clicon handle
119 * @param[in] plugin previous plugin, or NULL on init
120 * @code
121 * clicon_plugin *cp = NULL;
122 * while ((cp = clixon_plugin_each_revert(h, cp, nr)) != NULL) {
123 * ...
124 * }
125 * @endcode
126 * @note Not optimized, alwasy iterates from the start of the list
127 */
128 clixon_plugin *
clixon_plugin_each_revert(clicon_handle h,clixon_plugin * cpprev,int nr)129 clixon_plugin_each_revert(clicon_handle h,
130 clixon_plugin *cpprev,
131 int nr)
132 {
133 int i;
134 clixon_plugin *cp = NULL;
135 clixon_plugin *cpnext = NULL;
136
137 if (cpprev == NULL){
138 if (nr>0)
139 cpnext = &_clixon_plugins[nr-1];
140 }
141 else{
142 for (i = nr-1; i >= 0; i--) {
143 cp = &_clixon_plugins[i];
144 if (cp == cpprev)
145 break;
146 cp = NULL;
147 }
148 if (cp && i > 0)
149 cpnext = &_clixon_plugins[i-1];
150 }
151 return cpnext;
152 }
153
154 /*! Find plugin by name
155 * @param[in] h Clicon handle
156 * @param[in] name Plugin name
157 * @retval p Plugin if found
158 * @retval NULL Not found
159 * @code
160 * clixon_plugin *cp;
161 * cp = clixon_plugin_find(h, "plugin-name");
162 * @endcode
163 */
164 clixon_plugin *
clixon_plugin_find(clicon_handle h,const char * name)165 clixon_plugin_find(clicon_handle h,
166 const char *name)
167 {
168 int i;
169 clixon_plugin *cp = NULL;
170
171 for (i = 0; i < _clixon_nplugins; i++) {
172 cp = &_clixon_plugins[i];
173 if (strcmp(cp->cp_name, name) == 0)
174 return cp;
175 }
176 return NULL;
177 }
178
179 /*! Load a dynamic plugin object and call its init-function
180 * @param[in] h Clicon handle
181 * @param[in] file Which plugin to load
182 * @param[in] function Which function symbol to load and call
183 * @param[in] dlflags See man(3) dlopen
184 * @param[out] cpp Clixon plugin structure (if retval is 1)
185 * @retval 1 OK
186 * @retval 0 Failed load, log, skip and continue with other plugins
187 * @retval -1 Error
188 * @see clixon_plugins_load Load all plugins
189 */
190 static int
plugin_load_one(clicon_handle h,char * file,const char * function,int dlflags,clixon_plugin ** cpp)191 plugin_load_one(clicon_handle h,
192 char *file, /* note modified */
193 const char *function,
194 int dlflags,
195 clixon_plugin **cpp)
196 {
197 int retval = -1;
198 char *error;
199 void *handle = NULL;
200 plginit2_t *initfn;
201 clixon_plugin_api *api = NULL;
202 clixon_plugin *cp = NULL;
203 char *name;
204 char *p;
205
206 clicon_debug(1, "%s file:%s function:%s", __FUNCTION__, file, function);
207 dlerror(); /* Clear any existing error */
208 if ((handle = dlopen(file, dlflags)) == NULL) {
209 error = (char*)dlerror();
210 clicon_err(OE_PLUGIN, errno, "dlopen: %s", error ? error : "Unknown error");
211 goto done;
212 }
213 /* call plugin_init() if defined, eg CLIXON_PLUGIN_INIT or CLIXON_BACKEND_INIT */
214 if ((initfn = dlsym(handle, function)) == NULL){
215 clicon_err(OE_PLUGIN, errno, "Failed to find %s when loading clixon plugin %s", CLIXON_PLUGIN_INIT, file);
216 goto done;
217 }
218 if ((error = (char*)dlerror()) != NULL) {
219 clicon_err(OE_UNIX, 0, "dlsym: %s: %s", file, error);
220 goto done;
221 }
222 clicon_err_reset();
223 if ((api = initfn(h)) == NULL) {
224 if (!clicon_errno){ /* if clicon_err() is not called then log and continue */
225 clicon_log(LOG_DEBUG, "Warning: failed to initiate %s", strrchr(file,'/')?strchr(file, '/'):file);
226 retval = 0;
227 goto done;
228 }
229 else{
230 clicon_err(OE_PLUGIN, errno, "Failed to initiate %s", strrchr(file,'/')?strchr(file, '/'):file);
231 goto done;
232 }
233 }
234 /* Note: sizeof clixon_plugin_api which is largest of clixon_plugin_api:s */
235 if ((cp = (clixon_plugin *)malloc(sizeof(struct clixon_plugin))) == NULL){
236 clicon_err(OE_UNIX, errno, "malloc");
237 goto done;
238 }
239 memset(cp, 0, sizeof(struct clixon_plugin));
240 cp->cp_handle = handle;
241 /* Extract string after last '/' in filename, if any */
242 name = strrchr(file, '/') ? strrchr(file, '/')+1 : file;
243 /* strip extension, eg .so from name */
244 if ((p=strrchr(name, '.')) != NULL)
245 *p = '\0';
246 /* Copy name to struct */
247 snprintf(cp->cp_name, sizeof(cp->cp_name), "%*s",
248 (int)strlen(name), name);
249 cp->cp_api = *api;
250 clicon_debug(1, "%s", __FUNCTION__);
251 if (cp){
252 *cpp = cp;
253 cp = NULL;
254 }
255 retval = 1;
256 done:
257 if (retval != 1 && handle)
258 dlclose(handle);
259 if (cp)
260 free(cp);
261 return retval;
262 }
263
264 /*! Load a set of plugin objects from a directory and and call their init-function
265 * @param[in] h Clicon handle
266 * @param[in] function Which function symbol to load and call (eg CLIXON_PLUGIN_INIT)
267 * @param[in] dir Directory. .so files in this dir will be loaded.
268 * @param[in] regexp Regexp for matching files in plugin directory. Default *.so.
269 * @retval 0 OK
270 * @retval -1 Error
271 */
272 int
clixon_plugins_load(clicon_handle h,const char * function,const char * dir,const char * regexp)273 clixon_plugins_load(clicon_handle h,
274 const char *function,
275 const char *dir,
276 const char *regexp)
277 {
278 int retval = -1;
279 int ndp;
280 struct dirent *dp = NULL;
281 int i;
282 char filename[MAXPATHLEN];
283 clixon_plugin *cp = NULL;
284 int ret;
285
286 clicon_debug(1, "%s", __FUNCTION__);
287 /* Get plugin objects names from plugin directory */
288 if((ndp = clicon_file_dirent(dir, &dp, regexp?regexp:"(.so)$", S_IFREG)) < 0)
289 goto done;
290 /* Load all plugins */
291 for (i = 0; i < ndp; i++) {
292 snprintf(filename, MAXPATHLEN-1, "%s/%s", dir, dp[i].d_name);
293 clicon_debug(1, "DEBUG: Loading plugin '%.*s' ...",
294 (int)strlen(filename), filename);
295 if ((ret = plugin_load_one(h, filename, function, RTLD_NOW, &cp)) < 0)
296 goto done;
297 if (ret == 0)
298 continue;
299 _clixon_nplugins++;
300 if ((_clixon_plugins = realloc(_clixon_plugins, _clixon_nplugins*sizeof(clixon_plugin))) == NULL) {
301 clicon_err(OE_UNIX, errno, "realloc");
302 goto done;
303 }
304 _clixon_plugins[_clixon_nplugins-1] = *cp;
305 free(cp);
306 }
307 retval = 0;
308 done:
309 if (dp)
310 free(dp);
311 return retval;
312 }
313
314 /*! Create a pseudo plugin so that a main function can register callbacks
315 * @param[in] h Clicon handle
316 * @param[in] name Plugin name
317 * @param[out] cpp Clixon plugin structure (direct pointer)
318 * @retval 0 OK, with cpp set
319 * @retval -1 Error
320 */
321 int
clixon_pseudo_plugin(clicon_handle h,const char * name,clixon_plugin ** cpp)322 clixon_pseudo_plugin(clicon_handle h,
323 const char *name,
324 clixon_plugin **cpp)
325 {
326 int retval = -1;
327 clixon_plugin *cp = NULL;
328
329 clicon_debug(1, "%s", __FUNCTION__);
330
331 /* Create a pseudo plugins */
332 /* Note: sizeof clixon_plugin_api which is largest of clixon_plugin_api:s */
333 if ((cp = (clixon_plugin *)malloc(sizeof(struct clixon_plugin))) == NULL){
334 clicon_err(OE_UNIX, errno, "malloc");
335 goto done;
336 }
337 memset(cp, 0, sizeof(struct clixon_plugin));
338 snprintf(cp->cp_name, sizeof(cp->cp_name), "%*s", (int)strlen(name), name);
339
340 _clixon_nplugins++;
341 if ((_clixon_plugins = realloc(_clixon_plugins, _clixon_nplugins*sizeof(clixon_plugin))) == NULL) {
342 clicon_err(OE_UNIX, errno, "realloc");
343 goto done;
344 }
345 _clixon_plugins[_clixon_nplugins-1] = *cp;
346 *cpp = &_clixon_plugins[_clixon_nplugins-1];
347
348 retval = 0;
349 done:
350 if (cp)
351 free(cp);
352 return retval;
353 }
354
355 /*! Call single plugin start callback
356 * @param[in] cp Plugin handle
357 * @param[in] h Clixon handle
358 * @retval 0 OK
359 * @retval -1 Error
360 */
361 int
clixon_plugin_start_one(clixon_plugin * cp,clicon_handle h)362 clixon_plugin_start_one(clixon_plugin *cp,
363 clicon_handle h)
364 {
365 int retval = -1;
366 plgstart_t *fn; /* Plugin start */
367
368 if ((fn = cp->cp_api.ca_start) != NULL){
369 if (fn(h) < 0) {
370 if (clicon_errno < 0)
371 clicon_log(LOG_WARNING, "%s: Internal error: Start callback in plugin: %s returned -1 but did not make a clicon_err call",
372 __FUNCTION__, cp->cp_name);
373 goto done;
374 }
375 }
376 retval = 0;
377 done:
378 return retval;
379 }
380
381 /*! Call plugin_start in all plugins
382 * @param[in] h Clixon handle
383 * Call plugin start functions (if defined)
384 * @note Start functions can use clicon_argv_get() to get -- command line options
385 */
386 int
clixon_plugin_start_all(clicon_handle h)387 clixon_plugin_start_all(clicon_handle h)
388 {
389 int retval = -1;
390 clixon_plugin *cp = NULL;
391
392 while ((cp = clixon_plugin_each(h, cp)) != NULL) {
393 if (clixon_plugin_start_one(cp, h) < 0)
394 goto done;
395 }
396 retval = 0;
397 done:
398 return retval;
399 }
400
401 /*! Unload all plugins: call exit function and close shared handle
402 * @param[in] h Clicon handle
403 * @param[in] cp Plugin handle
404 * @param[in] h Clixon handle
405 * @retval 0 OK
406 * @retval -1 Error
407 */
408 int
clixon_plugin_exit_one(clixon_plugin * cp,clicon_handle h)409 clixon_plugin_exit_one(clixon_plugin *cp,
410 clicon_handle h)
411 {
412 int retval = -1;
413 char *error;
414 plgexit_t *fn;
415
416 if ((fn = cp->cp_api.ca_exit) != NULL){
417 if (fn(h) < 0) {
418 if (clicon_errno < 0)
419 clicon_log(LOG_WARNING, "%s: Internal error: Exit callback in plugin: %s returned -1 but did not make a clicon_err call",
420 __FUNCTION__, cp->cp_name);
421 goto done;
422 }
423 if (dlclose(cp->cp_handle) != 0) {
424 error = (char*)dlerror();
425 clicon_err(OE_PLUGIN, errno, "dlclose: %s", error ? error : "Unknown error");
426 }
427 }
428 retval = 0;
429 done:
430 return retval;
431 }
432
433 /*! Unload all plugins: call exit function and close shared handle
434 * @param[in] h Clixon handle
435 * @retval 0 OK
436 * @retval -1 Error
437 */
438 int
clixon_plugin_exit_all(clicon_handle h)439 clixon_plugin_exit_all(clicon_handle h)
440 {
441 int retval = -1;
442 clixon_plugin *cp = NULL;
443
444 while ((cp = clixon_plugin_each(h, cp)) != NULL) {
445 if (clixon_plugin_exit_one(cp, h) < 0)
446 goto done;
447 }
448 if (_clixon_plugins){
449 free(_clixon_plugins);
450 _clixon_plugins = NULL;
451 }
452 _clixon_nplugins = 0;
453 retval = 0;
454 done:
455 return retval;
456 }
457
458 /*! Run the restconf user-defined credentials callback
459 * @param[in] cp Plugin handle
460 * @param[in] h Clicon handle
461 * @param[in] arg Argument, such as fastcgi handler for restconf
462 * @retval -1 Error
463 * @retval 0 Not authenticated
464 * @retval 1 Authenticated
465 * @note If authenticated either a callback was called and clicon_username_set()
466 * Or no callback was found.
467 */
468 int
clixon_plugin_auth_one(clixon_plugin * cp,clicon_handle h,void * arg)469 clixon_plugin_auth_one(clixon_plugin *cp,
470 clicon_handle h,
471 void *arg)
472 {
473 int retval = 1; /* Authenticated */
474 plgauth_t *fn; /* Plugin auth */
475
476 if ((fn = cp->cp_api.ca_auth) != NULL){
477 if ((retval = fn(h, arg)) < 0) {
478 if (clicon_errno < 0)
479 clicon_log(LOG_WARNING, "%s: Internal error: Auth callback in plugin: %s returned -1 but did not make a clicon_err call",
480 __FUNCTION__, cp->cp_name);
481 goto done;
482 }
483 }
484 done:
485 return retval;
486 }
487
488 /*! Run the restconf user-defined credentials callback for all plugins
489 * Find first authentication callback and call that, then return.
490 * The callback is to set the authenticated user
491 * @param[in] cp Plugin handle
492 * @param[in] h Clicon handle
493 * @param[in] arg Argument, such as fastcgi handler for restconf
494 * @retval -1 Error
495 * @retval 0 Not authenticated
496 * @retval 1 Authenticated
497 * @note If authenticated either a callback was called and clicon_username_set()
498 * Or no callback was found.
499 */
500 int
clixon_plugin_auth_all(clicon_handle h,void * arg)501 clixon_plugin_auth_all(clicon_handle h,
502 void *arg)
503 {
504 int retval = -1;
505 clixon_plugin *cp = NULL;
506 int i = 0;
507 int ret;
508
509 while ((cp = clixon_plugin_each(h, cp)) != NULL) {
510 i++;
511 if ((ret = clixon_plugin_auth_one(cp, h, arg)) < 0)
512 goto done;
513 if (ret == 1)
514 goto authenticated;
515 break;
516 }
517 if (i==0)
518 retval = 1;
519 else
520 retval = 0;
521 done:
522 return retval;
523 authenticated:
524 retval = 1;
525 goto done;
526 }
527
528 /*! Callback for a yang extension (unknown) statement single plugin
529 * extension can be made.
530 * @param[in] cp Plugin handle
531 * @param[in] h Clixon handle
532 * @param[in] yext Yang node of extension
533 * @param[in] ys Yang node of (unknown) statement belonging to extension
534 * @retval 0 OK,
535 * @retval -1 Error
536 */
537 int
clixon_plugin_extension_one(clixon_plugin * cp,clicon_handle h,yang_stmt * yext,yang_stmt * ys)538 clixon_plugin_extension_one(clixon_plugin *cp,
539 clicon_handle h,
540 yang_stmt *yext,
541 yang_stmt *ys)
542 {
543 int retval = 1;
544 plgextension_t *fn; /* Plugin extension fn */
545
546 if ((fn = cp->cp_api.ca_extension) != NULL){
547 if (fn(h, yext, ys) < 0) {
548 if (clicon_errno < 0)
549 clicon_log(LOG_WARNING, "%s: Internal error: Extension callback in plugin: %s returned -1 but did not make a clicon_err call",
550 __FUNCTION__, cp->cp_name);
551 goto done;
552 }
553 }
554 retval = 0;
555 done:
556 return retval;
557 }
558
559 /*! Callback for a yang extension (unknown) statement in all plugins
560 * Called at parsing of yang module containing a statement of an extension.
561 * A plugin may identify the extension and perform actions
562 * on the yang statement, such as transforming the yang.
563 * A callback is made for every statement, which means that several calls per
564 * extension can be made.
565 * @param[in] h Clixon handle
566 * @param[in] yext Yang node of extension
567 * @param[in] ys Yang node of (unknown) statement belonging to extension
568 * @retval 0 OK, all callbacks executed OK
569 * @retval -1 Error in one callback
570 */
571 int
clixon_plugin_extension_all(clicon_handle h,yang_stmt * yext,yang_stmt * ys)572 clixon_plugin_extension_all(clicon_handle h,
573 yang_stmt *yext,
574 yang_stmt *ys)
575 {
576 int retval = -1;
577 clixon_plugin *cp = NULL;
578
579 while ((cp = clixon_plugin_each(h, cp)) != NULL) {
580 if (clixon_plugin_extension_one(cp, h, yext, ys) < 0)
581 goto done;
582 }
583 retval = 0;
584 done:
585 return retval;
586 }
587
588 /*! Call plugin general-purpose datastore upgrade in one plugin
589 *
590 * @param[in] cp Plugin handle
591 * @param[in] h Clicon handle
592 * @param[in] db Name of datastore, eg "running", "startup" or "tmp"
593 * @param[in] xt XML tree. Upgrade this "in place"
594 * @param[in] msd Module-state diff, info on datastore module-state
595 * @retval -1 Error
596 * @retval 0 OK
597 * Upgrade datastore on load before or as an alternative to module-specific upgrading mechanism
598 */
599 int
clixon_plugin_datastore_upgrade_one(clixon_plugin * cp,clicon_handle h,const char * db,cxobj * xt,modstate_diff_t * msd)600 clixon_plugin_datastore_upgrade_one(clixon_plugin *cp,
601 clicon_handle h,
602 const char *db,
603 cxobj *xt,
604 modstate_diff_t *msd)
605
606 {
607 int retval = -1;
608 datastore_upgrade_t *fn;
609
610 if ((fn = cp->cp_api.ca_datastore_upgrade) != NULL){
611 if (fn(h, db, xt, msd) < 0) {
612 if (clicon_errno < 0)
613 clicon_log(LOG_WARNING, "%s: Internal error: Datastore upgrade callback in plugin: %s returned -1 but did not make a clicon_err call",
614 __FUNCTION__, cp->cp_name);
615 goto done;
616 }
617 }
618 retval = 0;
619 done:
620 return retval;
621 }
622
623 /*! Call plugin general-purpose datastore upgrade in all plugins
624 *
625 * @param[in] h Clicon handle
626 * @param[in] db Name of datastore, eg "running", "startup" or "tmp"
627 * @param[in] xt XML tree. Upgrade this "in place"
628 * @param[in] msd Module-state diff, info on datastore module-state
629 * @retval -1 Error
630 * @retval 0 OK
631 * Upgrade datastore on load before or as an alternative to module-specific upgrading mechanism
632 */
633 int
clixon_plugin_datastore_upgrade_all(clicon_handle h,const char * db,cxobj * xt,modstate_diff_t * msd)634 clixon_plugin_datastore_upgrade_all(clicon_handle h,
635 const char *db,
636 cxobj *xt,
637 modstate_diff_t *msd)
638 {
639 int retval = -1;
640 clixon_plugin *cp = NULL;
641
642 while ((cp = clixon_plugin_each(h, cp)) != NULL) {
643 if (clixon_plugin_datastore_upgrade_one(cp, h, db, xt, msd) < 0)
644 goto done;
645 }
646 retval = 0;
647 done:
648 return retval;
649 }
650
651 /*--------------------------------------------------------------------
652 * RPC callbacks for both client/frontend and backend plugins.
653 * RPC callbacks are explicitly registered in the plugin_init() function
654 * with a tag and a function
655 * When the the tag is encountered, the callback is called.
656 * Primarily backend, but also netconf and restconf frontend plugins.
657 * CLI frontend so far have direct callbacks, ie functions in the cligen
658 * specification are directly dlsym:ed to the CLI plugin.
659 * It would be possible to use this rpc registering API for CLI plugins as well.
660 *
661 * When namespace and name match, the callback is made
662 */
663 typedef struct {
664 qelem_t rc_qelem; /* List header */
665 clicon_rpc_cb rc_callback; /* RPC Callback */
666 void *rc_arg; /* Application specific argument to cb */
667 char *rc_namespace;/* Namespace to combine with name tag */
668 char *rc_name; /* Xml/json tag/name */
669 } rpc_callback_t;
670
671 /* List of rpc callback entries XXX hang on handle */
672 static rpc_callback_t *rpc_cb_list = NULL;
673
674 /*! Register a RPC callback by appending a new RPC to the list
675 *
676 * @param[in] h clicon handle
677 * @param[in] cb Callback called
678 * @param[in] arg Domain-specific argument to send to callback
679 * @param[in] ns namespace of rpc
680 * @param[in] name RPC name
681 * @retval 0 OK
682 * @retval -1 Error
683 * @see rpc_callback_call which makes the actual callback
684 */
685 int
rpc_callback_register(clicon_handle h,clicon_rpc_cb cb,void * arg,const char * ns,const char * name)686 rpc_callback_register(clicon_handle h,
687 clicon_rpc_cb cb,
688 void *arg,
689 const char *ns,
690 const char *name)
691 {
692 rpc_callback_t *rc = NULL;
693
694 if (name == NULL || ns == NULL){
695 clicon_err(OE_DB, EINVAL, "name or namespace NULL");
696 goto done;
697 }
698 if ((rc = malloc(sizeof(rpc_callback_t))) == NULL) {
699 clicon_err(OE_DB, errno, "malloc: %s", strerror(errno));
700 goto done;
701 }
702 memset(rc, 0, sizeof(*rc));
703 rc->rc_callback = cb;
704 rc->rc_arg = arg;
705 rc->rc_namespace = strdup(ns);
706 rc->rc_name = strdup(name);
707 ADDQ(rc, rpc_cb_list);
708 return 0;
709 done:
710 if (rc){
711 if (rc->rc_namespace)
712 free(rc->rc_namespace);
713 if (rc->rc_name)
714 free(rc->rc_name);
715 free(rc);
716 }
717 return -1;
718 }
719
720 /*! Delete all RPC callbacks
721 */
722 int
rpc_callback_delete_all(clicon_handle h)723 rpc_callback_delete_all(clicon_handle h)
724 {
725 rpc_callback_t *rc;
726
727 while((rc = rpc_cb_list) != NULL) {
728 DELQ(rc, rpc_cb_list, rpc_callback_t *);
729 if (rc->rc_namespace)
730 free(rc->rc_namespace);
731 if (rc->rc_name)
732 free(rc->rc_name);
733 free(rc);
734 }
735 return 0;
736 }
737
738 /*! Search RPC callbacks and invoke if XML match with tag
739 *
740 * @param[in] h clicon handle
741 * @param[in] xn Sub-tree (under xorig) at child of rpc: <rpc><xn></rpc>.
742 * @param[out] cbret Return XML (as string in CLIgen buffer), error or OK
743 * @param[in] arg Domain-speific arg (eg client_entry)
744 * @retval -1 Error
745 * @retval 0 OK, not found handler.
746 * @retval n OK, <n> handler called
747 * @see rpc_callback_register which register a callback function
748 * @note that several callbacks can be registered. They need to cooperate on
749 * return values, ie if one writes cbret, the other needs to handle that by
750 * leaving it, replacing it or amending it.
751 */
752 int
rpc_callback_call(clicon_handle h,cxobj * xe,cbuf * cbret,void * arg)753 rpc_callback_call(clicon_handle h,
754 cxobj *xe,
755 cbuf *cbret,
756 void *arg)
757 {
758 int retval = -1;
759 rpc_callback_t *rc;
760 char *name;
761 char *prefix;
762 char *ns;
763 int nr = 0; /* How many callbacks */
764
765 if (rpc_cb_list == NULL)
766 return 0;
767 name = xml_name(xe);
768 prefix = xml_prefix(xe);
769 xml2ns(xe, prefix, &ns);
770 rc = rpc_cb_list;
771 do {
772 if (strcmp(rc->rc_name, name) == 0 &&
773 ns && rc->rc_namespace &&
774 strcmp(rc->rc_namespace, ns) == 0){
775 if (rc->rc_callback(h, xe, cbret, arg, rc->rc_arg) < 0){
776 clicon_debug(1, "%s Error in: %s", __FUNCTION__, rc->rc_name);
777 goto done;
778 }
779 nr++;
780 }
781 rc = NEXTQ(rpc_callback_t *, rc);
782 } while (rc != rpc_cb_list);
783 retval = nr; /* 0: none found, >0 nr of handlers called */
784 done:
785 clicon_debug(1, "%s retval:%d", __FUNCTION__, retval);
786 return retval;
787 }
788
789 /*--------------------------------------------------------------------
790 * Upgrade callbacks for backend upgrade of datastore
791 * Register upgrade callbacks in plugin_init() with a module and a "from" and "to"
792 * revision.
793 */
794 typedef struct {
795 qelem_t uc_qelem; /* List header */
796 clicon_upgrade_cb uc_callback; /* RPC Callback */
797 const char *uc_fnstr; /* Stringified fn name for debug */
798 void *uc_arg; /* Application specific argument to cb */
799 char *uc_namespace; /* Module namespace */
800 } upgrade_callback_t;
801
802 /* List of rpc callback entries XXX hang on handle */
803 static upgrade_callback_t *upgrade_cb_list = NULL;
804
805 /*! Register an upgrade callback by appending the new callback to the list
806 *
807 * @param[in] h clicon handle
808 * @param[in] cb Callback called
809 * @param[in] fnstr Stringified function for debug
810 * @param[in] arg Domain-specific argument to send to callback
811 * @param[in] ns Module namespace (if NULL all modules)
812 * @retval 0 OK
813 * @retval -1 Error
814 * @see upgrade_callback_call which makes the actual callback
815 */
816 int
upgrade_callback_reg_fn(clicon_handle h,clicon_upgrade_cb cb,const char * fnstr,const char * ns,void * arg)817 upgrade_callback_reg_fn(clicon_handle h,
818 clicon_upgrade_cb cb,
819 const char *fnstr,
820 const char *ns,
821 void *arg)
822 {
823 upgrade_callback_t *uc;
824
825 if ((uc = malloc(sizeof(upgrade_callback_t))) == NULL) {
826 clicon_err(OE_DB, errno, "malloc: %s", strerror(errno));
827 goto done;
828 }
829 memset(uc, 0, sizeof(*uc));
830 uc->uc_callback = cb;
831 uc->uc_fnstr = fnstr;
832 uc->uc_arg = arg;
833 if (ns)
834 uc->uc_namespace = strdup(ns);
835 ADDQ(uc, upgrade_cb_list);
836 return 0;
837 done:
838 if (uc){
839 if (uc->uc_namespace)
840 free(uc->uc_namespace);
841 free(uc);
842 }
843 return -1;
844 }
845
846 /*! Delete all Upgrade callbacks
847 */
848 int
upgrade_callback_delete_all(clicon_handle h)849 upgrade_callback_delete_all(clicon_handle h)
850 {
851 upgrade_callback_t *uc;
852
853 while((uc = upgrade_cb_list) != NULL) {
854 DELQ(uc, upgrade_cb_list, upgrade_callback_t *);
855 if (uc->uc_namespace)
856 free(uc->uc_namespace);
857 free(uc);
858 }
859 return 0;
860 }
861
862 /*! Upgrade specific module identified by namespace, search matching callbacks
863 *
864 * @param[in] h clicon handle
865 * @param[in] xt Top-level XML tree to be updated (includes other ns as well)
866 * @param[in] ns Namespace of module
867 * @param[in] op One of XML_FLAG_ADD, _DEL or _CHANGE
868 * @param[in] from From revision on the form YYYYMMDD (if DEL or CHANGE)
869 * @param[in] to To revision on the form YYYYMMDD (if ADD or CHANGE)
870 * @param[out] cbret Return XML (as string in CLIgen buffer), on invalid
871 * @retval -1 Error
872 * @retval 0 Invalid - cbret contains reason as netconf
873 * @retval 1 OK
874 * @see upgrade_callback_reg_fn which registers the callbacks
875 */
876 int
upgrade_callback_call(clicon_handle h,cxobj * xt,char * ns,uint16_t op,uint32_t from,uint32_t to,cbuf * cbret)877 upgrade_callback_call(clicon_handle h,
878 cxobj *xt,
879 char *ns,
880 uint16_t op,
881 uint32_t from,
882 uint32_t to,
883 cbuf *cbret)
884 {
885 int retval = -1;
886 upgrade_callback_t *uc;
887 int nr = 0; /* How many callbacks */
888 int ret;
889
890 if (upgrade_cb_list == NULL)
891 return 1;
892 uc = upgrade_cb_list;
893 do {
894 /* For matching an upgrade callback:
895 * - No module name registered (matches all modules) OR
896 * - Names match
897 * AND
898 * - No registered from revision (matches all revisions) OR
899 * - Registered from revision >= from AND
900 * - Registered to revision <= to (which includes case both 0)
901 */
902 if (uc->uc_namespace == NULL || strcmp(uc->uc_namespace, ns)==0){
903 if ((ret = uc->uc_callback(h, xt, ns, op, from, to, uc->uc_arg, cbret)) < 0){
904 clicon_debug(1, "%s Error in: %s", __FUNCTION__, uc->uc_namespace);
905 goto done;
906 }
907 if (ret == 0){
908 if (cbuf_len(cbret)==0){
909 clicon_err(OE_CFG, 0, "Validation fail %s(%s): cbret not set",
910 uc->uc_fnstr, ns);
911 goto done;
912 }
913 goto fail;
914 }
915 nr++;
916 }
917 uc = NEXTQ(upgrade_callback_t *, uc);
918 } while (uc != upgrade_cb_list);
919 retval = 1;
920 done:
921 clicon_debug(1, "%s retval:%d", __FUNCTION__, retval);
922 return retval;
923 fail:
924 retval =0;
925 goto done;
926 }
927
928