tvheadend/src/filebundle.c

516 lines
12 KiB
C

/*
* TV headend - File bundles
* Copyright (C) 2012 Adam Sutton
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "filebundle.h"
#include "tvheadend.h"
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#if ENABLE_ZLIB
#include <zlib.h>
#endif
#include <sys/types.h>
#include <sys/stat.h>
#include <assert.h>
/* **************************************************************************
* Opaque data types
* *************************************************************************/
/* File bundle dir handle */
struct filebundle_dir
{
fb_type type;
fb_dirent dirent;
union {
struct {
char *root;
DIR *cur;
} d;
struct {
const filebundle_entry_t *root;
filebundle_entry_t *cur;
} b;
};
};
/* File bundle file handle */
struct filebundle_file
{
fb_type type;
size_t size;
int gzip;
uint8_t *buf;
size_t pos;
union {
struct {
FILE *cur;
} d;
struct {
const filebundle_entry_t *root;
} b;
};
};
/* **************************************************************************
* Compression/Decompression
* *************************************************************************/
#if (ENABLE_ZLIB && ENABLE_BUNDLE)
static uint8_t *_fb_inflate ( const uint8_t *data, size_t size, size_t orig )
{
int err;
z_stream zstr;
uint8_t *bufin, *bufout;
/* Setup buffers */
bufin = malloc(size);
bufout = malloc(orig);
memcpy(bufin, data, size);
/* Setup zlib */
memset(&zstr, 0, sizeof(zstr));
inflateInit2(&zstr, 31);
zstr.avail_in = size;
zstr.next_in = bufin;
zstr.avail_out = orig;
zstr.next_out = bufout;
/* Decompress */
err = inflate(&zstr, Z_NO_FLUSH);
if ( err != Z_STREAM_END || zstr.avail_out != 0 ) {
free(bufout);
bufout = NULL;
}
free(bufin);
inflateEnd(&zstr);
return bufout;
}
#endif
#if ENABLE_ZLIB
static uint8_t *_fb_deflate ( const uint8_t *data, size_t orig, size_t *size )
{
int err;
z_stream zstr;
uint8_t *bufin, *bufout;
/* Setup buffers */
bufin = malloc(orig);
bufout = malloc(orig);
memcpy(bufin, data, orig);
/* Setup zlib */
memset(&zstr, 0, sizeof(zstr));
err = deflateInit2(&zstr, 9, Z_DEFLATED, 31, 9, Z_DEFAULT_STRATEGY);
zstr.avail_in = orig;
zstr.next_in = bufin;
zstr.avail_out = orig;
zstr.next_out = bufout;
/* Decompress */
err = deflate(&zstr, Z_FINISH);
if ( (err != Z_STREAM_END && err != Z_OK) || zstr.total_out == 0 ) {
free(bufout);
bufout = NULL;
} else {
*size = zstr.total_out;
}
free(bufin);
deflateEnd(&zstr);
return bufout;
}
#endif
/* **************************************************************************
* Miscellanous
* *************************************************************************/
/* Get path stats */
// TODO: this could do with being more efficient
int fb_stat ( const char *path, struct filebundle_stat *st )
{
int ret = 1;
fb_dir *dir;
fb_file *fp;
if (*path == '/') {
struct stat _st;
if (!lstat(path, &_st)) {
st->type = FB_DIRECT;
st->is_dir = S_ISDIR(_st.st_mode) ? 1 : 0;
st->size = _st.st_size;
ret = 0;
}
} else if ((dir = fb_opendir(path))) {
st->type = dir->type;
st->is_dir = 1;
st->size = 0;
ret = 0;
fb_closedir(dir);
} else if ((fp = fb_open(path, 0, 0))) {
st->type = fp->type;
st->is_dir = 0;
st->size = fp->size;
ret = 0;
fb_close(fp);
}
return ret;
}
/* **************************************************************************
* Directory processing
* *************************************************************************/
/* Open directory (directly) */
static fb_dir *_fb_opendir ( const char *root, const char *path )
{
DIR *dir;
char buf[512];
fb_dir *ret = NULL;
/* Pre-pend root */
if (root) {
snprintf(buf, sizeof(buf), "%s/%s", root, path);
path = buf;
}
/* Open */
if ((dir = opendir(path))) {
ret = calloc(1, sizeof(fb_dir));
ret->type = FB_DIRECT;
ret->d.root = strdup(path);
ret->d.cur = dir;
}
return ret;
}
/* Open directory */
fb_dir *fb_opendir ( const char *path )
{
fb_dir *ret = NULL;
/* In-direct (search) */
if (*path != '/') {
/* Bundle */
#if ENABLE_BUNDLE
char *tmp1 = strdup(path);
char *tmp2 = strtok(tmp1, "/");
filebundle_entry_t *fb = filebundle_root;
while (fb && tmp2) {
if (fb->type == FB_DIR && !strcmp(fb->name, tmp2)) {
tmp2 = strtok(NULL, "/");
if (tmp2) fb = fb->d.child;
} else {
fb = fb->next;
}
}
free(tmp1);
/* Found */
if (fb) {
ret = calloc(1, sizeof(fb_dir));
ret->type = FB_BUNDLE;
ret->b.root = fb;
ret->b.cur = fb->d.child;
}
#endif
/* Local */
if (!ret) ret = _fb_opendir(tvheadend_cwd, path);
/* System */
if (!ret) ret = _fb_opendir(TVHEADEND_DATADIR, path);
/* Direct */
} else {
ret = _fb_opendir(NULL, path);
}
return ret;
}
/* Close directory */
void fb_closedir ( fb_dir *dir )
{
if (dir->type == FB_DIRECT) {
closedir(dir->d.cur);
free(dir->d.root);
}
free(dir);
}
/* Iterate through entries */
fb_dirent *fb_readdir ( fb_dir *dir )
{
fb_dirent *ret = NULL;
if (dir->type == FB_BUNDLE) {
if (dir->b.cur) {
strcpy(dir->dirent.name, dir->b.cur->name);
dir->dirent.type = dir->b.cur->type;
dir->b.cur = dir->b.cur->next;
ret = &dir->dirent;
}
} else {
struct dirent *de = readdir(dir->d.cur);
if (de) {
struct stat st;
char buf[512];
snprintf(buf, sizeof(buf), "%s/%s", dir->d.root, de->d_name);
strcpy(dir->dirent.name, de->d_name);
dir->dirent.type = FB_UNKNOWN;
if (!lstat(buf, &st))
dir->dirent.type = S_ISDIR(st.st_mode) ? FB_DIR : FB_FILE;
ret = &dir->dirent;
}
}
return ret;
}
/* Get entry list */
int fb_scandir ( const char *path, fb_dirent ***list )
{
int i, ret = -1;
struct dirent **de;
fb_dir *dir;
if (!(dir = fb_opendir(path))) return -1;
/* Direct */
if (dir->type == FB_DIRECT) {
if ((ret = scandir(dir->d.root, &de, NULL, NULL)) != -1) {
if (ret == 0) return 0;
*list = malloc(sizeof(fb_dirent*)*ret);
for (i = 0; i < ret; i++) {
(*list)[i] = calloc(1, sizeof(fb_dirent));
strcpy((*list)[i]->name, de[i]->d_name);
(*list)[i]->type = FB_DIRECT;
free(de[i]);
}
free(de);
}
/* Bundle */
} else {
const filebundle_entry_t *fb;
ret = dir->b.root->d.count;
fb = dir->b.root->d.child;
*list = malloc(ret * sizeof(fb_dirent));
i = 0;
while (fb) {
(*list)[i] = calloc(1, sizeof(fb_dirent));
strcpy((*list)[i]->name, fb->name);
fb = fb->next;
i++;
}
}
/* Close */
fb_closedir(dir);
return ret;
}
/* **************************************************************************
* Directory processing
* *************************************************************************/
/* Open file (with dir and name) */
// Note: decompress is only used on bundled (not direct) files that were
// compressed at the time the bundle was generated, 1 can safely
// be passed in though and will be ignored if this is not the case
// Note: compress will work on EITHER type (but will be ignored for already
// compressed bundles)
fb_file *fb_open2
( const fb_dir *dir, const char *name, int decompress, int compress )
{
assert(!decompress || !compress);
fb_file *ret = NULL;
/* Bundle file */
if (dir->type == FB_BUNDLE) {
const filebundle_entry_t *fb = dir->b.root->d.child;
while (fb) {
if (!strcmp(name, fb->name)) break;
fb = fb->next;
}
if (fb) {
ret = calloc(1, sizeof(fb_file));
ret->type = FB_BUNDLE;
ret->size = fb->f.size;
ret->gzip = fb->f.orig != -1;
ret->b.root = fb;
/* Inflate the file */
if (fb->f.orig != -1 && decompress) {
#if (ENABLE_ZLIB && ENABLE_BUNDLE)
ret->gzip = 0;
ret->size = fb->f.orig;
ret->buf = _fb_inflate(fb->f.data, fb->f.size, fb->f.orig);
if (!ret->buf) {
free(ret);
ret = NULL;
}
#else
free(ret);
ret = NULL;
#endif
}
}
/* Direct file */
} else {
char path[512];
snprintf(path, sizeof(path), "%s/%s", dir->d.root, name);
FILE *fp = fopen(path, "r");
if (fp) {
struct stat st;
lstat(path, &st);
ret = calloc(1, sizeof(fb_file));
ret->type = FB_DIRECT;
ret->size = st.st_size;
ret->gzip = 0;
ret->d.cur = fp;
}
}
/* Compress */
#if ENABLE_ZLIB
if (ret && !ret->gzip && compress) {
ret->gzip = 1;
/* Get data */
if (ret->type == FB_BUNDLE) {
const uint8_t *data;
data = ret->b.root->f.data;
ret->buf = _fb_deflate(data, ret->size, &ret->size);
} else {
uint8_t *data = malloc(ret->size);
ssize_t c = fread(data, 1, ret->size, ret->d.cur);
if (c == ret->size)
ret->buf = _fb_deflate(data, ret->size, &ret->size);
fclose(ret->d.cur);
ret->d.cur = NULL;
free(data);
}
/* Cleanup */
if (!ret->buf) {
free(ret);
ret = NULL;
}
}
#endif
return ret;
}
/* Open file */
fb_file *fb_open ( const char *path, int decompress, int compress )
{
fb_file *ret = NULL;
fb_dir *dir = NULL;
char *tmp = strdup(path);
char *pos = strrchr(tmp, '/');
if (!pos) {
free(tmp);
return NULL;
}
/* Find directory */
*pos = '\0';
dir = fb_opendir(tmp);
if (!dir) {
free(tmp);
return NULL;
}
/* Open */
ret = fb_open2(dir, pos+1, decompress, compress);
fb_closedir(dir);
free(tmp);
return ret;
}
/* Close file */
void fb_close ( fb_file *fp )
{
if (fp->type == FB_DIRECT && fp->d.cur) {
fclose(fp->d.cur);
}
if (fp->buf)
free(fp->buf);
free(fp);
}
/* Get the files size */
size_t fb_size ( fb_file *fp )
{
return fp->size;
}
/* Check if compressed */
int fb_gzipped ( fb_file *fp )
{
return fp->gzip;
}
/* Check for EOF */
int fb_eof ( fb_file *fp )
{
return fp->pos >= fp->size;
}
/* Read some data */
ssize_t fb_read ( fb_file *fp, void *buf, size_t count )
{
if (fb_eof(fp)) {
return -1;
} else if (fp->buf) {
count = MIN(count, fp->size - fp->pos);
memcpy(buf, fp->buf + fp->pos, count);
fp->pos += count;
} else if (fp->type == FB_DIRECT) {
fp->pos += fread(buf, 1, count, fp->d.cur);
} else {
count = MIN(count, fp->b.root->f.size - fp->pos);
memcpy(buf, fp->b.root->f.data + fp->pos, count);
fp->pos += count;
}
return count;
}
/* Read a line */
char *fb_gets ( fb_file *fp, void *buf, size_t count )
{
ssize_t c = 0, err;
while ((err = fb_read(fp, buf+c, 1)) && c < (count-1)) {
char b = ((char*)buf)[c];
c++;
if (b == '\n' || b == '\0') break;
}
if (err < 0) return NULL;
((char*)buf)[c] = '\0';
return buf;
}