You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
343 lines
10 KiB
343 lines
10 KiB
/*
|
|
This is a simple read-only implementation of a file system. It uses a block of data coming from the
|
|
mkespfsimg tool, and can use that block to do abstracted operations on the files that are in there.
|
|
It's written for use with httpd, but doesn't need to be used as such.
|
|
*/
|
|
|
|
/*
|
|
* ----------------------------------------------------------------------------
|
|
* "THE BEER-WARE LICENSE" (Revision 42):
|
|
* Jeroen Domburg <jeroen@spritesmods.com> wrote this file. As long as you retain
|
|
* this notice you can do whatever you want with this stuff. If we meet some day,
|
|
* and you think this stuff is worth it, you can buy me a beer in return.
|
|
* ----------------------------------------------------------------------------
|
|
*/
|
|
|
|
|
|
#include <stdio.h>
|
|
#include <stdint.h>
|
|
#include <string.h>
|
|
#include <endian.h>
|
|
|
|
#include "espfsformat.h"
|
|
#include "espfs.h"
|
|
|
|
#include "httpd-logging.h"
|
|
|
|
// internal fields
|
|
struct EspFsFile {
|
|
/// Header pointer
|
|
size_t headerPos;
|
|
/// Decompressor type
|
|
uint8_t decompressor;
|
|
size_t posDecomp;
|
|
size_t posStart;
|
|
size_t posComp;
|
|
heatshrink_decoder *decompData;
|
|
};
|
|
|
|
// forward declaration for use in the stand-alone espfs tool
|
|
int httpdPlatEspfsRead(void *dest, size_t offset, size_t len);
|
|
|
|
|
|
EspFsInitResult espFsInit()
|
|
{
|
|
// check if there is valid header at address
|
|
EspFsHeader testHeader;
|
|
int rv;
|
|
rv = httpdPlatEspfsRead(&testHeader, 0, sizeof(EspFsHeader));
|
|
if (rv != 0 || testHeader.magic != EFS_MAGIC) {
|
|
espfs_error("[EspFS] Invalid magic on first file header");
|
|
return ESPFS_INIT_RESULT_NO_IMAGE;
|
|
}
|
|
return ESPFS_INIT_RESULT_OK;
|
|
}
|
|
|
|
// Returns flags of opened file.
|
|
int espFsFlags(EspFsFile *fh)
|
|
{
|
|
if (fh == NULL) {
|
|
espfs_error("[EspFS] File handle not ready");
|
|
return -1;
|
|
}
|
|
|
|
int8_t flags;
|
|
httpdPlatEspfsRead(&flags, fh->headerPos + offsetof(EspFsHeader, flags), 1);
|
|
return (int) flags;
|
|
}
|
|
|
|
void espFsWalkInit(EspFsWalk *walk)
|
|
{
|
|
if (!walk) { return; }
|
|
walk->hpos = 0;
|
|
}
|
|
|
|
bool espFsWalkNext(EspFsWalk *walk, EspFsHeader *header, char *namebuf, size_t namebuf_cap, uint32_t *filepos)
|
|
{
|
|
int rv;
|
|
|
|
if (!header || !namebuf) {
|
|
espfs_error("[EspFS] espFsWalkNext NULL header or namebuf arg");
|
|
return false;
|
|
}
|
|
|
|
uint32_t p = walk->hpos;
|
|
|
|
//Grab the next file header.
|
|
rv = httpdPlatEspfsRead(header, p, sizeof(EspFsHeader));
|
|
if (rv != 0) {
|
|
return false;
|
|
}
|
|
|
|
if (header->magic != EFS_MAGIC) {
|
|
espfs_error("[EspFS] Magic mismatch. EspFS image broken.");
|
|
return false;
|
|
}
|
|
|
|
if (header->flags & EFS_FLAG_LASTFILE) {
|
|
espfs_dbg("[EspFS] End of image.");
|
|
return false;
|
|
}
|
|
|
|
if (header->nameLen > namebuf_cap) {
|
|
espfs_dbg("[EspFS] Name too long for buffer");
|
|
return false;
|
|
}
|
|
|
|
//Grab the name of the file.
|
|
p += sizeof(EspFsHeader);
|
|
httpdPlatEspfsRead(namebuf, p, header->nameLen);
|
|
namebuf[header->nameLen] = 0; // ensure it's terminated
|
|
|
|
if (filepos) {
|
|
*filepos = walk->hpos;
|
|
}
|
|
|
|
walk->hpos += (uint32_t) (sizeof(EspFsHeader) + header->nameLen + header->fileLenComp);
|
|
// Align
|
|
while(walk->hpos & 3) {
|
|
walk->hpos++;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
//Open a file and return a pointer to the file desc struct.
|
|
EspFsFile *espFsOpenFromHeader(EspFsHeader *h, uint32_t hpos)
|
|
{
|
|
int rv;
|
|
if (h->magic != EFS_MAGIC) {
|
|
espfs_error("[EspFS] Magic mismatch. EspFS image broken.");
|
|
return NULL;
|
|
}
|
|
if (h->flags & EFS_FLAG_LASTFILE) {
|
|
espfs_dbg("[EspFS] End of image.");
|
|
return NULL;
|
|
}
|
|
|
|
EspFsFile *r = (EspFsFile *) httpdPlatMalloc(sizeof(EspFsFile)); //Alloc file desc mem
|
|
if (r == NULL) { return NULL; }
|
|
r->headerPos = hpos;
|
|
r->decompressor = h->compression;
|
|
|
|
hpos += sizeof(EspFsHeader);
|
|
hpos += h->nameLen; // Skip to content
|
|
r->posComp = hpos;
|
|
r->posStart = hpos;
|
|
r->posDecomp = 0;
|
|
|
|
espfs_dbg("[EspFS] Found file @ hpos %d", hpos);
|
|
|
|
if (h->compression == EFS_COMPRESS_NONE) {
|
|
r->decompData = NULL;
|
|
return r;
|
|
} else if (h->compression == EFS_COMPRESS_HEATSHRINK) {
|
|
//File is compressed with Heatshrink.
|
|
char parm;
|
|
//Decoder params are stored in 1st byte.
|
|
rv = httpdPlatEspfsRead(&parm, r->posComp, 1);
|
|
if (rv != 0) {
|
|
return NULL;
|
|
}
|
|
r->posComp++;
|
|
espfs_dbg("[EspFS] Heatshrink compressed file; decode parms = %x", parm);
|
|
heatshrink_decoder *dec = heatshrink_decoder_alloc(16, (parm >> 4) & 0xf, parm & 0xf);
|
|
r->decompData = dec;
|
|
return r;
|
|
} else {
|
|
espfs_error("[EspFS] Invalid compression: %d", h->compression);
|
|
httpdPlatFree(r);
|
|
return NULL;
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
//Open a file and return a pointer to the file desc struct.
|
|
EspFsFile *espFsOpenAt(uint32_t hpos)
|
|
{
|
|
EspFsHeader h;
|
|
int rv = httpdPlatEspfsRead(&h, hpos, sizeof(EspFsHeader));
|
|
if (rv != 0) {
|
|
return NULL;
|
|
}
|
|
return espFsOpenFromHeader(&h, hpos);
|
|
}
|
|
|
|
int espFsFileReadHeader(const EspFsFile *file, EspFsHeader *header)
|
|
{
|
|
return httpdPlatEspfsRead(header, file->headerPos, sizeof(EspFsHeader));
|
|
}
|
|
|
|
//Open a file and return a pointer to the file desc struct.
|
|
EspFsFile *espFsOpen(const char *fileName)
|
|
{
|
|
int rv;
|
|
uint32_t p = 0;
|
|
uint32_t hpos;
|
|
char namebuf[256];
|
|
EspFsHeader h;
|
|
//Strip initial slashes
|
|
while (fileName[0] == '/') { fileName++; }
|
|
|
|
espfs_dbg("[EspFS] Open file: %s", fileName);
|
|
|
|
//Go find that file!
|
|
while (1) {
|
|
hpos = p;
|
|
//Grab the next file header.
|
|
rv = httpdPlatEspfsRead(&h, p, sizeof(EspFsHeader));
|
|
if (rv != 0) {
|
|
return NULL;
|
|
}
|
|
|
|
// Indians
|
|
h.magic = le32toh(h.magic);
|
|
h.nameLen = le16toh(h.nameLen);
|
|
h.fileLenComp = le32toh(h.fileLenComp);
|
|
h.fileLenDecomp = le32toh(h.fileLenDecomp);
|
|
|
|
if (h.magic != EFS_MAGIC) {
|
|
espfs_error("[EspFS] Magic mismatch. EspFS image broken.");
|
|
return NULL;
|
|
}
|
|
if (h.flags & EFS_FLAG_LASTFILE) {
|
|
espfs_dbg("[EspFS] End of image.");
|
|
return NULL;
|
|
}
|
|
|
|
//Grab the name of the file.
|
|
p += sizeof(EspFsHeader);
|
|
rv = httpdPlatEspfsRead(&namebuf, p, h.nameLen);
|
|
if (rv != 0) {
|
|
return NULL;
|
|
}
|
|
namebuf[h.nameLen] = 0; // ensure it's terminated
|
|
|
|
if (strcmp(namebuf, fileName) == 0) {
|
|
espfs_dbg("[EspFS] Found matching file: name %s, len %d", namebuf, h.fileLenDecomp);
|
|
|
|
//Yay, this is the file we need!
|
|
return espFsOpenFromHeader(&h, hpos);
|
|
}
|
|
//We don't need this file. Skip name and file
|
|
p += h.nameLen + h.fileLenComp;
|
|
while(p & 3) {
|
|
p++;
|
|
}
|
|
}
|
|
}
|
|
|
|
//Read len bytes from the given file into buff. Returns the actual amount of bytes read.
|
|
size_t espFsRead(EspFsFile *fh, uint8_t *buff, size_t buf_cap)
|
|
{
|
|
int rv = 0;
|
|
uint32_t binary_len = 0;
|
|
uint32_t decompressed_len = 0;
|
|
if (fh == NULL) { return 0; }
|
|
|
|
espfs_dbg("[EspFS] File read, pos @%d: cap %d", (int)fh->posComp, (int) buf_cap);
|
|
|
|
rv = httpdPlatEspfsRead(&binary_len, fh->headerPos + offsetof(EspFsHeader, fileLenComp), 4);
|
|
if (rv != 0) {
|
|
return 0;
|
|
}
|
|
|
|
//Cache file length.
|
|
//Do stuff depending on the way the file is compressed.
|
|
if (fh->decompressor == EFS_COMPRESS_NONE) {
|
|
int toRead = (int) binary_len - (int) (fh->posComp - fh->posStart);
|
|
if (toRead < 0) { toRead = 0; }
|
|
if ((int) buf_cap > toRead) { buf_cap = (size_t) toRead; }
|
|
espfs_dbg("[EspFS] Plain data, read chunk @%d: %d", (int)fh->posComp, (int) buf_cap);
|
|
rv = httpdPlatEspfsRead(buff, fh->posComp, buf_cap);
|
|
if (rv != 0) {
|
|
return 0;
|
|
}
|
|
|
|
fh->posDecomp += buf_cap;
|
|
fh->posComp += buf_cap;
|
|
return buf_cap;
|
|
} else if (fh->decompressor == EFS_COMPRESS_HEATSHRINK) {
|
|
rv = httpdPlatEspfsRead(&decompressed_len, fh->headerPos + offsetof(EspFsHeader, fileLenDecomp), 4);
|
|
if (rv != 0) {
|
|
return 0;
|
|
}
|
|
|
|
size_t decoded_bytes = 0;
|
|
size_t remaining_binary_len, sunk_len, decoded_chunk_len;
|
|
#define HS_DECOMP_CHUNK_LEN 16
|
|
char ebuff[HS_DECOMP_CHUNK_LEN];
|
|
heatshrink_decoder *dec = fh->decompData;
|
|
|
|
// We must ensure that whole file is decompressed and written to output buffer.
|
|
// This means even when there is no input data (elen==0) try to poll decoder until
|
|
// posDecomp equals decompressed file length
|
|
|
|
while (decoded_bytes < buf_cap) {
|
|
//Feed data into the decompressor
|
|
//ToDo: Check ret val of heatshrink fns for errors
|
|
remaining_binary_len = binary_len - (fh->posComp - fh->posStart);
|
|
if (remaining_binary_len > 0) {
|
|
const size_t chunk = remaining_binary_len < HS_DECOMP_CHUNK_LEN ?
|
|
remaining_binary_len : HS_DECOMP_CHUNK_LEN;
|
|
|
|
espfs_dbg("[EspFS] HS data, read chunk @%d: %d", (int)fh->posComp, (int) chunk);
|
|
rv = httpdPlatEspfsRead(ebuff, fh->posComp, chunk);
|
|
if (rv != 0) {
|
|
return 0;
|
|
}
|
|
|
|
heatshrink_decoder_sink(dec, (uint8_t *) ebuff, chunk, &sunk_len);
|
|
espfs_dbg("[EspFS] HS sunk %d bytes", (int) sunk_len);
|
|
fh->posComp += sunk_len;
|
|
}
|
|
//Grab decompressed data and put into buff
|
|
decoded_chunk_len = 0;
|
|
heatshrink_decoder_poll(dec, buff, buf_cap - decoded_bytes, &decoded_chunk_len);
|
|
fh->posDecomp += decoded_chunk_len;
|
|
buff += decoded_chunk_len;
|
|
decoded_bytes += decoded_chunk_len;
|
|
|
|
espfs_dbg("[EspFS] HS pulled %d bytes, remaining binary len %d", (int) decoded_chunk_len, (int) remaining_binary_len);
|
|
if (remaining_binary_len == 0) {
|
|
if (fh->posDecomp == decompressed_len) {
|
|
heatshrink_decoder_finish(dec);
|
|
}
|
|
return decoded_bytes;
|
|
}
|
|
}
|
|
return buf_cap;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
//Close the file.
|
|
void espFsClose(EspFsFile *fh)
|
|
{
|
|
if (fh == NULL) { return; }
|
|
if (fh->decompressor == EFS_COMPRESS_HEATSHRINK) {
|
|
heatshrink_decoder *dec = (heatshrink_decoder *) fh->decompData;
|
|
heatshrink_decoder_free(dec);
|
|
}
|
|
httpdPlatFree(fh);
|
|
}
|
|
|