/* * 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 . */ #include "filebundle.h" #include "tvheadend.h" #include #include #include #include #if ENABLE_ZLIB #include #endif #include #include #include /* ************************************************************************** * 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; }