275 lines
4.5 KiB
C
275 lines
4.5 KiB
C
/*
|
|
* resource_internal.c
|
|
*
|
|
* Resource management
|
|
*
|
|
* Copyright (C) 2013-2021 Sylvain Munaut
|
|
* SPDX-License-Identifier: GPL-3.0-or-later
|
|
*/
|
|
|
|
/*! \addtogroup resource
|
|
* @{
|
|
*/
|
|
|
|
/*! \file resource.c
|
|
* \brief Resource management implementation
|
|
*/
|
|
|
|
#include <assert.h>
|
|
#include <errno.h>
|
|
#include <limits.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
|
|
#include "resource.h"
|
|
#include "resource_internal.h"
|
|
|
|
#ifndef PATH_MAX
|
|
# define PATH_MAX 1024
|
|
#endif
|
|
|
|
|
|
/*! \brief Packed resources */
|
|
extern struct resource_pack __resources[];
|
|
|
|
/*! \brief Loaded resources */
|
|
static LLIST_HEAD(g_cache);
|
|
|
|
|
|
/* ------------------------------------------------------------------------ */
|
|
/* Loading */
|
|
/* ------------------------------------------------------------------------ */
|
|
|
|
|
|
static FILE *
|
|
_rc_try_open(struct resource_cache *rc)
|
|
{
|
|
FILE *fh;
|
|
char p[PATH_MAX];
|
|
char *env;
|
|
|
|
/* Try locally */
|
|
fh = fopen(rc->name, "rb");
|
|
if (fh)
|
|
return fh;
|
|
|
|
/* Try environment path */
|
|
env = getenv("RESOURCE_PATH");
|
|
if (!env)
|
|
return NULL;
|
|
|
|
if ((strlen(env) + strlen(rc->name) + 2) > PATH_MAX)
|
|
return NULL;
|
|
|
|
sprintf(p, "%s/%s", env, rc->name);
|
|
|
|
/* Retry full path */
|
|
fh = fopen(p, "rb");
|
|
|
|
return fh;
|
|
}
|
|
|
|
static int
|
|
_rc_load(struct resource_cache *rc)
|
|
{
|
|
FILE *fh;
|
|
|
|
/* Try to find file */
|
|
fh = _rc_try_open(rc);
|
|
|
|
if (fh) {
|
|
char *data;
|
|
long len;
|
|
|
|
/* Get len */
|
|
fseek(fh, 0, SEEK_END);
|
|
len = ftell(fh);
|
|
fseek(fh, 0, SEEK_SET);
|
|
|
|
/* Get buffer with size for added \0 */
|
|
data = malloc(len+1);
|
|
if (!data)
|
|
goto read_done;
|
|
|
|
/* Fill it */
|
|
if (fread(data, len, 1, fh) != 1) {
|
|
free(data);
|
|
goto read_done;
|
|
}
|
|
|
|
data[len] = '\x00';
|
|
|
|
/* Store result */
|
|
rc->data = data;
|
|
rc->len = len;
|
|
rc->flags |= RES_FLAG_MALLOCED;
|
|
|
|
read_done:
|
|
/* Done */
|
|
fclose(fh);
|
|
}
|
|
|
|
/* Search packed resource */
|
|
if (!rc->data)
|
|
{
|
|
struct resource_pack *rp = __resources;
|
|
|
|
while (rp->name) {
|
|
if (!strcmp(rp->name, rc->name)) {
|
|
rc->data = rp->data;
|
|
rc->len = rp->len;
|
|
break;
|
|
}
|
|
rp++;
|
|
}
|
|
}
|
|
|
|
return rc->data ? 0 : -ENOENT;
|
|
}
|
|
|
|
static void
|
|
_rc_unload(struct resource_cache *rc)
|
|
{
|
|
if (rc->flags & RES_FLAG_MALLOCED)
|
|
free((void*)rc->data);
|
|
}
|
|
|
|
|
|
/* ------------------------------------------------------------------------ */
|
|
/* Internal cache */
|
|
/* ------------------------------------------------------------------------ */
|
|
|
|
static struct resource_cache *
|
|
_rc_alloc(const char *name)
|
|
{
|
|
size_t l;
|
|
struct resource_cache *rc;
|
|
char *n;
|
|
|
|
l = sizeof(struct resource_cache) + strlen(name) + 1;
|
|
rc = malloc(l);
|
|
if (!rc)
|
|
return NULL;
|
|
|
|
memset(rc, 0x00, l);
|
|
|
|
n = &rc->extra[0];
|
|
strcpy(n, name);
|
|
rc->name = n;
|
|
|
|
return rc;
|
|
}
|
|
|
|
static struct resource_cache *
|
|
_rc_find_by_name(const char *name)
|
|
{
|
|
struct resource_cache *rc;
|
|
|
|
llist_for_each_entry(struct resource_cache, rc, &g_cache, head) {
|
|
if (!strcmp(rc->name, name))
|
|
return rc;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static struct resource_cache *
|
|
_rc_find_by_data(const void *data)
|
|
{
|
|
struct resource_cache *rc;
|
|
|
|
llist_for_each_entry(struct resource_cache, rc, &g_cache, head) {
|
|
if (rc->data == data)
|
|
return rc;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static struct resource_cache *
|
|
_rc_get(struct resource_cache *rc)
|
|
{
|
|
rc->refcnt++;
|
|
return rc;
|
|
}
|
|
|
|
static struct resource_cache *
|
|
_rc_get_new(const char *name)
|
|
{
|
|
struct resource_cache *rc;
|
|
|
|
rc = _rc_alloc(name);
|
|
if (!rc)
|
|
return NULL;
|
|
|
|
if (_rc_load(rc)) {
|
|
free(rc);
|
|
return NULL;
|
|
}
|
|
|
|
llist_add(&rc->head, &g_cache);
|
|
|
|
return _rc_get(rc);
|
|
}
|
|
|
|
static struct resource_cache *
|
|
_rc_put(struct resource_cache *rc)
|
|
{
|
|
assert(rc->refcnt > 0);
|
|
|
|
rc->refcnt--;
|
|
|
|
if (rc->refcnt == 0) {
|
|
llist_del(&rc->head);
|
|
_rc_unload(rc);
|
|
free(rc);
|
|
rc = NULL;
|
|
}
|
|
|
|
return rc;
|
|
}
|
|
|
|
|
|
/* ------------------------------------------------------------------------ */
|
|
/* Public API */
|
|
/* ------------------------------------------------------------------------ */
|
|
|
|
const void *
|
|
resource_get(const char *name, int *len)
|
|
{
|
|
struct resource_cache *rc;
|
|
|
|
/* Search the cache */
|
|
rc = _rc_find_by_name(name);
|
|
if (rc)
|
|
goto done;
|
|
|
|
/* No such luck ? Need a new entry */
|
|
rc = _rc_get_new(name);
|
|
if (!rc)
|
|
return NULL;
|
|
|
|
done:
|
|
if (rc && len)
|
|
*len = rc->len;
|
|
|
|
return rc ? rc->data : NULL;
|
|
}
|
|
|
|
void
|
|
resource_put(const void *r)
|
|
{
|
|
struct resource_cache *rc;
|
|
|
|
/* Search the cache */
|
|
rc = _rc_find_by_data(r);
|
|
if (!rc)
|
|
return;
|
|
|
|
/* We're done with it */
|
|
_rc_put(rc);
|
|
}
|
|
|
|
/*! @} */
|