vlib: add plugin override support
[vpp.git] / src / vlib / unix / plugin.c
1 /*
2  * plugin.c: plugin handling
3  *
4  * Copyright (c) 2011 Cisco and/or its affiliates.
5  * Licensed under the Apache License, Version 2.0 (the "License");
6  * you may not use this file except in compliance with the License.
7  * You may obtain a copy of the License at:
8  *
9  *     http://www.apache.org/licenses/LICENSE-2.0
10  *
11  * Unless required by applicable law or agreed to in writing, software
12  * distributed under the License is distributed on an "AS IS" BASIS,
13  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14  * See the License for the specific language governing permissions and
15  * limitations under the License.
16  */
17
18 #include <vlib/unix/plugin.h>
19 #include <vppinfra/elf.h>
20 #include <dlfcn.h>
21 #include <dirent.h>
22
23 plugin_main_t vlib_plugin_main;
24
25 #define PLUGIN_LOG_DBG(...) \
26   do {vlib_log_debug (vlib_plugin_main.logger, __VA_ARGS__);} while(0)
27 #define PLUGIN_LOG_ERR(...) \
28   do {vlib_log_err (vlib_plugin_main.logger, __VA_ARGS__);} while(0)
29 #define PLUGIN_LOG_NOTICE(...) \
30   do {vlib_log_notice (vlib_plugin_main.logger, __VA_ARGS__);} while(0)
31
32 char *vlib_plugin_path __attribute__ ((weak));
33 char *vlib_plugin_path = "";
34 char *vlib_plugin_app_version __attribute__ ((weak));
35 char *vlib_plugin_app_version = "";
36
37 void *
38 vlib_get_plugin_symbol (char *plugin_name, char *symbol_name)
39 {
40   plugin_main_t *pm = &vlib_plugin_main;
41   uword *p;
42   plugin_info_t *pi;
43
44   if ((p = hash_get_mem (pm->plugin_by_name_hash, plugin_name)) == 0)
45     return 0;
46
47   pi = vec_elt_at_index (pm->plugin_info, p[0]);
48   return dlsym (pi->handle, symbol_name);
49 }
50
51 static char *
52 str_array_to_vec (char *array, int len)
53 {
54   char c, *r = 0;
55   int n = 0;
56
57   do
58     {
59       c = array[n];
60       vec_add1 (r, c);
61     }
62   while (c && ++n < len);
63
64   if (c)
65     vec_add1 (r, 0);
66
67   return r;
68 }
69
70 static u8 *
71 extract (u8 * sp, u8 * ep)
72 {
73   u8 *rv = 0;
74
75   while (sp <= ep)
76     {
77       vec_add1 (rv, *sp);
78       sp++;
79     }
80   vec_add1 (rv, 0);
81   return rv;
82 }
83
84 static int
85 load_one_plugin (plugin_main_t * pm, plugin_info_t * pi, int from_early_init)
86 {
87   void *handle;
88   clib_error_t *error;
89   elf_main_t em = { 0 };
90   elf_section_t *section;
91   u8 *data;
92   char *version_required;
93   vlib_plugin_registration_t *reg;
94   plugin_config_t *pc = 0;
95   uword *p;
96
97   if (elf_read_file (&em, (char *) pi->filename))
98     return -1;
99
100   error = elf_get_section_by_name (&em, ".vlib_plugin_registration",
101                                    &section);
102   if (error)
103     {
104       PLUGIN_LOG_ERR ("Not a plugin: %s\n", (char *) pi->name);
105       return -1;
106     }
107
108   data = elf_get_section_contents (&em, section->index, 1);
109   reg = (vlib_plugin_registration_t *) data;
110
111   if (vec_len (data) != sizeof (*reg))
112     {
113       PLUGIN_LOG_ERR ("vlib_plugin_registration size mismatch in plugin %s\n",
114                       (char *) pi->name);
115       goto error;
116     }
117
118   if (pm->plugins_default_disable)
119     reg->default_disabled = 1;
120
121   p = hash_get_mem (pm->config_index_by_name, pi->name);
122   if (p)
123     {
124       pc = vec_elt_at_index (pm->configs, p[0]);
125       if (pc->is_disabled)
126         {
127           PLUGIN_LOG_NOTICE ("Plugin disabled: %s", pi->name);
128           goto error;
129         }
130       if (reg->default_disabled && pc->is_enabled == 0)
131         {
132           PLUGIN_LOG_NOTICE ("Plugin disabled (default): %s", pi->name);
133           goto error;
134         }
135     }
136   else if (reg->default_disabled)
137     {
138       PLUGIN_LOG_NOTICE ("Plugin disabled (default): %s", pi->name);
139       goto error;
140     }
141
142   version_required = str_array_to_vec ((char *) &reg->version_required,
143                                        sizeof (reg->version_required));
144
145   if ((strlen (version_required) > 0) &&
146       (strncmp (vlib_plugin_app_version, version_required,
147                 strlen (version_required))))
148     {
149       PLUGIN_LOG_ERR ("Plugin %s version mismatch: %s != %s",
150                       pi->name, vlib_plugin_app_version,
151                       reg->version_required);
152       if (!(pc && pc->skip_version_check == 1))
153         {
154           vec_free (version_required);
155           goto error;
156         }
157     }
158
159   /*
160    * Collect names of plugins overridden (disabled) by the
161    * current plugin.
162    */
163   if (reg->overrides[0])
164     {
165       const char *overrides = reg->overrides;
166       u8 *override_name_copy, *overridden_by_name_copy;
167       u8 *sp, *ep;
168       uword *p;
169
170       sp = ep = (u8 *) overrides;
171
172       while (1)
173         {
174           if (*sp == 0
175               || (sp >= (u8 *) overrides + ARRAY_LEN (reg->overrides)))
176             break;
177           if (*sp == ' ' || *sp == ',')
178             {
179               sp++;
180               continue;
181             }
182           ep = sp;
183           while (*ep && *ep != ' ' && *ep != ',' &&
184                  ep < (u8 *) overrides + ARRAY_LEN (reg->overrides))
185             ep++;
186           if (*ep == ' ' || *ep == ',')
187             ep--;
188
189           override_name_copy = extract (sp, ep);
190
191
192           p = hash_get_mem (pm->plugin_overrides_by_name_hash,
193                             override_name_copy);
194           /* Already overridden... */
195           if (p)
196             vec_free (override_name_copy);
197           else
198             {
199               overridden_by_name_copy = format (0, "%s%c", pi->name, 0);
200               hash_set_mem (pm->plugin_overrides_by_name_hash,
201                             override_name_copy, overridden_by_name_copy);
202             }
203           sp = *ep ? ep + 1 : ep;
204         }
205     }
206   vec_free (version_required);
207
208   handle = dlopen ((char *) pi->filename, RTLD_LAZY);
209
210   if (handle == 0)
211     {
212       PLUGIN_LOG_ERR ("%s", dlerror ());
213       PLUGIN_LOG_ERR ("Failed to load plugin '%s'", pi->name);
214       goto error;
215     }
216
217   pi->handle = handle;
218
219   reg = dlsym (pi->handle, "vlib_plugin_registration");
220
221   if (reg == 0)
222     {
223       /* This should never happen unless somebody changes registration macro */
224       PLUGIN_LOG_ERR ("Missing plugin registration in plugin '%s'", pi->name);
225       dlclose (pi->handle);
226       goto error;
227     }
228
229   pi->reg = reg;
230   pi->version = str_array_to_vec ((char *) &reg->version,
231                                   sizeof (reg->version));
232
233   if (reg->early_init)
234     {
235       clib_error_t *(*ei) (vlib_main_t *);
236       void *h;
237
238       h = dlsym (pi->handle, reg->early_init);
239       if (h)
240         {
241           ei = h;
242           error = (*ei) (pm->vlib_main);
243           if (error)
244             {
245               u8 *err = format (0, "%s: %U%c", pi->name,
246                                 format_clib_error, error, 0);
247               PLUGIN_LOG_ERR ((char *) err);
248               clib_error_free (error);
249               dlclose (pi->handle);
250               pi->handle = 0;
251               goto error;
252             }
253         }
254       else
255         PLUGIN_LOG_ERR ("Plugin %s: early init function %s set but not found",
256                         (char *) pi->name, reg->early_init);
257     }
258
259   if (reg->description)
260     PLUGIN_LOG_NOTICE ("Loaded plugin: %s (%s)", pi->name, reg->description);
261   else
262     PLUGIN_LOG_NOTICE ("Loaded plugin: %s", pi->name);
263
264   vec_free (data);
265   elf_main_free (&em);
266   return 0;
267
268 error:
269   vec_free (data);
270   elf_main_free (&em);
271   return -1;
272 }
273
274 static u8 **
275 split_plugin_path (plugin_main_t * pm)
276 {
277   int i;
278   u8 **rv = 0;
279   u8 *path = pm->plugin_path;
280   u8 *this = 0;
281
282   for (i = 0; i < vec_len (pm->plugin_path); i++)
283     {
284       if (path[i] != ':')
285         {
286           vec_add1 (this, path[i]);
287           continue;
288         }
289       vec_add1 (this, 0);
290       vec_add1 (rv, this);
291       this = 0;
292     }
293   if (this)
294     {
295       vec_add1 (this, 0);
296       vec_add1 (rv, this);
297     }
298   return rv;
299 }
300
301 static int
302 plugin_name_sort_cmp (void *a1, void *a2)
303 {
304   plugin_info_t *p1 = a1;
305   plugin_info_t *p2 = a2;
306
307   return strcmp ((char *) p1->name, (char *) p2->name);
308 }
309
310 static int
311 index_cmp (void *a1, void *a2)
312 {
313   uword *i1 = (uword *) a1, *i2 = (uword *) a2;
314
315   if (*i1 < *i2)
316     return -1;
317   else if (*i1 > *i2)
318     return 1;
319   else
320     return 0;
321 }
322
323 int
324 vlib_load_new_plugins (plugin_main_t * pm, int from_early_init)
325 {
326   DIR *dp;
327   struct dirent *entry;
328   struct stat statb;
329   uword *p;
330   plugin_info_t *pi;
331   u8 **plugin_path;
332   uword *not_loaded_indices = 0;
333   int i;
334
335   plugin_path = split_plugin_path (pm);
336
337   for (i = 0; i < vec_len (plugin_path); i++)
338     {
339       dp = opendir ((char *) plugin_path[i]);
340
341       if (dp == 0)
342         continue;
343
344       while ((entry = readdir (dp)))
345         {
346           u8 *plugin_name;
347           u8 *filename;
348
349           if (pm->plugin_name_filter)
350             {
351               int j;
352               for (j = 0; j < vec_len (pm->plugin_name_filter); j++)
353                 if (entry->d_name[j] != pm->plugin_name_filter[j])
354                   goto next;
355             }
356
357           filename = format (0, "%s/%s%c", plugin_path[i], entry->d_name, 0);
358
359           /* Only accept .so */
360           char *ext = strrchr ((const char *) filename, '.');
361           /* unreadable */
362           if (!ext || (strcmp (ext, ".so") != 0) ||
363               stat ((char *) filename, &statb) < 0)
364             {
365             ignore:
366               vec_free (filename);
367               continue;
368             }
369
370           /* a dir or other things which aren't plugins */
371           if (!S_ISREG (statb.st_mode))
372             goto ignore;
373
374           plugin_name = format (0, "%s%c", entry->d_name, 0);
375           /* Have we seen this plugin already? */
376           p = hash_get_mem (pm->plugin_by_name_hash, plugin_name);
377           if (p == 0)
378             {
379               /* No, add it to the plugin vector */
380               vec_add2 (pm->plugin_info, pi, 1);
381               pi->name = plugin_name;
382               pi->filename = filename;
383               pi->file_info = statb;
384               pi->handle = 0;
385               hash_set_mem (pm->plugin_by_name_hash, plugin_name,
386                             pi - pm->plugin_info);
387             }
388         next:
389           ;
390         }
391       closedir (dp);
392       vec_free (plugin_path[i]);
393     }
394   vec_free (plugin_path);
395
396
397   /*
398    * Sort the plugins by name. This is important.
399    * API traces contain absolute message numbers.
400    * Loading plugins in directory (vs. alphabetical) order
401    * makes trace replay incredibly fragile.
402    */
403   vec_sort_with_function (pm->plugin_info, plugin_name_sort_cmp);
404
405   /*
406    * Attempt to load the plugins
407    */
408   for (i = 0; i < vec_len (pm->plugin_info); i++)
409     {
410       pi = vec_elt_at_index (pm->plugin_info, i);
411
412       if (load_one_plugin (pm, pi, from_early_init))
413         {
414           /* Make a note of any which fail to load */
415           vec_add1 (not_loaded_indices, i);
416         }
417     }
418
419   /*
420    * Honor override list
421    */
422   for (i = 0; i < vec_len (pm->plugin_info); i++)
423     {
424       uword *p;
425
426       pi = vec_elt_at_index (pm->plugin_info, i);
427
428       p = hash_get_mem (pm->plugin_overrides_by_name_hash, pi->name);
429
430       /* Plugin overridden? */
431       if (p)
432         {
433           PLUGIN_LOG_NOTICE ("Plugin '%s' overridden by '%s'", pi->name,
434                              p[0]);
435           vec_add1 (not_loaded_indices, i);
436         }
437     }
438
439   /*
440    * Sort the vector of indices to delete to avoid screwing up
441    * the indices as we delete them.
442    */
443   vec_sort_with_function (not_loaded_indices, index_cmp);
444
445   /*
446    * Remove duplicates, which can happen if a plugin is
447    * disabled from the command line and disabled by
448    * a plugin which is loaded.
449    */
450   for (i = 0; i < vec_len (not_loaded_indices); i++)
451     {
452       if (i < vec_len (not_loaded_indices) - 1)
453         {
454           if (not_loaded_indices[i + 1] == not_loaded_indices[i])
455             {
456               vec_delete (not_loaded_indices, 1, i);
457               i--;
458             }
459         }
460     }
461
462   /* Remove plugin info vector elements corresponding to load failures */
463   if (vec_len (not_loaded_indices) > 0)
464     {
465       for (i = vec_len (not_loaded_indices) - 1; i >= 0; i--)
466         {
467           pi = vec_elt_at_index (pm->plugin_info, not_loaded_indices[i]);
468           hash_unset_mem (pm->plugin_by_name_hash, pi->name);
469           if (pi->handle)
470             {
471               dlclose (pi->handle);
472               PLUGIN_LOG_NOTICE ("Unloaded plugin: %s", pi->name);
473             }
474           vec_free (pi->name);
475           vec_free (pi->filename);
476           vec_delete (pm->plugin_info, 1, not_loaded_indices[i]);
477         }
478       vec_free (not_loaded_indices);
479     }
480
481   /* Recreate the plugin name hash */
482   hash_free (pm->plugin_by_name_hash);
483   pm->plugin_by_name_hash = hash_create_string (0, sizeof (uword));
484
485   for (i = 0; i < vec_len (pm->plugin_info); i++)
486     {
487       pi = vec_elt_at_index (pm->plugin_info, i);
488       hash_set_mem (pm->plugin_by_name_hash, pi->name, pi - pm->plugin_info);
489     }
490
491   return 0;
492 }
493
494 int
495 vlib_plugin_early_init (vlib_main_t * vm)
496 {
497   plugin_main_t *pm = &vlib_plugin_main;
498
499   pm->logger =
500     vlib_log_register_class_rate_limit ("plugin", "load",
501                                         0x7FFFFFFF /* aka no rate limit */ );
502
503   if (pm->plugin_path == 0)
504     pm->plugin_path = format (0, "%s%c", vlib_plugin_path, 0);
505
506   PLUGIN_LOG_DBG ("plugin path %s", pm->plugin_path);
507
508   pm->plugin_by_name_hash = hash_create_string (0, sizeof (uword));
509   pm->plugin_overrides_by_name_hash = hash_create_string (0, sizeof (uword));
510   pm->vlib_main = vm;
511
512   return vlib_load_new_plugins (pm, 1 /* from_early_init */ );
513 }
514
515 u8 *
516 vlib_get_vat_plugin_path (void)
517 {
518   plugin_main_t *pm = &vlib_plugin_main;
519   return (pm->vat_plugin_path);
520 }
521
522 u8 *
523 vlib_get_vat_plugin_name_filter (void)
524 {
525   plugin_main_t *pm = &vlib_plugin_main;
526   return (pm->vat_plugin_name_filter);
527 }
528
529 static clib_error_t *
530 vlib_plugins_show_cmd_fn (vlib_main_t * vm,
531                           unformat_input_t * input, vlib_cli_command_t * cmd)
532 {
533   plugin_main_t *pm = &vlib_plugin_main;
534   u8 *s = 0;
535   u8 *key = 0;
536   uword value = 0;
537   int index = 1;
538   plugin_info_t *pi;
539
540   s = format (s, " Plugin path is: %s\n\n", pm->plugin_path);
541   s = format (s, "     %-41s%-33s%s\n", "Plugin", "Version", "Description");
542
543   /* *INDENT-OFF* */
544   hash_foreach_mem (key, value, pm->plugin_by_name_hash,
545     {
546       if (key != 0)
547         {
548           pi = vec_elt_at_index (pm->plugin_info, value);
549           s = format (s, "%3d. %-40s %-32s %s\n", index, key, pi->version,
550                       (pi->reg && pi->reg->description) ?
551                       pi->reg->description : "");
552           index++;
553         }
554     });
555   /* *INDENT-ON* */
556
557   vlib_cli_output (vm, "%v", s);
558   vec_free (s);
559   return 0;
560 }
561
562 /* *INDENT-OFF* */
563 VLIB_CLI_COMMAND (plugins_show_cmd, static) =
564 {
565   .path = "show plugins",
566   .short_help = "show loaded plugins",
567   .function = vlib_plugins_show_cmd_fn,
568 };
569 /* *INDENT-ON* */
570
571 static clib_error_t *
572 config_one_plugin (vlib_main_t * vm, char *name, unformat_input_t * input)
573 {
574   plugin_main_t *pm = &vlib_plugin_main;
575   plugin_config_t *pc;
576   clib_error_t *error = 0;
577   uword *p;
578   int is_enable = 0;
579   int is_disable = 0;
580   int skip_version_check = 0;
581
582   if (pm->config_index_by_name == 0)
583     pm->config_index_by_name = hash_create_string (0, sizeof (uword));
584
585   p = hash_get_mem (pm->config_index_by_name, name);
586
587   if (p)
588     {
589       error = clib_error_return (0, "plugin '%s' already configured", name);
590       goto done;
591     }
592
593   while (unformat_check_input (input) != UNFORMAT_END_OF_INPUT)
594     {
595       if (unformat (input, "enable"))
596         is_enable = 1;
597       else if (unformat (input, "disable"))
598         is_disable = 1;
599       else if (unformat (input, "skip-version-check"))
600         skip_version_check = 1;
601       else
602         {
603           error = clib_error_return (0, "unknown input '%U'",
604                                      format_unformat_error, input);
605           goto done;
606         }
607     }
608
609   if (is_enable && is_disable)
610     {
611       error = clib_error_return (0, "please specify either enable or disable"
612                                  " for plugin '%s'", name);
613       goto done;
614     }
615
616   vec_add2 (pm->configs, pc, 1);
617   hash_set_mem (pm->config_index_by_name, name, pc - pm->configs);
618   pc->is_enabled = is_enable;
619   pc->is_disabled = is_disable;
620   pc->skip_version_check = skip_version_check;
621   pc->name = name;
622
623 done:
624   return error;
625 }
626
627 clib_error_t *
628 vlib_plugin_config (vlib_main_t * vm, unformat_input_t * input)
629 {
630   plugin_main_t *pm = &vlib_plugin_main;
631   clib_error_t *error = 0;
632   unformat_input_t in;
633
634   unformat_init (&in, 0, 0);
635
636   while (unformat_check_input (input) != UNFORMAT_END_OF_INPUT)
637     {
638       u8 *s, *v;
639       if (unformat (input, "%s %v", &s, &v))
640         {
641           if (strncmp ((const char *) s, "plugins", 8) == 0)
642             {
643               if (vec_len (in.buffer) > 0)
644                 vec_add1 (in.buffer, ' ');
645               vec_add (in.buffer, v, vec_len (v));
646             }
647         }
648       else
649         {
650           error = clib_error_return (0, "unknown input '%U'",
651                                      format_unformat_error, input);
652           goto done;
653         }
654
655       vec_free (v);
656       vec_free (s);
657     }
658 done:
659   input = &in;
660   while (unformat_check_input (input) != UNFORMAT_END_OF_INPUT)
661     {
662       unformat_input_t sub_input;
663       u8 *s = 0;
664       if (unformat (input, "path %s", &s))
665         pm->plugin_path = s;
666       else if (unformat (input, "name-filter %s", &s))
667         pm->plugin_name_filter = s;
668       else if (unformat (input, "vat-path %s", &s))
669         pm->vat_plugin_path = s;
670       else if (unformat (input, "vat-name-filter %s", &s))
671         pm->vat_plugin_name_filter = s;
672       else if (unformat (input, "plugin default %U",
673                          unformat_vlib_cli_sub_input, &sub_input))
674         {
675           pm->plugins_default_disable =
676             unformat (&sub_input, "disable") ? 1 : 0;
677           unformat_free (&sub_input);
678         }
679       else if (unformat (input, "plugin %s %U", &s,
680                          unformat_vlib_cli_sub_input, &sub_input))
681         {
682           error = config_one_plugin (vm, (char *) s, &sub_input);
683           unformat_free (&sub_input);
684           if (error)
685             goto done2;
686         }
687       else
688         {
689           error = clib_error_return (0, "unknown input '%U'",
690                                      format_unformat_error, input);
691           {
692             vec_free (s);
693             goto done2;
694           }
695         }
696     }
697
698 done2:
699   unformat_free (&in);
700   return error;
701 }
702
703 /* discard whole 'plugins' section, as it is already consumed prior to
704    plugin load */
705 static clib_error_t *
706 plugins_config (vlib_main_t * vm, unformat_input_t * input)
707 {
708   u8 *junk;
709
710   while (unformat_check_input (input) != UNFORMAT_END_OF_INPUT)
711     {
712       if (unformat (input, "%s", &junk))
713         {
714           vec_free (junk);
715           return 0;
716         }
717       else
718         return clib_error_return (0, "unknown input '%U'",
719                                   format_unformat_error, input);
720     }
721   return 0;
722 }
723
724 VLIB_CONFIG_FUNCTION (plugins_config, "plugins");
725
726 /*
727  * fd.io coding-style-patch-verification: ON
728  *
729  * Local Variables:
730  * eval: (c-set-style "gnu")
731  * End:
732  */