/*****************************************************************************
* cache.c: Plugins cache
*****************************************************************************
* Copyright (C) 2001-2007 VLC authors and VideoLAN
*
* Authors: Sam Hocevar <sam@zoy.org>
* Ethan C. Baldridge <BaldridgeE@cadmus.com>
* Hans-Peter Jansen <hpj@urpla.net>
* Gildas Bazin <gbazin@videolan.org>
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation; either version 2.1 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
*****************************************************************************/
#ifdef HAVE_CONFIG_H
# include "config.h"
#endif
#include <stdalign.h>
#include <stdatomic.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <assert.h>
#include <vlc_common.h>
#include <vlc_block.h>
#include "../libvlc.h"
#include <vlc_plugin.h>
#include <errno.h>
#include "config/configuration.h"
#include <vlc_fs.h>
#include "modules/modules.h"
/*****************************************************************************
* Local prototypes
*****************************************************************************/
#ifdef HAVE_DYNAMIC_PLUGINS
/* Sub-version number
* (only used to avoid breakage in dev version when cache structure changes) */
#define CACHE_SUBVERSION_NUM 36
/* Cache filename */
#define CACHE_NAME "plugins.dat"
/* Magic for the cache filename */
#define CACHE_STRING "cache "PACKAGE_NAME" "PACKAGE_VERSION
static int vlc_cache_load_immediate(void *out, block_t *in, size_t size)
{
if (in->i_buffer < size)
return -1;
memcpy(out, in->p_buffer, size);
in->p_buffer += size;
in->i_buffer -= size;
return 0;
}
static int vlc_cache_load_bool(bool *out, block_t *in)
{
unsigned char b;
if (vlc_cache_load_immediate(&b, in, 1) || b > 1)
return -1;
*out = b;
return 0;
}
static int vlc_cache_load_array(const void **p, size_t size, size_t n,
block_t *file)
{
if (n == 0)
{
*p = NULL;
return 0;
}
if (unlikely(size * n < size))
return -1;
size *= n;
if (file->i_buffer < size)
return -1;
*p = file->p_buffer;
file->p_buffer += size;
file->i_buffer -= size;
return 0;
}
static int vlc_cache_load_string(const char **restrict p, block_t *file)
{
uint16_t size;
if (vlc_cache_load_immediate(&size, file, sizeof (size)) || size > 16384)
return -1;
if (size == 0)
{
*p = NULL;
return 0;
}
const char *str = (char *)file->p_buffer;
if (file->i_buffer < size || str[size - 1] != '\0')
return -1;
file->p_buffer += size;
file->i_buffer -= size;
*p = str;
return 0;
}
static int vlc_cache_load_align(size_t align, block_t *file)
{
assert(align > 0);
size_t skip = (-(uintptr_t)file->p_buffer) % align;
if (skip == 0)
return 0;
assert(skip < align);
if (file->i_buffer < skip)
return -1;
file->p_buffer += skip;
file->i_buffer -= skip;
assert((((uintptr_t)file->p_buffer) % align) == 0);
return 0;
}
#define LOAD_IMMEDIATE(a) \
if (vlc_cache_load_immediate(&(a), file, sizeof (a))) \
goto error
#define LOAD_FLAG(a) \
do \
{ \
bool b; \
if (vlc_cache_load_bool(&b, file)) \
goto error; \
(a) = b; \
} while (0)
#define LOAD_ARRAY(a,n) \
do \
{ \
const void *base; \
if (vlc_cache_load_array(&base, sizeof (*(a)), (n), file)) \
goto error; \
(a) = base; \
} while (0)
#define LOAD_STRING(a) \
if (vlc_cache_load_string(&(a), file)) \
goto error
#define LOAD_ALIGNOF(t) \
if (vlc_cache_load_align(alignof(t), file)) \
goto error
static int vlc_cache_load_config(struct vlc_param *param, block_t *file)
{
module_config_t *cfg = ¶m->item;
LOAD_IMMEDIATE (cfg->i_type);
LOAD_IMMEDIATE (param->shortname);
LOAD_FLAG (param->internal);
LOAD_FLAG (param->unsaved);
LOAD_FLAG (param->safe);
LOAD_FLAG (param->obsolete);
LOAD_STRING (cfg->psz_type);
LOAD_STRING (cfg->psz_name);
LOAD_STRING (cfg->psz_text);
LOAD_STRING (cfg->psz_longtext);
LOAD_IMMEDIATE (cfg->list_count);
if (IsConfigStringType (cfg->i_type))
{
const char *psz;
LOAD_STRING(psz);
cfg->orig.psz = (char *)psz;
atomic_init(¶m->value.str, NULL);
vlc_param_SetString(param, psz);
if (cfg->list_count)
cfg->list.psz = xmalloc (cfg->list_count * sizeof (char *));
for (unsigned i = 0; i < cfg->list_count; i++)
{
LOAD_STRING (cfg->list.psz[i]);
if (cfg->list.psz[i] == NULL /* NULL -> empty string */
&& (cfg->list.psz[i] = calloc (1, 1)) == NULL)
goto error;
}
}
else
{
LOAD_IMMEDIATE (cfg->orig);
LOAD_IMMEDIATE (cfg->min);
LOAD_IMMEDIATE (cfg->max);
if (IsConfigFloatType(cfg->i_type))
atomic_store_explicit(¶m->value.f, cfg->orig.f,
memory_order_relaxed);
else
atomic_store_explicit(¶m->value.i, cfg->orig.i,
memory_order_relaxed);
cfg->value = cfg->orig;
if (cfg->list_count)
{
LOAD_ALIGNOF(int);
}
LOAD_ARRAY(cfg->list.i, cfg->list_count);
}
cfg->list_text = xmalloc (cfg->list_count * sizeof (char *));
for (unsigned i = 0; i < cfg->list_count; i++)
{
LOAD_STRING (cfg->list_text[i]);
if (cfg->list_text[i] == NULL /* NULL -> empty string */
&& (cfg->list_text[i] = calloc (1, 1)) == NULL)
goto error;
}
return 0;
error:
return -1; /* FIXME: leaks */
}
static int vlc_cache_load_plugin_config(vlc_plugin_t *plugin, block_t *file)
{
uint16_t lines;
/* Calculate the structure length */
LOAD_IMMEDIATE (lines);
/* Allocate memory */
if (lines)
{
plugin->conf.params = calloc(lines, sizeof (struct vlc_param));
if (unlikely(plugin->conf.params == NULL))
{
plugin->conf.size = 0;
return -1;
}
}
else
plugin->conf.params = NULL;
plugin->conf.size = lines;
/* Do the duplication job */
for (size_t i = 0; i < lines; i++)
{
struct vlc_param *param = plugin->conf.params + i;
module_config_t *item = ¶m->item;
if (vlc_cache_load_config(param, file))
return -1;
if (CONFIG_ITEM(item->i_type))
{
plugin->conf.count++;
if (item->i_type == CONFIG_ITEM_BOOL)
plugin->conf.booleans++;
}
param->owner = plugin;
}
return 0;
error:
return -1; /* FIXME: leaks */
}
static int vlc_cache_load_module(vlc_plugin_t *plugin, block_t *file)
{
module_t *module = vlc_module_create(plugin);
if (unlikely(module == NULL))
return -1;
LOAD_STRING(module->psz_shortname);
LOAD_STRING(module->psz_longname);
LOAD_STRING(module->psz_help);
LOAD_STRING(module->psz_help_html);
LOAD_IMMEDIATE(module->i_shortcuts);
if (module->i_shortcuts > MODULE_SHORTCUT_MAX)
goto error;
else
{
module->pp_shortcuts =
xmalloc (sizeof (*module->pp_shortcuts) * module->i_shortcuts);
for (unsigned j = 0; j < module->i_shortcuts; j++)
LOAD_STRING(module->pp_shortcuts[j]);
}
LOAD_STRING(module->activate_name);
LOAD_STRING(module->deactivate_name);
LOAD_STRING(module->psz_capability);
LOAD_IMMEDIATE(module->i_score);
return 0;
error:
return -1;
}
static vlc_plugin_t *vlc_cache_load_plugin(block_t *file)
{
vlc_plugin_t *plugin = vlc_plugin_create();
if (unlikely(plugin == NULL))
return NULL;
uint32_t modules;
LOAD_IMMEDIATE(modules);
for (size_t i = 0; i < modules; i++)
if (vlc_cache_load_module(plugin, file))
goto error;
if (vlc_cache_load_plugin_config(plugin, file))
goto error;
LOAD_STRING(plugin->textdomain);
const char *path;
LOAD_STRING(path);
if (path == NULL)
goto error;
plugin->path = strdup(path);
if (unlikely(plugin->path == NULL))
goto error;
LOAD_FLAG(plugin->unloadable);
LOAD_IMMEDIATE(plugin->mtime);
LOAD_IMMEDIATE(plugin->size);
if (plugin->textdomain != NULL)
vlc_bindtextdomain(plugin->textdomain);
return plugin;
error:
vlc_plugin_destroy(plugin);
return NULL;
}
/**
* Loads a plugins cache file.
*
* This function will load the plugin cache if present and valid. This cache
* will in turn be queried by AllocateAllPlugins() to see if it needs to
* actually load the dynamically loadable module.
* This allows us to only fully load plugins when they are actually used.
*/
vlc_plugin_t *vlc_cache_load(libvlc_int_t *p_this, const char *dir,
block_t **backingp)
{
char *psz_filename;
assert( dir != NULL );
if( asprintf( &psz_filename, "%s"DIR_SEP CACHE_NAME, dir ) == -1 )
return NULL;
msg_Dbg( p_this, "loading plugins cache file %s", psz_filename );
block_t *file = block_FilePath(psz_filename, false);
if (file == NULL)
msg_Warn(p_this, "cannot read %s: %s", psz_filename,
vlc_strerror_c(errno));
free(psz_filename);
if (file == NULL)
return NULL;
/* Check the file is a plugins cache */
char cachestr[sizeof (CACHE_STRING) - 1];
if (vlc_cache_load_immediate(cachestr, file, sizeof (cachestr))
|| memcmp(cachestr, CACHE_STRING, sizeof (cachestr)))
{
msg_Warn( p_this, "This doesn't look like a valid plugins cache" );
block_Release(file);
return NULL;
}
#ifdef DISTRO_VERSION
/* Check for distribution specific version */
char distrostr[sizeof (DISTRO_VERSION) - 1];
if (vlc_cache_load_immediate(distrostr, file, sizeof (distrostr))
|| memcmp(distrostr, DISTRO_VERSION, sizeof (distrostr)))
{
msg_Warn( p_this, "This doesn't look like a valid plugins cache" );
block_Release(file);
return NULL;
}
#endif
/* Check sub-version number */
uint32_t marker;
if (vlc_cache_load_immediate(&marker, file, sizeof (marker))
|| marker != CACHE_SUBVERSION_NUM)
{
msg_Warn( p_this, "This doesn't look like a valid plugins cache "
"(corrupted header)" );
block_Release(file);
return NULL;
}
/* Check header marker */
if (vlc_cache_load_immediate(&marker, file, sizeof (marker))
#ifdef DISTRO_VERSION
|| marker != (sizeof (cachestr) + sizeof (distrostr) + sizeof (marker))
#else
|| marker != (sizeof (cachestr) + sizeof (marker))
#endif
)
{
msg_Warn( p_this, "This doesn't look like a valid plugins cache "
"(corrupted header)" );
block_Release(file);
return NULL;
}
vlc_plugin_t *cache = NULL;
while (file->i_buffer > 0)
{
vlc_plugin_t *plugin = vlc_cache_load_plugin(file);
if (plugin == NULL)
goto error;
if (unlikely(asprintf(&plugin->abspath, "%s" DIR_SEP "%s", dir,
plugin->path) == -1))
{
plugin->abspath = NULL;
vlc_plugin_destroy(plugin);
goto error;
}
plugin->next = cache;
cache = plugin;
}
file->p_next = *backingp;
*backingp = file;
return cache;
error:
msg_Warn( p_this, "plugins cache not loaded (corrupted)" );
/* TODO: cleanup */
block_Release(file);
return NULL;
}
#define SAVE_IMMEDIATE( a ) \
if (fwrite (&(a), sizeof(a), 1, file) != 1) \
goto error
#define SAVE_FLAG(a) \
do { \
char b = (a); \
SAVE_IMMEDIATE(b); \
} while (0)
static int CacheSaveString (FILE *file, const char *str)
{
uint16_t size = (str != NULL) ? (strlen (str) + 1) : 0;
SAVE_IMMEDIATE (size);
if (size != 0 && fwrite(str, 1, size, file) != size)
{
error:
return -1;
}
return 0;
}
#define SAVE_STRING( a ) \
if (CacheSaveString (file, (a))) \
goto error
static int CacheSaveAlign(FILE *file, size_t align)
{
assert(align > 0);
size_t skip = (-ftell(file)) % align;
if (skip == 0)
return 0;
assert(((ftell(file) + skip) % align) == 0);
return fseek(file, skip, SEEK_CUR);
}
#define SAVE_ALIGNOF(t) \
if (CacheSaveAlign(file, alignof (t))) \
goto error
static int CacheSaveConfig(FILE *file, const struct vlc_param *param)
{
const module_config_t *cfg = ¶m->item;
SAVE_IMMEDIATE (cfg->i_type);
SAVE_IMMEDIATE (param->shortname);
SAVE_FLAG (param->internal);
SAVE_FLAG (param->unsaved);
SAVE_FLAG (param->safe);
SAVE_FLAG (param->obsolete);
SAVE_STRING (cfg->psz_type);
SAVE_STRING (cfg->psz_name);
SAVE_STRING (cfg->psz_text);
SAVE_STRING (cfg->psz_longtext);
SAVE_IMMEDIATE (cfg->list_count);
if (IsConfigStringType (cfg->i_type))
{
SAVE_STRING (cfg->orig.psz);
for (unsigned i = 0; i < cfg->list_count; i++)
SAVE_STRING (cfg->list.psz[i]);
}
else
{
SAVE_IMMEDIATE (cfg->orig);
SAVE_IMMEDIATE (cfg->min);
SAVE_IMMEDIATE (cfg->max);
if (cfg->list_count > 0)
{
SAVE_ALIGNOF(int);
}
for (unsigned i = 0; i < cfg->list_count; i++)
SAVE_IMMEDIATE (cfg->list.i[i]);
}
for (unsigned i = 0; i < cfg->list_count; i++)
SAVE_STRING (cfg->list_text[i]);
return 0;
error:
return -1;
}
static int CacheSaveModuleConfig(FILE *file, const vlc_plugin_t *plugin)
{
uint16_t lines = plugin->conf.size;
SAVE_IMMEDIATE (lines);
for (size_t i = 0; i < lines; i++)
if (CacheSaveConfig(file, plugin->conf.params + i))
goto error;
return 0;
error:
return -1;
}
static int CacheSaveModule(FILE *file, const module_t *module)
{
SAVE_STRING(module->psz_shortname);
SAVE_STRING(module->psz_longname);
SAVE_STRING(module->psz_help);
SAVE_STRING(module->psz_help_html);
SAVE_IMMEDIATE(module->i_shortcuts);
for (size_t j = 0; j < module->i_shortcuts; j++)
SAVE_STRING(module->pp_shortcuts[j]);
SAVE_STRING(module->activate_name);
SAVE_STRING(module->deactivate_name);
SAVE_STRING(module->psz_capability);
SAVE_IMMEDIATE(module->i_score);
return 0;
error:
return -1;
}
static int CacheSaveBank(FILE *file, vlc_plugin_t *const *cache, size_t n)
{
uint32_t i_file_size = 0;
/* Contains version number */
if (fputs (CACHE_STRING, file) == EOF)
goto error;
#ifdef DISTRO_VERSION
/* Allow binary maintainer to pass a string to detect new binary version*/
if (fputs( DISTRO_VERSION, file ) == EOF)
goto error;
#endif
/* Sub-version number (to avoid breakage in the dev version when cache
* structure changes) */
i_file_size = CACHE_SUBVERSION_NUM;
if (fwrite (&i_file_size, sizeof (i_file_size), 1, file) != 1 )
goto error;
/* Header marker */
i_file_size = ftell( file );
if (fwrite (&i_file_size, sizeof (i_file_size), 1, file) != 1)
goto error;
for (size_t i = 0; i < n; i++)
{
const vlc_plugin_t *plugin = cache[i];
uint32_t count = plugin->modules_count;
SAVE_IMMEDIATE(count);
for (module_t *module = plugin->module;
module != NULL;
module = module->next)
if (CacheSaveModule(file, module))
goto error;
/* Config stuff */
if (CacheSaveModuleConfig(file, plugin))
goto error;
/* Save common info */
SAVE_STRING(plugin->textdomain);
SAVE_STRING(plugin->path);
SAVE_FLAG(plugin->unloadable);
SAVE_IMMEDIATE(plugin->mtime);
SAVE_IMMEDIATE(plugin->size);
}
if (fflush (file)) /* flush libc buffers */
goto error;
return 0; /* success! */
error:
return -1;
}
/**
* Saves a module cache to disk, and release cache data from memory.
*/
void CacheSave(libvlc_int_t *p_this, const char *dir,
vlc_plugin_t *const *entries, size_t n)
{
char *filename = NULL, *tmpname = NULL;
if (asprintf (&filename, "%s"DIR_SEP CACHE_NAME, dir ) == -1)
return;
if (asprintf (&tmpname, "%s.%"PRIu32, filename, (uint32_t)getpid ()) == -1)
{
free (filename);
return;
}
msg_Dbg (p_this, "saving plugins cache %s", filename);
FILE *file = vlc_fopen (tmpname, "wb");
if (file == NULL)
{
if (errno != EACCES && errno != ENOENT)
msg_Warn (p_this, "cannot create %s: %s", tmpname,
vlc_strerror_c(errno));
goto out;
}
if (CacheSaveBank(file, entries, n))
{
msg_Warn (p_this, "cannot write %s: %s", tmpname,
vlc_strerror_c(errno));
clearerr (file);
fclose (file);
vlc_unlink (tmpname);
goto out;
}
#if !defined( _WIN32 ) && !defined( __OS2__ )
vlc_rename (tmpname, filename); /* atomically replace old cache */
fclose (file);
#else
vlc_unlink (filename);
fclose (file);
vlc_rename (tmpname, filename);
#endif
out:
free (filename);
free (tmpname);
}
/**
* Looks up a plugin file in a table of cached plugins.
*/
vlc_plugin_t *vlc_cache_lookup(vlc_plugin_t **cache, const char *path)
{
vlc_plugin_t **pp = cache, *plugin;
while ((plugin = *pp) != NULL)
{
if (plugin->path != NULL && !strcmp(plugin->path, path))
{
*pp = plugin->next;
plugin->next = NULL;
return plugin;
}
pp = &plugin->next;
}
return NULL;
}
#endif /* HAVE_DYNAMIC_PLUGINS */
¤ Dauer der Verarbeitung: 0.3 Sekunden
(vorverarbeitet am 2026-11-04)
¤
*© Formatika GbR, Deutschland
|
|