1 /*****************************************************************************
2  * cache.c: Plugins cache
3  *****************************************************************************
4  * Copyright (C) 2001-2007 VLC authors and VideoLAN
5  * $Id: a9ce8efa9fc06ac1a333a10af9a5f00571afbaca $
6  *
7  * Authors: Sam Hocevar <sam@zoy.org>
8  *          Ethan C. Baldridge <BaldridgeE@cadmus.com>
9  *          Hans-Peter Jansen <hpj@urpla.net>
10  *          Gildas Bazin <gbazin@videolan.org>
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 <stdalign.h>
32 #include <stdlib.h>
33 #include <stdio.h>
34 #include <string.h>
35 #include <sys/types.h>
36 #include <sys/stat.h>
37 #include <unistd.h>
38 #include <assert.h>
39 
40 #include <vlc_common.h>
41 #include <vlc_block.h>
42 #include "libvlc.h"
43 
44 #include <vlc_plugin.h>
45 #include <errno.h>
46 
47 #include "config/configuration.h"
48 
49 #include <vlc_fs.h>
50 
51 #include "modules/modules.h"
52 
53 
54 /*****************************************************************************
55  * Local prototypes
56  *****************************************************************************/
57 #ifdef HAVE_DYNAMIC_PLUGINS
58 /* Sub-version number
59  * (only used to avoid breakage in dev version when cache structure changes) */
60 #define CACHE_SUBVERSION_NUM 34
61 
62 /* Cache filename */
63 #define CACHE_NAME "plugins.dat"
64 /* Magic for the cache filename */
65 #define CACHE_STRING "cache "PACKAGE_NAME" "PACKAGE_VERSION
66 
67 
vlc_cache_load_immediate(void * out,block_t * in,size_t size)68 static int vlc_cache_load_immediate(void *out, block_t *in, size_t size)
69 {
70     if (in->i_buffer < size)
71         return -1;
72 
73     memcpy(out, in->p_buffer, size);
74     in->p_buffer += size;
75     in->i_buffer -= size;
76     return 0;
77 }
78 
vlc_cache_load_bool(bool * out,block_t * in)79 static int vlc_cache_load_bool(bool *out, block_t *in)
80 {
81     unsigned char b;
82 
83     if (vlc_cache_load_immediate(&b, in, 1) || b > 1)
84         return -1;
85 
86     *out = b;
87     return 0;
88 }
89 
vlc_cache_load_array(const void ** p,size_t size,size_t n,block_t * file)90 static int vlc_cache_load_array(const void **p, size_t size, size_t n,
91                                 block_t *file)
92 {
93     if (n == 0)
94     {
95         *p = NULL;
96         return 0;
97     }
98 
99     if (unlikely(size * n < size))
100         return -1;
101 
102     size *= n;
103 
104     if (file->i_buffer < size)
105         return -1;
106 
107     *p = file->p_buffer;
108     file->p_buffer += size;
109     file->i_buffer -= size;
110     return 0;
111 }
112 
vlc_cache_load_string(const char ** restrict p,block_t * file)113 static int vlc_cache_load_string(const char **restrict p, block_t *file)
114 {
115     uint16_t size;
116 
117     if (vlc_cache_load_immediate(&size, file, sizeof (size)) || size > 16384)
118         return -1;
119 
120     if (size == 0)
121     {
122         *p = NULL;
123         return 0;
124     }
125 
126     const char *str = (char *)file->p_buffer;
127 
128     if (file->i_buffer < size || str[size - 1] != '\0')
129         return -1;
130 
131     file->p_buffer += size;
132     file->i_buffer -= size;
133     *p = str;
134     return 0;
135 }
136 
vlc_cache_load_align(size_t align,block_t * file)137 static int vlc_cache_load_align(size_t align, block_t *file)
138 {
139     assert(align > 0);
140 
141     size_t skip = (-(uintptr_t)file->p_buffer) % align;
142     if (skip == 0)
143         return 0;
144 
145     assert(skip < align);
146 
147     if (file->i_buffer < skip)
148         return -1;
149 
150     file->p_buffer += skip;
151     file->i_buffer -= skip;
152     assert((((uintptr_t)file->p_buffer) % align) == 0);
153     return 0;
154 }
155 
156 #define LOAD_IMMEDIATE(a) \
157     if (vlc_cache_load_immediate(&(a), file, sizeof (a))) \
158         goto error
159 #define LOAD_FLAG(a) \
160     do \
161     { \
162         bool b; \
163         if (vlc_cache_load_bool(&b, file)) \
164             goto error; \
165         (a) = b; \
166     } while (0)
167 #define LOAD_ARRAY(a,n) \
168     do \
169     { \
170         const void *base; \
171         if (vlc_cache_load_array(&base, sizeof (*(a)), (n), file)) \
172             goto error; \
173         (a) = base; \
174     } while (0)
175 #define LOAD_STRING(a) \
176     if (vlc_cache_load_string(&(a), file)) \
177         goto error
178 #define LOAD_ALIGNOF(t) \
179     if (vlc_cache_load_align(alignof(t), file)) \
180         goto error
181 
vlc_cache_load_config(module_config_t * cfg,block_t * file)182 static int vlc_cache_load_config(module_config_t *cfg, block_t *file)
183 {
184     LOAD_IMMEDIATE (cfg->i_type);
185     LOAD_IMMEDIATE (cfg->i_short);
186     LOAD_FLAG (cfg->b_advanced);
187     LOAD_FLAG (cfg->b_internal);
188     LOAD_FLAG (cfg->b_unsaveable);
189     LOAD_FLAG (cfg->b_safe);
190     LOAD_FLAG (cfg->b_removed);
191     LOAD_STRING (cfg->psz_type);
192     LOAD_STRING (cfg->psz_name);
193     LOAD_STRING (cfg->psz_text);
194     LOAD_STRING (cfg->psz_longtext);
195     LOAD_IMMEDIATE (cfg->list_count);
196 
197     if (IsConfigStringType (cfg->i_type))
198     {
199         const char *psz;
200         LOAD_STRING(psz);
201         cfg->orig.psz = (char *)psz;
202         cfg->value.psz = (psz != NULL) ? strdup (cfg->orig.psz) : NULL;
203 
204         if (cfg->list_count)
205             cfg->list.psz = xmalloc (cfg->list_count * sizeof (char *));
206         else
207             LOAD_STRING(cfg->list_cb_name);
208         for (unsigned i = 0; i < cfg->list_count; i++)
209         {
210             LOAD_STRING (cfg->list.psz[i]);
211             if (cfg->list.psz[i] == NULL /* NULL -> empty string */
212              && (cfg->list.psz[i] = calloc (1, 1)) == NULL)
213                 goto error;
214         }
215     }
216     else
217     {
218         LOAD_IMMEDIATE (cfg->orig);
219         LOAD_IMMEDIATE (cfg->min);
220         LOAD_IMMEDIATE (cfg->max);
221         cfg->value = cfg->orig;
222 
223         if (cfg->list_count)
224         {
225             LOAD_ALIGNOF(*cfg->list.i);
226         }
227         else
228             LOAD_STRING(cfg->list_cb_name);
229 
230         LOAD_ARRAY(cfg->list.i, cfg->list_count);
231     }
232 
233     cfg->list_text = xmalloc (cfg->list_count * sizeof (char *));
234     for (unsigned i = 0; i < cfg->list_count; i++)
235     {
236         LOAD_STRING (cfg->list_text[i]);
237         if (cfg->list_text[i] == NULL /* NULL -> empty string */
238          && (cfg->list_text[i] = calloc (1, 1)) == NULL)
239             goto error;
240     }
241 
242     return 0;
243 error:
244     return -1; /* FIXME: leaks */
245 }
246 
vlc_cache_load_plugin_config(vlc_plugin_t * plugin,block_t * file)247 static int vlc_cache_load_plugin_config(vlc_plugin_t *plugin, block_t *file)
248 {
249     uint16_t lines;
250 
251     /* Calculate the structure length */
252     LOAD_IMMEDIATE (lines);
253 
254     /* Allocate memory */
255     if (lines)
256     {
257         plugin->conf.items = calloc(sizeof (module_config_t), lines);
258         if (unlikely(plugin->conf.items == NULL))
259         {
260             plugin->conf.size = 0;
261             return -1;
262         }
263     }
264     else
265         plugin->conf.items = NULL;
266 
267     plugin->conf.size = lines;
268 
269     /* Do the duplication job */
270     for (size_t i = 0; i < lines; i++)
271     {
272         module_config_t *item = plugin->conf.items + i;
273 
274         if (vlc_cache_load_config(item, file))
275             return -1;
276 
277         if (CONFIG_ITEM(item->i_type))
278         {
279             plugin->conf.count++;
280             if (item->i_type == CONFIG_ITEM_BOOL)
281                 plugin->conf.booleans++;
282         }
283         item->owner = plugin;
284     }
285 
286     return 0;
287 error:
288     return -1; /* FIXME: leaks */
289 }
290 
vlc_cache_load_module(vlc_plugin_t * plugin,block_t * file)291 static int vlc_cache_load_module(vlc_plugin_t *plugin, block_t *file)
292 {
293     module_t *module = vlc_module_create(plugin);
294     if (unlikely(module == NULL))
295         return -1;
296 
297     LOAD_STRING(module->psz_shortname);
298     LOAD_STRING(module->psz_longname);
299     LOAD_STRING(module->psz_help);
300 
301     LOAD_IMMEDIATE(module->i_shortcuts);
302     if (module->i_shortcuts > MODULE_SHORTCUT_MAX)
303         goto error;
304     else
305     {
306         module->pp_shortcuts =
307             xmalloc (sizeof (*module->pp_shortcuts) * module->i_shortcuts);
308         for (unsigned j = 0; j < module->i_shortcuts; j++)
309             LOAD_STRING(module->pp_shortcuts[j]);
310     }
311 
312     LOAD_STRING(module->activate_name);
313     LOAD_STRING(module->deactivate_name);
314     LOAD_STRING(module->psz_capability);
315     LOAD_IMMEDIATE(module->i_score);
316     return 0;
317 error:
318     return -1;
319 }
320 
vlc_cache_load_plugin(block_t * file)321 static vlc_plugin_t *vlc_cache_load_plugin(block_t *file)
322 {
323     vlc_plugin_t *plugin = vlc_plugin_create();
324     if (unlikely(plugin == NULL))
325         return NULL;
326 
327     uint32_t modules;
328     LOAD_IMMEDIATE(modules);
329 
330     for (size_t i = 0; i < modules; i++)
331         if (vlc_cache_load_module(plugin, file))
332             goto error;
333 
334     if (vlc_cache_load_plugin_config(plugin, file))
335         goto error;
336 
337     LOAD_STRING(plugin->textdomain);
338 
339     const char *path;
340     LOAD_STRING(path);
341     if (path == NULL)
342         goto error;
343 
344     plugin->path = strdup(path);
345     if (unlikely(plugin->path == NULL))
346         goto error;
347 
348     LOAD_FLAG(plugin->unloadable);
349     LOAD_IMMEDIATE(plugin->mtime);
350     LOAD_IMMEDIATE(plugin->size);
351 
352     if (plugin->textdomain != NULL)
353         vlc_bindtextdomain(plugin->textdomain);
354 
355     return plugin;
356 
357 error:
358     vlc_plugin_destroy(plugin);
359     return NULL;
360 }
361 
362 /**
363  * Loads a plugins cache file.
364  *
365  * This function will load the plugin cache if present and valid. This cache
366  * will in turn be queried by AllocateAllPlugins() to see if it needs to
367  * actually load the dynamically loadable module.
368  * This allows us to only fully load plugins when they are actually used.
369  */
vlc_cache_load(vlc_object_t * p_this,const char * dir,block_t ** backingp)370 vlc_plugin_t *vlc_cache_load(vlc_object_t *p_this, const char *dir,
371                              block_t **backingp)
372 {
373     char *psz_filename;
374 
375     assert( dir != NULL );
376 
377     if( asprintf( &psz_filename, "%s"DIR_SEP CACHE_NAME, dir ) == -1 )
378         return 0;
379 
380     msg_Dbg( p_this, "loading plugins cache file %s", psz_filename );
381 
382     block_t *file = block_FilePath(psz_filename, false);
383     if (file == NULL)
384         msg_Warn(p_this, "cannot read %s: %s", psz_filename,
385                  vlc_strerror_c(errno));
386     free(psz_filename);
387     if (file == NULL)
388         return 0;
389 
390     /* Check the file is a plugins cache */
391     char cachestr[sizeof (CACHE_STRING) - 1];
392 
393     if (vlc_cache_load_immediate(cachestr, file, sizeof (cachestr))
394      || memcmp(cachestr, CACHE_STRING, sizeof (cachestr)))
395     {
396         msg_Warn( p_this, "This doesn't look like a valid plugins cache" );
397         block_Release(file);
398         return 0;
399     }
400 
401 #ifdef DISTRO_VERSION
402     /* Check for distribution specific version */
403     char distrostr[sizeof (DISTRO_VERSION) - 1];
404 
405     if (vlc_cache_load_immediate(distrostr, file, sizeof (distrostr))
406      || memcmp(distrostr, DISTRO_VERSION, sizeof (distrostr)))
407     {
408         msg_Warn( p_this, "This doesn't look like a valid plugins cache" );
409         block_Release(file);
410         return 0;
411     }
412 #endif
413 
414     /* Check sub-version number */
415     uint32_t marker;
416 
417     if (vlc_cache_load_immediate(&marker, file, sizeof (marker))
418      || marker != CACHE_SUBVERSION_NUM)
419     {
420         msg_Warn( p_this, "This doesn't look like a valid plugins cache "
421                   "(corrupted header)" );
422         block_Release(file);
423         return 0;
424     }
425 
426     /* Check header marker */
427     if (vlc_cache_load_immediate(&marker, file, sizeof (marker))
428 #ifdef DISTRO_VERSION
429      || marker != (sizeof (cachestr) + sizeof (distrostr) + sizeof (marker))
430 #else
431      || marker != (sizeof (cachestr) + sizeof (marker))
432 #endif
433         )
434     {
435         msg_Warn( p_this, "This doesn't look like a valid plugins cache "
436                   "(corrupted header)" );
437         block_Release(file);
438         return 0;
439     }
440 
441     vlc_plugin_t *cache = NULL;
442 
443     while (file->i_buffer > 0)
444     {
445         vlc_plugin_t *plugin = vlc_cache_load_plugin(file);
446         if (plugin == NULL)
447             goto error;
448 
449         if (unlikely(asprintf(&plugin->abspath, "%s" DIR_SEP "%s", dir,
450                               plugin->path) == -1))
451         {
452             plugin->abspath = NULL;
453             vlc_plugin_destroy(plugin);
454             goto error;
455         }
456 
457         plugin->next = cache;
458         cache = plugin;
459     }
460 
461     file->p_next = *backingp;
462     *backingp = file;
463     return cache;
464 
465 error:
466     msg_Warn( p_this, "plugins cache not loaded (corrupted)" );
467 
468     /* TODO: cleanup */
469     block_Release(file);
470     return NULL;
471 }
472 
473 #define SAVE_IMMEDIATE( a ) \
474     if (fwrite (&(a), sizeof(a), 1, file) != 1) \
475         goto error
476 #define SAVE_FLAG(a) \
477     do { \
478         char b = (a); \
479         SAVE_IMMEDIATE(b); \
480     } while (0)
481 
CacheSaveString(FILE * file,const char * str)482 static int CacheSaveString (FILE *file, const char *str)
483 {
484     uint16_t size = (str != NULL) ? (strlen (str) + 1) : 0;
485 
486     SAVE_IMMEDIATE (size);
487     if (size != 0 && fwrite(str, 1, size, file) != size)
488     {
489 error:
490         return -1;
491     }
492     return 0;
493 }
494 
495 #define SAVE_STRING( a ) \
496     if (CacheSaveString (file, (a))) \
497         goto error
498 
CacheSaveAlign(FILE * file,size_t align)499 static int CacheSaveAlign(FILE *file, size_t align)
500 {
501     assert(align > 0);
502 
503     size_t skip = (-ftell(file)) % align;
504     if (skip == 0)
505         return 0;
506 
507     assert(((ftell(file) + skip) % align) == 0);
508     return fseek(file, skip, SEEK_CUR);
509 }
510 
511 #define SAVE_ALIGNOF(t) \
512     if (CacheSaveAlign(file, alignof (t))) \
513         goto error
514 
CacheSaveConfig(FILE * file,const module_config_t * cfg)515 static int CacheSaveConfig (FILE *file, const module_config_t *cfg)
516 {
517     SAVE_IMMEDIATE (cfg->i_type);
518     SAVE_IMMEDIATE (cfg->i_short);
519     SAVE_FLAG (cfg->b_advanced);
520     SAVE_FLAG (cfg->b_internal);
521     SAVE_FLAG (cfg->b_unsaveable);
522     SAVE_FLAG (cfg->b_safe);
523     SAVE_FLAG (cfg->b_removed);
524     SAVE_STRING (cfg->psz_type);
525     SAVE_STRING (cfg->psz_name);
526     SAVE_STRING (cfg->psz_text);
527     SAVE_STRING (cfg->psz_longtext);
528     SAVE_IMMEDIATE (cfg->list_count);
529 
530     if (IsConfigStringType (cfg->i_type))
531     {
532         SAVE_STRING (cfg->orig.psz);
533         if (cfg->list_count == 0)
534             SAVE_STRING(cfg->list_cb_name);
535 
536         for (unsigned i = 0; i < cfg->list_count; i++)
537             SAVE_STRING (cfg->list.psz[i]);
538     }
539     else
540     {
541         SAVE_IMMEDIATE (cfg->orig);
542         SAVE_IMMEDIATE (cfg->min);
543         SAVE_IMMEDIATE (cfg->max);
544 
545         if (cfg->list_count > 0)
546         {
547             SAVE_ALIGNOF(*cfg->list.i);
548         }
549         else
550             SAVE_STRING(cfg->list_cb_name);
551 
552         for (unsigned i = 0; i < cfg->list_count; i++)
553              SAVE_IMMEDIATE (cfg->list.i[i]);
554     }
555     for (unsigned i = 0; i < cfg->list_count; i++)
556         SAVE_STRING (cfg->list_text[i]);
557 
558     return 0;
559 error:
560     return -1;
561 }
562 
CacheSaveModuleConfig(FILE * file,const vlc_plugin_t * plugin)563 static int CacheSaveModuleConfig(FILE *file, const vlc_plugin_t *plugin)
564 {
565     uint16_t lines = plugin->conf.size;
566 
567     SAVE_IMMEDIATE (lines);
568 
569     for (size_t i = 0; i < lines; i++)
570         if (CacheSaveConfig(file, plugin->conf.items + i))
571            goto error;
572 
573     return 0;
574 error:
575     return -1;
576 }
577 
CacheSaveModule(FILE * file,const module_t * module)578 static int CacheSaveModule(FILE *file, const module_t *module)
579 {
580     SAVE_STRING(module->psz_shortname);
581     SAVE_STRING(module->psz_longname);
582     SAVE_STRING(module->psz_help);
583     SAVE_IMMEDIATE(module->i_shortcuts);
584 
585     for (size_t j = 0; j < module->i_shortcuts; j++)
586          SAVE_STRING(module->pp_shortcuts[j]);
587 
588     SAVE_STRING(module->activate_name);
589     SAVE_STRING(module->deactivate_name);
590     SAVE_STRING(module->psz_capability);
591     SAVE_IMMEDIATE(module->i_score);
592     return 0;
593 error:
594     return -1;
595 }
596 
CacheSaveBank(FILE * file,vlc_plugin_t * const * cache,size_t n)597 static int CacheSaveBank(FILE *file, vlc_plugin_t *const *cache, size_t n)
598 {
599     uint32_t i_file_size = 0;
600 
601     /* Contains version number */
602     if (fputs (CACHE_STRING, file) == EOF)
603         goto error;
604 #ifdef DISTRO_VERSION
605     /* Allow binary maintaner to pass a string to detect new binary version*/
606     if (fputs( DISTRO_VERSION, file ) == EOF)
607         goto error;
608 #endif
609     /* Sub-version number (to avoid breakage in the dev version when cache
610      * structure changes) */
611     i_file_size = CACHE_SUBVERSION_NUM;
612     if (fwrite (&i_file_size, sizeof (i_file_size), 1, file) != 1 )
613         goto error;
614 
615     /* Header marker */
616     i_file_size = ftell( file );
617     if (fwrite (&i_file_size, sizeof (i_file_size), 1, file) != 1)
618         goto error;
619 
620     for (size_t i = 0; i < n; i++)
621     {
622         const vlc_plugin_t *plugin = cache[i];
623         uint32_t count = plugin->modules_count;
624 
625         SAVE_IMMEDIATE(count);
626 
627         for (module_t *module = plugin->module;
628              module != NULL;
629              module = module->next)
630             if (CacheSaveModule(file, module))
631                 goto error;
632 
633         /* Config stuff */
634         if (CacheSaveModuleConfig(file, plugin))
635             goto error;
636 
637         /* Save common info */
638         SAVE_STRING(plugin->textdomain);
639         SAVE_STRING(plugin->path);
640         SAVE_FLAG(plugin->unloadable);
641         SAVE_IMMEDIATE(plugin->mtime);
642         SAVE_IMMEDIATE(plugin->size);
643     }
644 
645     if (fflush (file)) /* flush libc buffers */
646         goto error;
647     return 0; /* success! */
648 
649 error:
650     return -1;
651 }
652 
653 /**
654  * Saves a module cache to disk, and release cache data from memory.
655  */
CacheSave(vlc_object_t * p_this,const char * dir,vlc_plugin_t * const * entries,size_t n)656 void CacheSave(vlc_object_t *p_this, const char *dir,
657                vlc_plugin_t *const *entries, size_t n)
658 {
659     char *filename = NULL, *tmpname = NULL;
660 
661     if (asprintf (&filename, "%s"DIR_SEP CACHE_NAME, dir ) == -1)
662         goto out;
663 
664     if (asprintf (&tmpname, "%s.%"PRIu32, filename, (uint32_t)getpid ()) == -1)
665         goto out;
666     msg_Dbg (p_this, "saving plugins cache %s", filename);
667 
668     FILE *file = vlc_fopen (tmpname, "wb");
669     if (file == NULL)
670     {
671         if (errno != EACCES && errno != ENOENT)
672             msg_Warn (p_this, "cannot create %s: %s", tmpname,
673                       vlc_strerror_c(errno));
674         goto out;
675     }
676 
677     if (CacheSaveBank(file, entries, n))
678     {
679         msg_Warn (p_this, "cannot write %s: %s", tmpname,
680                   vlc_strerror_c(errno));
681         clearerr (file);
682         fclose (file);
683         vlc_unlink (tmpname);
684         goto out;
685     }
686 
687 #if !defined( _WIN32 ) && !defined( __OS2__ )
688     vlc_rename (tmpname, filename); /* atomically replace old cache */
689     fclose (file);
690 #else
691     vlc_unlink (filename);
692     fclose (file);
693     vlc_rename (tmpname, filename);
694 #endif
695 out:
696     free (filename);
697     free (tmpname);
698 }
699 
700 /**
701  * Looks up a plugin file in a table of cached plugins.
702  */
vlc_cache_lookup(vlc_plugin_t ** cache,const char * path)703 vlc_plugin_t *vlc_cache_lookup(vlc_plugin_t **cache, const char *path)
704 {
705     vlc_plugin_t **pp = cache, *plugin;
706 
707     while ((plugin = *pp) != NULL)
708     {
709         if (plugin->path != NULL && !strcmp(plugin->path, path))
710         {
711             *pp = plugin->next;
712             plugin->next = NULL;
713             return plugin;
714         }
715 
716         pp = &plugin->next;
717     }
718 
719     return NULL;
720 }
721 #endif /* HAVE_DYNAMIC_PLUGINS */
722