vlib: improvement to automatic core pinning
[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 (const char *plugin_name, const 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 /*
85  * If a plugin .so contains a ".vlib_plugin_r2" section,
86  * this function converts the vlib_plugin_r2_t to
87  * a vlib_plugin_registration_t.
88  */
89
90 static clib_error_t *
91 r2_to_reg (elf_main_t * em, vlib_plugin_r2_t * r2,
92            vlib_plugin_registration_t * reg)
93 {
94   clib_error_t *error;
95   elf_section_t *section;
96   uword data_segment_offset;
97   u8 *data;
98
99   /* It turns out that the strings land in the ".data" section */
100   error = elf_get_section_by_name (em, ".data", &section);
101   if (error)
102     return error;
103   data = elf_get_section_contents (em, section->index, 1);
104
105   /*
106    * Offsets in the ".vlib_plugin_r2" section
107    * need to have the data section base subtracted from them.
108    * The offset is in the first 8 bytes of the ".data" section
109    */
110
111   data_segment_offset = *((uword *) data);
112
113   /* Relocate pointers, subtract data_segment_offset */
114 #define _(a) r2->a.data_segment_offset -= data_segment_offset;
115   foreach_r2_string_field;
116 #undef _
117
118   if (r2->version.length >= ARRAY_LEN (reg->version) - 1)
119     return clib_error_return (0, "Version string too long");
120
121   if (r2->version_required.length >= ARRAY_LEN (reg->version_required) - 1)
122     return clib_error_return (0, "Version-required string too long");
123
124   if (r2->overrides.length >= ARRAY_LEN (reg->overrides) - 1)
125     return clib_error_return (0, "Override string too long");
126
127   /* Compatibility with C-initializer */
128   memcpy ((void *) reg->version, data + r2->version.data_segment_offset,
129           r2->version.length);
130   memcpy ((void *) reg->version_required,
131           data + r2->version_required.data_segment_offset,
132           r2->version_required.length);
133   memcpy ((void *) reg->overrides, data + r2->overrides.data_segment_offset,
134           r2->overrides.length);
135
136   if (r2->early_init.length > 0)
137     {
138       u8 *ei = 0;
139       vec_validate (ei, r2->early_init.length + 1);
140       memcpy (ei, data + r2->early_init.data_segment_offset,
141               r2->early_init.length);
142       reg->early_init = (void *) ei;
143     }
144
145   if (r2->description.length > 0)
146     {
147       u8 *desc = 0;
148       vec_validate (desc, r2->description.length + 1);
149       memcpy (desc, data + r2->description.data_segment_offset,
150               r2->description.length);
151       reg->description = (void *) desc;
152     }
153   vec_free (data);
154   return 0;
155 }
156
157
158 static int
159 load_one_plugin (plugin_main_t * pm, plugin_info_t * pi, int from_early_init)
160 {
161   void *handle;
162   int reread_reg = 1;
163   clib_error_t *error;
164   elf_main_t em = { 0 };
165   elf_section_t *section;
166   u8 *data;
167   char *version_required;
168   vlib_plugin_registration_t *reg;
169   vlib_plugin_r2_t *r2;
170   plugin_config_t *pc = 0;
171   uword *p;
172
173   if (elf_read_file (&em, (char *) pi->filename))
174     return -1;
175
176   /* New / improved (well, not really) registration structure? */
177   error = elf_get_section_by_name (&em, ".vlib_plugin_r2", &section);
178   if (error == 0)
179     {
180       data = elf_get_section_contents (&em, section->index, 1);
181       r2 = (vlib_plugin_r2_t *) data;
182       reg = clib_mem_alloc (sizeof (*reg));
183       memset (reg, 0, sizeof (*reg));
184
185       reg->default_disabled = r2->default_disabled != 0;
186       error = r2_to_reg (&em, r2, reg);
187       if (error)
188         {
189           PLUGIN_LOG_ERR ("Bad r2 registration: %s\n", (char *) pi->name);
190           return -1;
191         }
192       if (pm->plugins_default_disable)
193         reg->default_disabled = 1;
194       reread_reg = 0;
195       goto process_reg;
196     }
197   else
198     clib_error_free (error);
199
200   error = elf_get_section_by_name (&em, ".vlib_plugin_registration",
201                                    &section);
202   if (error)
203     {
204       PLUGIN_LOG_ERR ("Not a plugin: %s\n", (char *) pi->name);
205       return -1;
206     }
207
208   data = elf_get_section_contents (&em, section->index, 1);
209   reg = (vlib_plugin_registration_t *) data;
210
211   if (vec_len (data) != sizeof (*reg))
212     {
213       PLUGIN_LOG_ERR ("vlib_plugin_registration size mismatch in plugin %s\n",
214                       (char *) pi->name);
215       goto error;
216     }
217
218   if (pm->plugins_default_disable)
219     reg->default_disabled = 1;
220
221 process_reg:
222   p = hash_get_mem (pm->config_index_by_name, pi->name);
223   if (p)
224     {
225       pc = vec_elt_at_index (pm->configs, p[0]);
226       if (pc->is_disabled)
227         {
228           PLUGIN_LOG_NOTICE ("Plugin disabled: %s", pi->name);
229           goto error;
230         }
231       if (reg->default_disabled && pc->is_enabled == 0)
232         {
233           PLUGIN_LOG_NOTICE ("Plugin disabled (default): %s", pi->name);
234           goto error;
235         }
236     }
237   else if (reg->default_disabled)
238     {
239       PLUGIN_LOG_NOTICE ("Plugin disabled (default): %s", pi->name);
240       goto error;
241     }
242
243   version_required = str_array_to_vec ((char *) &reg->version_required,
244                                        sizeof (reg->version_required));
245
246   if ((strlen (version_required) > 0) &&
247       (strncmp (vlib_plugin_app_version, version_required,
248                 strlen (version_required))))
249     {
250       PLUGIN_LOG_ERR ("Plugin %s version mismatch: %s != %s",
251                       pi->name, vlib_plugin_app_version,
252                       reg->version_required);
253       if (!(pc && pc->skip_version_check == 1))
254         {
255           vec_free (version_required);
256           goto error;
257         }
258     }
259
260   /*
261    * Collect names of plugins overridden (disabled) by the
262    * current plugin.
263    */
264   if (reg->overrides[0])
265     {
266       const char *overrides = reg->overrides;
267       u8 *override_name_copy, *overridden_by_name_copy;
268       u8 *sp, *ep;
269       uword *p;
270
271       sp = ep = (u8 *) overrides;
272
273       while (1)
274         {
275           if (*sp == 0
276               || (sp >= (u8 *) overrides + ARRAY_LEN (reg->overrides)))
277             break;
278           if (*sp == ' ' || *sp == ',')
279             {
280               sp++;
281               continue;
282             }
283           ep = sp;
284           while (*ep && *ep != ' ' && *ep != ',' &&
285                  ep < (u8 *) overrides + ARRAY_LEN (reg->overrides))
286             ep++;
287           if (*ep == ' ' || *ep == ',')
288             ep--;
289
290           override_name_copy = extract (sp, ep);
291
292
293           p = hash_get_mem (pm->plugin_overrides_by_name_hash,
294                             override_name_copy);
295           /* Already overridden... */
296           if (p)
297             vec_free (override_name_copy);
298           else
299             {
300               overridden_by_name_copy = format (0, "%s%c", pi->name, 0);
301               hash_set_mem (pm->plugin_overrides_by_name_hash,
302                             override_name_copy, overridden_by_name_copy);
303             }
304           sp = *ep ? ep + 1 : ep;
305         }
306     }
307   vec_free (version_required);
308
309 #if defined(RTLD_DEEPBIND)
310   handle = dlopen ((char *) pi->filename,
311                    RTLD_LAZY | (reg->deep_bind ? RTLD_DEEPBIND : 0));
312 #else
313   handle = dlopen ((char *) pi->filename, RTLD_LAZY);
314 #endif
315
316   if (handle == 0)
317     {
318       PLUGIN_LOG_ERR ("%s", dlerror ());
319       PLUGIN_LOG_ERR ("Failed to load plugin '%s'", pi->name);
320       goto error;
321     }
322
323   pi->handle = handle;
324
325   if (reread_reg)
326     reg = dlsym (pi->handle, "vlib_plugin_registration");
327
328   pi->reg = reg;
329   pi->version = str_array_to_vec ((char *) &reg->version,
330                                   sizeof (reg->version));
331
332   if (reg->early_init)
333     {
334       clib_error_t *(*ei) (vlib_main_t *);
335       void *h;
336
337       h = dlsym (pi->handle, reg->early_init);
338       if (h)
339         {
340           ei = h;
341           error = (*ei) (pm->vlib_main);
342           if (error)
343             {
344               u8 *err = format (0, "%s: %U%c", pi->name,
345                                 format_clib_error, error, 0);
346               PLUGIN_LOG_ERR ((char *) err);
347               clib_error_free (error);
348               dlclose (pi->handle);
349               pi->handle = 0;
350               goto error;
351             }
352         }
353       else
354         PLUGIN_LOG_ERR ("Plugin %s: early init function %s set but not found",
355                         (char *) pi->name, reg->early_init);
356     }
357
358   if (reg->description)
359     PLUGIN_LOG_NOTICE ("Loaded plugin: %s (%s)", pi->name, reg->description);
360   else
361     PLUGIN_LOG_NOTICE ("Loaded plugin: %s", pi->name);
362
363   vec_free (data);
364   elf_main_free (&em);
365   return 0;
366
367 error:
368   vec_free (data);
369   elf_main_free (&em);
370   return -1;
371 }
372
373 static u8 **
374 split_plugin_path (plugin_main_t * pm)
375 {
376   int i;
377   u8 **rv = 0;
378   u8 *path = pm->plugin_path;
379   u8 *this = 0;
380
381   for (i = 0; i < vec_len (pm->plugin_path); i++)
382     {
383       if (path[i] != ':')
384         {
385           vec_add1 (this, path[i]);
386           continue;
387         }
388       vec_add1 (this, 0);
389       vec_add1 (rv, this);
390       this = 0;
391     }
392   if (this)
393     {
394       vec_add1 (this, 0);
395       vec_add1 (rv, this);
396     }
397   return rv;
398 }
399
400 static int
401 plugin_name_sort_cmp (void *a1, void *a2)
402 {
403   plugin_info_t *p1 = a1;
404   plugin_info_t *p2 = a2;
405
406   return strcmp ((char *) p1->name, (char *) p2->name);
407 }
408
409 static int
410 index_cmp (void *a1, void *a2)
411 {
412   uword *i1 = (uword *) a1, *i2 = (uword *) a2;
413
414   if (*i1 < *i2)
415     return -1;
416   else if (*i1 > *i2)
417     return 1;
418   else
419     return 0;
420 }
421
422 int
423 vlib_load_new_plugins (plugin_main_t * pm, int from_early_init)
424 {
425   DIR *dp;
426   struct dirent *entry;
427   struct stat statb;
428   uword *p;
429   plugin_info_t *pi;
430   u8 **plugin_path;
431   uword *not_loaded_indices = 0;
432   int i;
433
434   plugin_path = split_plugin_path (pm);
435
436   for (i = 0; i < vec_len (plugin_path); i++)
437     {
438       dp = opendir ((char *) plugin_path[i]);
439
440       if (dp == 0)
441         continue;
442
443       while ((entry = readdir (dp)))
444         {
445           u8 *plugin_name;
446           u8 *filename;
447
448           if (pm->plugin_name_filter)
449             {
450               int j;
451               for (j = 0; j < vec_len (pm->plugin_name_filter); j++)
452                 if (entry->d_name[j] != pm->plugin_name_filter[j])
453                   goto next;
454             }
455
456           filename = format (0, "%s/%s%c", plugin_path[i], entry->d_name, 0);
457
458           /* Only accept .so */
459           char *ext = strrchr ((const char *) filename, '.');
460           /* unreadable */
461           if (!ext || (strcmp (ext, ".so") != 0) ||
462               stat ((char *) filename, &statb) < 0)
463             {
464             ignore:
465               vec_free (filename);
466               continue;
467             }
468
469           /* a dir or other things which aren't plugins */
470           if (!S_ISREG (statb.st_mode))
471             goto ignore;
472
473           plugin_name = format (0, "%s%c", entry->d_name, 0);
474           /* Have we seen this plugin already? */
475           p = hash_get_mem (pm->plugin_by_name_hash, plugin_name);
476           if (p == 0)
477             {
478               /* No, add it to the plugin vector */
479               vec_add2 (pm->plugin_info, pi, 1);
480               pi->name = plugin_name;
481               pi->filename = filename;
482               pi->file_info = statb;
483               pi->handle = 0;
484               hash_set_mem (pm->plugin_by_name_hash, plugin_name,
485                             pi - pm->plugin_info);
486             }
487         next:
488           ;
489         }
490       closedir (dp);
491       vec_free (plugin_path[i]);
492     }
493   vec_free (plugin_path);
494
495
496   /*
497    * Sort the plugins by name. This is important.
498    * API traces contain absolute message numbers.
499    * Loading plugins in directory (vs. alphabetical) order
500    * makes trace replay incredibly fragile.
501    */
502   vec_sort_with_function (pm->plugin_info, plugin_name_sort_cmp);
503
504   /*
505    * Attempt to load the plugins
506    */
507   for (i = 0; i < vec_len (pm->plugin_info); i++)
508     {
509       pi = vec_elt_at_index (pm->plugin_info, i);
510
511       if (load_one_plugin (pm, pi, from_early_init))
512         {
513           /* Make a note of any which fail to load */
514           vec_add1 (not_loaded_indices, i);
515         }
516     }
517
518   /*
519    * Honor override list
520    */
521   for (i = 0; i < vec_len (pm->plugin_info); i++)
522     {
523       uword *p;
524
525       pi = vec_elt_at_index (pm->plugin_info, i);
526
527       p = hash_get_mem (pm->plugin_overrides_by_name_hash, pi->name);
528
529       /* Plugin overridden? */
530       if (p)
531         {
532           PLUGIN_LOG_NOTICE ("Plugin '%s' overridden by '%s'", pi->name,
533                              p[0]);
534           vec_add1 (not_loaded_indices, i);
535         }
536     }
537
538   /*
539    * Sort the vector of indices to delete to avoid screwing up
540    * the indices as we delete them.
541    */
542   vec_sort_with_function (not_loaded_indices, index_cmp);
543
544   /*
545    * Remove duplicates, which can happen if a plugin is
546    * disabled from the command line and disabled by
547    * a plugin which is loaded.
548    */
549   for (i = 0; i < vec_len (not_loaded_indices); i++)
550     {
551       if (i < vec_len (not_loaded_indices) - 1)
552         {
553           if (not_loaded_indices[i + 1] == not_loaded_indices[i])
554             {
555               vec_delete (not_loaded_indices, 1, i);
556               i--;
557             }
558         }
559     }
560
561   /* Remove plugin info vector elements corresponding to load failures */
562   if (vec_len (not_loaded_indices) > 0)
563     {
564       for (i = vec_len (not_loaded_indices) - 1; i >= 0; i--)
565         {
566           pi = vec_elt_at_index (pm->plugin_info, not_loaded_indices[i]);
567           hash_unset_mem (pm->plugin_by_name_hash, pi->name);
568           if (pi->handle)
569             {
570               dlclose (pi->handle);
571               PLUGIN_LOG_NOTICE ("Unloaded plugin: %s", pi->name);
572             }
573           vec_free (pi->name);
574           vec_free (pi->filename);
575           vec_delete (pm->plugin_info, 1, not_loaded_indices[i]);
576         }
577       vec_free (not_loaded_indices);
578     }
579
580   /* Recreate the plugin name hash */
581   hash_free (pm->plugin_by_name_hash);
582   pm->plugin_by_name_hash = hash_create_string (0, sizeof (uword));
583
584   for (i = 0; i < vec_len (pm->plugin_info); i++)
585     {
586       pi = vec_elt_at_index (pm->plugin_info, i);
587       hash_set_mem (pm->plugin_by_name_hash, pi->name, pi - pm->plugin_info);
588     }
589
590   return 0;
591 }
592
593 int
594 vlib_plugin_early_init (vlib_main_t * vm)
595 {
596   plugin_main_t *pm = &vlib_plugin_main;
597
598   pm->logger =
599     vlib_log_register_class_rate_limit ("plugin", "load",
600                                         0x7FFFFFFF /* aka no rate limit */ );
601
602   if (pm->plugin_path == 0)
603     pm->plugin_path = format (0, "%s", vlib_plugin_path);
604
605   if (pm->plugin_path_add)
606     pm->plugin_path = format (pm->plugin_path, ":%s", pm->plugin_path_add);
607
608   pm->plugin_path = format (pm->plugin_path, "%c", 0);
609
610   PLUGIN_LOG_DBG ("plugin path %s", pm->plugin_path);
611
612   pm->plugin_by_name_hash = hash_create_string (0, sizeof (uword));
613   pm->plugin_overrides_by_name_hash = hash_create_string (0, sizeof (uword));
614   pm->vlib_main = vm;
615
616   return vlib_load_new_plugins (pm, 1 /* from_early_init */ );
617 }
618
619 u8 *
620 vlib_get_vat_plugin_path (void)
621 {
622   plugin_main_t *pm = &vlib_plugin_main;
623   return (pm->vat_plugin_path);
624 }
625
626 u8 *
627 vlib_get_vat_plugin_name_filter (void)
628 {
629   plugin_main_t *pm = &vlib_plugin_main;
630   return (pm->vat_plugin_name_filter);
631 }
632
633 static clib_error_t *
634 vlib_plugins_show_cmd_fn (vlib_main_t * vm,
635                           unformat_input_t * input, vlib_cli_command_t * cmd)
636 {
637   plugin_main_t *pm = &vlib_plugin_main;
638   u8 *s = 0;
639   u8 *key = 0;
640   uword value = 0;
641   int index = 1;
642   plugin_info_t *pi;
643
644   s = format (s, " Plugin path is: %s\n\n", pm->plugin_path);
645   s = format (s, "     %-41s%-33s%s\n", "Plugin", "Version", "Description");
646
647   hash_foreach_mem (key, value, pm->plugin_by_name_hash,
648     {
649       if (key != 0)
650         {
651           pi = vec_elt_at_index (pm->plugin_info, value);
652           s = format (s, "%3d. %-40s %-32s %s\n", index, key, pi->version,
653                       (pi->reg && pi->reg->description) ?
654                       pi->reg->description : "");
655           index++;
656         }
657     });
658
659   vlib_cli_output (vm, "%v", s);
660   vec_free (s);
661   return 0;
662 }
663
664 VLIB_CLI_COMMAND (plugins_show_cmd, static) =
665 {
666   .path = "show plugins",
667   .short_help = "show loaded plugins",
668   .function = vlib_plugins_show_cmd_fn,
669 };
670
671 static clib_error_t *
672 config_one_plugin (vlib_main_t * vm, char *name, unformat_input_t * input)
673 {
674   plugin_main_t *pm = &vlib_plugin_main;
675   plugin_config_t *pc;
676   clib_error_t *error = 0;
677   uword *p;
678   int is_enable = 0;
679   int is_disable = 0;
680   int skip_version_check = 0;
681
682   if (pm->config_index_by_name == 0)
683     pm->config_index_by_name = hash_create_string (0, sizeof (uword));
684
685   p = hash_get_mem (pm->config_index_by_name, name);
686
687   if (p)
688     {
689       error = clib_error_return (0, "plugin '%s' already configured", name);
690       goto done;
691     }
692
693   while (unformat_check_input (input) != UNFORMAT_END_OF_INPUT)
694     {
695       if (unformat (input, "enable"))
696         is_enable = 1;
697       else if (unformat (input, "disable"))
698         is_disable = 1;
699       else if (unformat (input, "skip-version-check"))
700         skip_version_check = 1;
701       else
702         {
703           error = clib_error_return (0, "unknown input '%U'",
704                                      format_unformat_error, input);
705           goto done;
706         }
707     }
708
709   if (is_enable && is_disable)
710     {
711       error = clib_error_return (0, "please specify either enable or disable"
712                                  " for plugin '%s'", name);
713       goto done;
714     }
715
716   vec_add2 (pm->configs, pc, 1);
717   hash_set_mem (pm->config_index_by_name, name, pc - pm->configs);
718   pc->is_enabled = is_enable;
719   pc->is_disabled = is_disable;
720   pc->skip_version_check = skip_version_check;
721   pc->name = name;
722
723 done:
724   return error;
725 }
726
727 clib_error_t *
728 vlib_plugin_config (vlib_main_t * vm, unformat_input_t * input)
729 {
730   plugin_main_t *pm = &vlib_plugin_main;
731   clib_error_t *error = 0;
732   unformat_input_t in;
733
734   unformat_init (&in, 0, 0);
735
736   while (unformat_check_input (input) != UNFORMAT_END_OF_INPUT)
737     {
738       u8 *s, *v;
739       if (unformat (input, "%s %v", &s, &v))
740         {
741           if (strncmp ((const char *) s, "plugins", 8) == 0)
742             {
743               if (vec_len (in.buffer) > 0)
744                 vec_add1 (in.buffer, ' ');
745               vec_add (in.buffer, v, vec_len (v));
746             }
747         }
748       else
749         {
750           error = clib_error_return (0, "unknown input '%U'",
751                                      format_unformat_error, input);
752           goto done;
753         }
754
755       vec_free (v);
756       vec_free (s);
757     }
758 done:
759   input = &in;
760   while (unformat_check_input (input) != UNFORMAT_END_OF_INPUT)
761     {
762       unformat_input_t sub_input;
763       u8 *s = 0;
764       if (unformat (input, "path %s", &s))
765         pm->plugin_path = s;
766       else if (unformat (input, "add-path %s", &s))
767         pm->plugin_path_add = s;
768       else if (unformat (input, "name-filter %s", &s))
769         pm->plugin_name_filter = s;
770       else if (unformat (input, "vat-path %s", &s))
771         pm->vat_plugin_path = s;
772       else if (unformat (input, "vat-name-filter %s", &s))
773         pm->vat_plugin_name_filter = s;
774       else if (unformat (input, "plugin default %U",
775                          unformat_vlib_cli_sub_input, &sub_input))
776         {
777           pm->plugins_default_disable =
778             unformat (&sub_input, "disable") ? 1 : 0;
779           unformat_free (&sub_input);
780         }
781       else if (unformat (input, "plugin %s %U", &s,
782                          unformat_vlib_cli_sub_input, &sub_input))
783         {
784           error = config_one_plugin (vm, (char *) s, &sub_input);
785           unformat_free (&sub_input);
786           if (error)
787             goto done2;
788         }
789       else
790         {
791           error = clib_error_return (0, "unknown input '%U'",
792                                      format_unformat_error, input);
793           {
794             vec_free (s);
795             goto done2;
796           }
797         }
798     }
799
800 done2:
801   unformat_free (&in);
802   return error;
803 }
804
805 /* discard whole 'plugins' section, as it is already consumed prior to
806    plugin load */
807 static clib_error_t *
808 plugins_config (vlib_main_t * vm, unformat_input_t * input)
809 {
810   u8 *junk;
811
812   while (unformat_check_input (input) != UNFORMAT_END_OF_INPUT)
813     {
814       if (unformat (input, "%s", &junk))
815         {
816           vec_free (junk);
817           return 0;
818         }
819       else
820         return clib_error_return (0, "unknown input '%U'",
821                                   format_unformat_error, input);
822     }
823   return 0;
824 }
825
826 VLIB_CONFIG_FUNCTION (plugins_config, "plugins");
827
828 /*
829  * fd.io coding-style-patch-verification: ON
830  *
831  * Local Variables:
832  * eval: (c-set-style "gnu")
833  * End:
834  */