1 /*****************************************************************************
2  * bank.c : Modules list
3  *****************************************************************************
4  * Copyright (C) 2001-2011 VLC authors and VideoLAN
5  *
6  * Authors: Sam Hocevar <sam@zoy.org>
7  *          Ethan C. Baldridge <BaldridgeE@cadmus.com>
8  *          Hans-Peter Jansen <hpj@urpla.net>
9  *          Gildas Bazin <gbazin@videolan.org>
10  *          Rémi Denis-Courmont
11  *
12  * This program is free software; you can redistribute it and/or modify it
13  * under the terms of the GNU Lesser General Public License as published by
14  * the Free Software Foundation; either version 2.1 of the License, or
15  * (at your option) any later version.
16  *
17  * This program is distributed in the hope that it will be useful,
18  * but WITHOUT ANY WARRANTY; without even the implied warranty of
19  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20  * GNU Lesser General Public License for more details.
21  *
22  * You should have received a copy of the GNU Lesser General Public License
23  * along with this program; if not, write to the Free Software Foundation,
24  * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
25  *****************************************************************************/
26 
27 #ifdef HAVE_CONFIG_H
28 # include "config.h"
29 #endif
30 
31 #include <stdlib.h>
32 #include <stdio.h>
33 #include <string.h>
34 #include <assert.h>
35 
36 #include <sys/types.h>
37 #include <sys/stat.h>
38 #include <unistd.h>
39 #ifdef HAVE_SEARCH_H
40 # include <search.h>
41 #endif
42 
43 #include <vlc_common.h>
44 #include <vlc_plugin.h>
45 #include <vlc_modules.h>
46 #include <vlc_fs.h>
47 #include <vlc_block.h>
48 #include "libvlc.h"
49 #include "config/configuration.h"
50 #include "modules/modules.h"
51 
52 typedef struct vlc_modcap
53 {
54     char *name;
55     module_t **modv;
56     size_t modc;
57 } vlc_modcap_t;
58 
vlc_modcap_cmp(const void * a,const void * b)59 static int vlc_modcap_cmp(const void *a, const void *b)
60 {
61     const vlc_modcap_t *capa = a, *capb = b;
62     return strcmp(capa->name, capb->name);
63 }
64 
vlc_modcap_free(void * data)65 static void vlc_modcap_free(void *data)
66 {
67     vlc_modcap_t *cap = data;
68 
69     free(cap->modv);
70     free(cap->name);
71     free(cap);
72 }
73 
vlc_module_cmp(const void * a,const void * b)74 static int vlc_module_cmp (const void *a, const void *b)
75 {
76     const module_t *const *ma = a, *const *mb = b;
77     /* Note that qsort() uses _ascending_ order,
78      * so the smallest module is the one with the biggest score. */
79     return (*mb)->i_score - (*ma)->i_score;
80 }
81 
vlc_modcap_sort(const void * node,const VISIT which,const int depth)82 static void vlc_modcap_sort(const void *node, const VISIT which,
83                             const int depth)
84 {
85     vlc_modcap_t *const *cp = node, *cap = *cp;
86 
87     if (which != postorder && which != leaf)
88         return;
89 
90     qsort(cap->modv, cap->modc, sizeof (*cap->modv), vlc_module_cmp);
91     (void) depth;
92 }
93 
94 static struct
95 {
96     vlc_mutex_t lock;
97     block_t *caches;
98     void *caps_tree;
99     unsigned usage;
100 } modules = { VLC_STATIC_MUTEX, NULL, NULL, 0 };
101 
102 vlc_plugin_t *vlc_plugins = NULL;
103 
104 /**
105  * Adds a module to the bank
106  */
vlc_module_store(module_t * mod)107 static int vlc_module_store(module_t *mod)
108 {
109     const char *name = module_get_capability(mod);
110     vlc_modcap_t *cap = malloc(sizeof (*cap));
111     if (unlikely(cap == NULL))
112         return -1;
113 
114     cap->name = strdup(name);
115     cap->modv = NULL;
116     cap->modc = 0;
117 
118     if (unlikely(cap->name == NULL))
119         goto error;
120 
121     vlc_modcap_t **cp = tsearch(cap, &modules.caps_tree, vlc_modcap_cmp);
122     if (unlikely(cp == NULL))
123         goto error;
124 
125     if (*cp != cap)
126     {
127         vlc_modcap_free(cap);
128         cap = *cp;
129     }
130 
131     module_t **modv = realloc(cap->modv, sizeof (*modv) * (cap->modc + 1));
132     if (unlikely(modv == NULL))
133         return -1;
134 
135     cap->modv = modv;
136     cap->modv[cap->modc] = mod;
137     cap->modc++;
138     return 0;
139 error:
140     vlc_modcap_free(cap);
141     return -1;
142 }
143 
144 /**
145  * Adds a plugin (and all its modules) to the bank
146  */
vlc_plugin_store(vlc_plugin_t * lib)147 static void vlc_plugin_store(vlc_plugin_t *lib)
148 {
149     /*vlc_assert_locked (&modules.lock);*/
150 
151     lib->next = vlc_plugins;
152     vlc_plugins = lib;
153 
154     for (module_t *m = lib->module; m != NULL; m = m->next)
155         vlc_module_store(m);
156 }
157 
158 /**
159  * Registers a statically-linked plug-in.
160  */
module_InitStatic(vlc_plugin_cb entry)161 static vlc_plugin_t *module_InitStatic(vlc_plugin_cb entry)
162 {
163     /* Initializes the statically-linked library */
164     vlc_plugin_t *lib = vlc_plugin_describe (entry);
165     if (unlikely(lib == NULL))
166         return NULL;
167 
168 #ifdef HAVE_DYNAMIC_PLUGINS
169     atomic_init(&lib->loaded, true);
170     lib->unloadable = false;
171 #endif
172     return lib;
173 }
174 
175 #if defined(__ELF__) || !HAVE_DYNAMIC_PLUGINS
176 # ifdef __GNUC__
177 __attribute__((weak))
178 # else
179 #  pragma weak vlc_static_modules
180 # endif
181 extern vlc_plugin_cb vlc_static_modules[];
182 
module_InitStaticModules(void)183 static void module_InitStaticModules(void)
184 {
185     if (!vlc_static_modules)
186         return;
187 
188     for (unsigned i = 0; vlc_static_modules[i]; i++)
189     {
190         vlc_plugin_t *lib = module_InitStatic(vlc_static_modules[i]);
191         if (likely(lib != NULL))
192             vlc_plugin_store(lib);
193     }
194 }
195 #else
module_InitStaticModules(void)196 static void module_InitStaticModules(void) { }
197 #endif
198 
199 #ifdef HAVE_DYNAMIC_PLUGINS
200 static const char vlc_entry_name[] = "vlc_entry" MODULE_SUFFIX;
201 
202 /**
203  * Loads a dynamically-linked plug-in into memory and initialize it.
204  *
205  * The module can then be handled by module_need() and module_unneed().
206  *
207  * \param path file path of the shared object
208  * \param fast whether to optimize loading for speed or safety
209  *             (fast is used when the plug-in is registered but not used)
210  */
module_InitDynamic(vlc_object_t * obj,const char * path,bool fast)211 static vlc_plugin_t *module_InitDynamic(vlc_object_t *obj, const char *path,
212                                         bool fast)
213 {
214     module_handle_t handle;
215 
216     if (module_Load (obj, path, &handle, fast))
217         return NULL;
218 
219     /* Try to resolve the symbol */
220     vlc_plugin_cb entry =
221         (vlc_plugin_cb) module_Lookup(handle, vlc_entry_name);
222     if (entry == NULL)
223     {
224         msg_Warn (obj, "cannot find plug-in entry point in %s", path);
225         goto error;
226     }
227 
228     /* We can now try to call the symbol */
229     vlc_plugin_t *plugin = vlc_plugin_describe(entry);
230     if (unlikely(plugin == NULL))
231     {
232         /* With a well-written module we shouldn't have to print an
233          * additional error message here, but just make sure. */
234         msg_Err (obj, "cannot initialize plug-in %s", path);
235         goto error;
236     }
237 
238     plugin->handle = handle;
239     atomic_init(&plugin->loaded, true);
240     return plugin;
241 error:
242     module_Unload( handle );
243     return NULL;
244 }
245 
246 typedef enum
247 {
248     CACHE_READ_FILE  = 0x1,
249     CACHE_SCAN_DIR   = 0x2,
250     CACHE_WRITE_FILE = 0x4,
251 } cache_mode_t;
252 
253 typedef struct module_bank
254 {
255     vlc_object_t *obj;
256     const char   *base;
257     cache_mode_t  mode;
258 
259     size_t        size;
260     vlc_plugin_t **plugins;
261     vlc_plugin_t *cache;
262 } module_bank_t;
263 
264 /**
265  * Scans a plug-in from a file.
266  */
AllocatePluginFile(module_bank_t * bank,const char * abspath,const char * relpath,const struct stat * st)267 static int AllocatePluginFile (module_bank_t *bank, const char *abspath,
268                                const char *relpath, const struct stat *st)
269 {
270     vlc_plugin_t *plugin = NULL;
271 
272     /* Check our plugins cache first then load plugin if needed */
273     if (bank->mode & CACHE_READ_FILE)
274     {
275         plugin = vlc_cache_lookup(&bank->cache, relpath);
276 
277         if (plugin != NULL
278          && (plugin->mtime != (int64_t)st->st_mtime
279           || plugin->size != (uint64_t)st->st_size))
280         {
281             msg_Err(bank->obj, "stale plugins cache: modified %s",
282                     plugin->abspath);
283             vlc_plugin_destroy(plugin);
284             plugin = NULL;
285         }
286     }
287 
288     if (plugin == NULL)
289     {
290         plugin = module_InitDynamic(bank->obj, abspath, true);
291 
292         if (plugin != NULL)
293         {
294             plugin->path = xstrdup(relpath);
295             plugin->mtime = st->st_mtime;
296             plugin->size = st->st_size;
297         }
298     }
299 
300     if (plugin == NULL)
301         return -1;
302 
303     vlc_plugin_store(plugin);
304 
305     if (bank->mode & CACHE_WRITE_FILE) /* Add entry to to-be-saved cache */
306     {
307         bank->plugins = xrealloc(bank->plugins,
308                                  (bank->size + 1) * sizeof (vlc_plugin_t *));
309         bank->plugins[bank->size] = plugin;
310         bank->size++;
311     }
312 
313     /* TODO: deal with errors */
314     return  0;
315 }
316 
317 /**
318  * Recursively browses a directory to look for plug-ins.
319  */
AllocatePluginDir(module_bank_t * bank,unsigned maxdepth,const char * absdir,const char * reldir)320 static void AllocatePluginDir (module_bank_t *bank, unsigned maxdepth,
321                                const char *absdir, const char *reldir)
322 {
323     if (maxdepth == 0)
324         return;
325     maxdepth--;
326 
327     DIR *dh = vlc_opendir (absdir);
328     if (dh == NULL)
329         return;
330 
331     /* Parse the directory and try to load all files it contains. */
332     for (;;)
333     {
334         char *relpath = NULL, *abspath = NULL;
335         const char *file = vlc_readdir (dh);
336         if (file == NULL)
337             break;
338 
339         /* Skip ".", ".." */
340         if (!strcmp (file, ".") || !strcmp (file, ".."))
341             continue;
342 
343         /* Compute path relative to plug-in base directory */
344         if (reldir != NULL)
345         {
346             if (asprintf (&relpath, "%s"DIR_SEP"%s", reldir, file) == -1)
347                 relpath = NULL;
348         }
349         else
350             relpath = strdup (file);
351         if (unlikely(relpath == NULL))
352             continue;
353 
354         /* Compute absolute path */
355         if (asprintf (&abspath, "%s"DIR_SEP"%s", bank->base, relpath) == -1)
356         {
357             abspath = NULL;
358             goto skip;
359         }
360 
361         struct stat st;
362         if (vlc_stat (abspath, &st) == -1)
363             goto skip;
364 
365         if (S_ISREG (st.st_mode))
366         {
367             static const char prefix[] = "lib";
368             static const char suffix[] = "_plugin"LIBEXT;
369             size_t len = strlen (file);
370 
371 #ifndef __OS2__
372             /* Check that file matches the "lib*_plugin"LIBEXT pattern */
373             if (len > strlen (suffix)
374              && !strncmp (file, prefix, strlen (prefix))
375              && !strcmp (file + len - strlen (suffix), suffix))
376 #else
377             /* We load all the files ending with LIBEXT on OS/2,
378              * because OS/2 has a 8.3 length limitation for DLL name */
379             if (len > strlen (LIBEXT)
380              && !strcasecmp (file + len - strlen (LIBEXT), LIBEXT))
381 #endif
382                 AllocatePluginFile (bank, abspath, relpath, &st);
383         }
384         else if (S_ISDIR (st.st_mode))
385             /* Recurse into another directory */
386             AllocatePluginDir (bank, maxdepth, abspath, relpath);
387     skip:
388         free (relpath);
389         free (abspath);
390     }
391     closedir (dh);
392 }
393 
394 /**
395  * Scans for plug-ins within a file system hierarchy.
396  * \param path base directory to browse
397  */
AllocatePluginPath(vlc_object_t * obj,const char * path,cache_mode_t mode)398 static void AllocatePluginPath(vlc_object_t *obj, const char *path,
399                                cache_mode_t mode)
400 {
401     module_bank_t bank =
402     {
403         .obj = obj,
404         .base = path,
405         .mode = mode,
406     };
407 
408     if (mode & CACHE_READ_FILE)
409         bank.cache = vlc_cache_load(obj, path, &modules.caches);
410     else
411         msg_Dbg(bank.obj, "ignoring plugins cache file");
412 
413     if (mode & CACHE_SCAN_DIR)
414     {
415         msg_Dbg(obj, "recursively browsing `%s'", bank.base);
416 
417         /* Don't go deeper than 5 subdirectories */
418         AllocatePluginDir(&bank, 5, path, NULL);
419     }
420 
421     /* Deal with unmatched cache entries from cache file */
422     while (bank.cache != NULL)
423     {
424         vlc_plugin_t *plugin = bank.cache;
425 
426         bank.cache = plugin->next;
427         if (mode & CACHE_SCAN_DIR)
428             vlc_plugin_destroy(plugin);
429         else
430             vlc_plugin_store(plugin);
431     }
432 
433     if (mode & CACHE_WRITE_FILE)
434         CacheSave(obj, path, bank.plugins, bank.size);
435 
436     free(bank.plugins);
437 }
438 
439 /**
440  * Enumerates all dynamic plug-ins that can be found.
441  *
442  * This function will recursively browse the default plug-ins directory and any
443  * directory listed in the VLC_PLUGIN_PATH environment variable.
444  * For performance reasons, a cache is normally used so that plug-in shared
445  * objects do not need to loaded and linked into the process.
446  */
AllocateAllPlugins(vlc_object_t * p_this)447 static void AllocateAllPlugins (vlc_object_t *p_this)
448 {
449     char *paths;
450     cache_mode_t mode = 0;
451 
452     if (var_InheritBool(p_this, "plugins-cache"))
453         mode |= CACHE_READ_FILE;
454     if (var_InheritBool(p_this, "plugins-scan"))
455         mode |= CACHE_SCAN_DIR;
456     if (var_InheritBool(p_this, "reset-plugins-cache"))
457         mode = (mode | CACHE_WRITE_FILE) & ~CACHE_READ_FILE;
458 
459 #if VLC_WINSTORE_APP
460     /* Windows Store Apps can not load external plugins with absolute paths. */
461     AllocatePluginPath (p_this, "plugins", mode);
462 #else
463     /* Contruct the special search path for system that have a relocatable
464      * executable. Set it to <vlc path>/plugins. */
465     char *vlcpath = config_GetLibDir ();
466     if (likely(vlcpath != NULL)
467      && likely(asprintf (&paths, "%s" DIR_SEP "plugins", vlcpath) != -1))
468     {
469         AllocatePluginPath (p_this, paths, mode);
470         free( paths );
471     }
472     free (vlcpath);
473 #endif /* VLC_WINSTORE_APP */
474 
475     /* If the user provided a plugin path, we add it to the list */
476     paths = getenv( "VLC_PLUGIN_PATH" );
477     if( paths == NULL )
478         return;
479 
480     paths = strdup( paths ); /* don't harm the environment ! :) */
481     if( unlikely(paths == NULL) )
482         return;
483 
484     for( char *buf, *path = strtok_r( paths, PATH_SEP, &buf );
485          path != NULL;
486          path = strtok_r( NULL, PATH_SEP, &buf ) )
487         AllocatePluginPath (p_this, path, mode);
488 
489     free( paths );
490 }
491 
492 /**
493  * Ensures that a plug-in is loaded.
494  *
495  * \note This function is thread-safe but not re-entrant.
496  *
497  * \return 0 on success, -1 on failure
498  */
module_Map(vlc_object_t * obj,vlc_plugin_t * plugin)499 int module_Map(vlc_object_t *obj, vlc_plugin_t *plugin)
500 {
501     static vlc_mutex_t lock = VLC_STATIC_MUTEX;
502 
503     if (atomic_load_explicit(&plugin->loaded, memory_order_acquire))
504         return 0; /* fast path: already loaded */
505 
506     /* Try to load the plug-in (without locks, so read-only) */
507     module_handle_t handle;
508 
509     assert(plugin->abspath != NULL);
510 
511     if (module_Load(obj, plugin->abspath, &handle, false))
512         return -1;
513 
514     vlc_plugin_cb entry =
515         (vlc_plugin_cb) module_Lookup(handle, vlc_entry_name);
516     if (entry == NULL)
517     {
518         msg_Err(obj, "cannot find plug-in entry point in %s", plugin->abspath);
519         module_Unload(handle);
520         return -1;
521     }
522 
523     vlc_mutex_lock(&lock);
524     if (!atomic_load_explicit(&plugin->loaded, memory_order_relaxed))
525     {   /* Lock is held, update the plug-in structure */
526         if (vlc_plugin_resolve(plugin, entry))
527         {
528             vlc_mutex_unlock(&lock);
529             return -1;
530         }
531 
532         plugin->handle = handle;
533         atomic_store_explicit(&plugin->loaded, true, memory_order_release);
534     }
535     else /* Another thread won the race to load the plugin */
536         module_Unload(handle);
537     vlc_mutex_unlock(&lock);
538 
539     return 0;
540 }
541 
542 /**
543  * Ensures that a module is not loaded.
544  *
545  * \note This function is not thread-safe. The caller must ensure that the
546  * plug-in is no longer used before calling this function.
547  */
module_Unmap(vlc_plugin_t * plugin)548 static void module_Unmap(vlc_plugin_t *plugin)
549 {
550     if (!plugin->unloadable)
551         return;
552     if (!atomic_exchange_explicit(&plugin->loaded, false,
553                                   memory_order_acquire))
554         return;
555 
556     assert(plugin->handle != NULL);
557     module_Unload(plugin->handle);
558 }
559 #else
module_Map(vlc_object_t * obj,vlc_plugin_t * plugin)560 int module_Map(vlc_object_t *obj, vlc_plugin_t *plugin)
561 {
562     (void) obj; (void) plugin;
563     return 0;
564 }
565 
module_Unmap(vlc_plugin_t * plugin)566 static void module_Unmap(vlc_plugin_t *plugin)
567 {
568     (void) plugin;
569 }
570 #endif /* HAVE_DYNAMIC_PLUGINS */
571 
572 /**
573  * Init bank
574  *
575  * Creates a module bank structure which will be filled later
576  * on with all the modules found.
577  */
module_InitBank(void)578 void module_InitBank (void)
579 {
580     vlc_mutex_lock (&modules.lock);
581 
582     if (modules.usage == 0)
583     {
584         /* Fills the module bank structure with the core module infos.
585          * This is very useful as it will allow us to consider the core
586          * library just as another module, and for instance the configuration
587          * options of core will be available in the module bank structure just
588          * as for every other module. */
589         vlc_plugin_t *plugin = module_InitStatic(vlc_entry__core);
590         if (likely(plugin != NULL))
591             vlc_plugin_store(plugin);
592         config_SortConfig ();
593     }
594     modules.usage++;
595 
596     /* We do retain the module bank lock until the plugins are loaded as well.
597      * This is ugly, this staged loading approach is needed: LibVLC gets
598      * some configuration parameters relevant to loading the plugins from
599      * the core (builtin) module. The module bank becomes shared read-only data
600      * once it is ready, so we need to fully serialize initialization.
601      * DO NOT UNCOMMENT the following line unless you managed to squeeze
602      * module_LoadPlugins() before you unlock the mutex. */
603     /*vlc_mutex_unlock (&modules.lock);*/
604 }
605 
606 /**
607  * Unloads all unused plugin modules and empties the module
608  * bank in case of success.
609  */
module_EndBank(bool b_plugins)610 void module_EndBank (bool b_plugins)
611 {
612     vlc_plugin_t *libs = NULL;
613     block_t *caches = NULL;
614     void *caps_tree = NULL;
615 
616     /* If plugins were _not_ loaded, then the caller still has the bank lock
617      * from module_InitBank(). */
618     if( b_plugins )
619         vlc_mutex_lock (&modules.lock);
620     /*else
621         vlc_assert_locked (&modules.lock); not for static mutexes :( */
622 
623     assert (modules.usage > 0);
624     if (--modules.usage == 0)
625     {
626         config_UnsortConfig ();
627         libs = vlc_plugins;
628         caches = modules.caches;
629         caps_tree = modules.caps_tree;
630         vlc_plugins = NULL;
631         modules.caches = NULL;
632         modules.caps_tree = NULL;
633     }
634     vlc_mutex_unlock (&modules.lock);
635 
636     tdestroy(caps_tree, vlc_modcap_free);
637 
638     while (libs != NULL)
639     {
640         vlc_plugin_t *lib = libs;
641 
642         libs = lib->next;
643         module_Unmap(lib);
644         vlc_plugin_destroy(lib);
645     }
646 
647     block_ChainRelease(caches);
648 }
649 
650 #undef module_LoadPlugins
651 /**
652  * Loads module descriptions for all available plugins.
653  * Fills the module bank structure with the plugin modules.
654  *
655  * \param p_this vlc object structure
656  * \return total number of modules in bank after loading all plug-ins
657  */
module_LoadPlugins(vlc_object_t * obj)658 size_t module_LoadPlugins (vlc_object_t *obj)
659 {
660     /*vlc_assert_locked (&modules.lock); not for static mutexes :( */
661 
662     if (modules.usage == 1)
663     {
664         module_InitStaticModules ();
665 #ifdef HAVE_DYNAMIC_PLUGINS
666         msg_Dbg (obj, "searching plug-in modules");
667         AllocateAllPlugins (obj);
668 #endif
669         config_UnsortConfig ();
670         config_SortConfig ();
671 
672         twalk(modules.caps_tree, vlc_modcap_sort);
673     }
674     vlc_mutex_unlock (&modules.lock);
675 
676     size_t count;
677     module_t **list = module_list_get (&count);
678     module_list_free (list);
679     msg_Dbg (obj, "plug-ins loaded: %zu modules", count);
680     return count;
681 }
682 
683 /**
684  * Frees the flat list of VLC modules.
685  * @param list list obtained by module_list_get()
686  * @param length number of items on the list
687  * @return nothing.
688  */
module_list_free(module_t ** list)689 void module_list_free (module_t **list)
690 {
691     free (list);
692 }
693 
694 /**
695  * Gets the flat list of VLC modules.
696  * @param n [OUT] pointer to the number of modules
697  * @return table of module pointers (release with module_list_free()),
698  *         or NULL in case of error (in that case, *n is zeroed).
699  */
module_list_get(size_t * n)700 module_t **module_list_get (size_t *n)
701 {
702     module_t **tab = NULL;
703     size_t i = 0;
704 
705     assert (n != NULL);
706 
707     for (vlc_plugin_t *lib = vlc_plugins; lib != NULL; lib = lib->next)
708     {
709         module_t **nt = realloc(tab, (i + lib->modules_count) * sizeof (*tab));
710         if (unlikely(nt == NULL))
711         {
712             free (tab);
713             *n = 0;
714             return NULL;
715         }
716 
717         tab = nt;
718         for (module_t *m = lib->module; m != NULL; m = m->next)
719             tab[i++] = m;
720     }
721     *n = i;
722     return tab;
723 }
724 
725 /**
726  * Builds a sorted list of all VLC modules with a given capability.
727  * The list is sorted from the highest module score to the lowest.
728  * @param list pointer to the table of modules [OUT]
729  * @param name name of capability of modules to look for
730  * @return the number of matching found, or -1 on error (*list is then NULL).
731  * @note *list must be freed with module_list_free().
732  */
module_list_cap(module_t *** restrict list,const char * name)733 ssize_t module_list_cap (module_t ***restrict list, const char *name)
734 {
735     const vlc_modcap_t **cp = tfind(&name, &modules.caps_tree, vlc_modcap_cmp);
736     if (cp == NULL)
737     {
738         *list = NULL;
739         return 0;
740     }
741 
742     const vlc_modcap_t *cap = *cp;
743     size_t n = cap->modc;
744     module_t **tab = vlc_alloc (n, sizeof (*tab));
745     *list = tab;
746     if (unlikely(tab == NULL))
747         return -1;
748 
749     memcpy(tab, cap->modv, sizeof (*tab) * n);
750     return n;
751 }
752