SpriteHTTPD - embedded HTTP server with read-only filesystem and templating, originally developed for ESP8266, now stand-alone and POSIX compatible.
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.
 
 
spritehttpd/spritehttpd/lib/espfs/espfs.c

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);
}