|
|
|
/*
|
|
|
|
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.
|
|
|
|
* ----------------------------------------------------------------------------
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
|
|
//These routines can also be tested by comping them in with the espfstest tool. This
|
|
|
|
//simplifies debugging, but needs some slightly different headers. The #ifdef takes
|
|
|
|
//care of that.
|
|
|
|
|
|
|
|
#ifdef __ets__
|
|
|
|
//esp build
|
|
|
|
#include <esp8266.h>
|
|
|
|
#else
|
|
|
|
//Test build
|
|
|
|
#include <stdio.h>
|
|
|
|
#include <stdint.h>
|
|
|
|
#include <stdlib.h>
|
|
|
|
#include <string.h>
|
|
|
|
#define ICACHE_FLASH_ATTR
|
|
|
|
#endif
|
|
|
|
|
|
|
|
#include <esp8266.h>
|
|
|
|
#include "espfsformat.h"
|
|
|
|
#include "espfs.h"
|
|
|
|
|
|
|
|
#ifdef ESPFS_HEATSHRINK
|
|
|
|
#include "heatshrink_config_custom.h"
|
|
|
|
#include "heatshrink_decoder.h"
|
|
|
|
#endif
|
|
|
|
|
|
|
|
static const char* espFsData = NULL;
|
|
|
|
|
|
|
|
|
|
|
|
struct EspFsFile {
|
|
|
|
const EspFsHeader *header;
|
|
|
|
char decompressor;
|
|
|
|
int32_t posDecomp;
|
|
|
|
const char *posStart;
|
|
|
|
const char *posComp;
|
|
|
|
void *decompData;
|
|
|
|
};
|
|
|
|
|
|
|
|
/*
|
|
|
|
Available locations, at least in my flash, with boundaries partially guessed. This
|
|
|
|
is using 0.9.1/0.9.2 SDK on a not-too-new module.
|
|
|
|
0x00000 (0x10000): Code/data (RAM data?)
|
|
|
|
0x10000 (0x02000): Gets erased by something?
|
|
|
|
0x12000 (0x2E000): Free (filled with zeroes) (parts used by ESPCloud and maybe SSL)
|
|
|
|
0x40000 (0x20000): Code/data (ROM data?)
|
|
|
|
0x60000 (0x1C000): Free
|
|
|
|
0x7c000 (0x04000): Param store
|
|
|
|
0x80000 - end of flash
|
|
|
|
|
|
|
|
Accessing the flash through the mem emulation at 0x40200000 is a bit hairy: All accesses
|
|
|
|
*must* be aligned 32-bit accesses. Reading a short, byte or unaligned word will result in
|
|
|
|
a memory exception, crashing the program.
|
|
|
|
*/
|
|
|
|
|
|
|
|
EspFsInitResult ICACHE_FLASH_ATTR espFsInit(const void *flashAddress)
|
|
|
|
{
|
|
|
|
if ((uint32_t)flashAddress > 0x40200000) {
|
|
|
|
flashAddress = (void*)((uint32_t)flashAddress - 0x40200000);
|
|
|
|
}
|
|
|
|
|
|
|
|
// base address must be aligned to 4 bytes
|
|
|
|
if (((int)flashAddress & 3) != 0) {
|
|
|
|
return ESPFS_INIT_RESULT_BAD_ALIGN;
|
|
|
|
}
|
|
|
|
|
|
|
|
// check if there is valid header at address
|
|
|
|
EspFsHeader testHeader;
|
|
|
|
spi_flash_read((uint32)flashAddress, (uint32*)&testHeader, sizeof(EspFsHeader));
|
|
|
|
if (testHeader.magic != ESPFS_MAGIC) {
|
|
|
|
return ESPFS_INIT_RESULT_NO_IMAGE;
|
|
|
|
}
|
|
|
|
|
|
|
|
espFsData = (const char *)flashAddress;
|
|
|
|
return ESPFS_INIT_RESULT_OK;
|
|
|
|
}
|
|
|
|
|
|
|
|
//Copies len bytes over from dst to src, but does it using *only*
|
|
|
|
//aligned 32-bit reads. Yes, it's no too optimized but it's short and sweet and it works.
|
|
|
|
|
|
|
|
//ToDo: perhaps memcpy also does unaligned accesses?
|
|
|
|
#ifdef __ets__
|
|
|
|
void ICACHE_FLASH_ATTR readFlashUnaligned(char *dst, char *src, int len)
|
|
|
|
{
|
|
|
|
uint8_t src_offset = ((uint32_t)src) & 3;
|
|
|
|
uint32_t src_address = ((uint32_t)src) - src_offset;
|
|
|
|
|
|
|
|
uint32_t tmp_buf[len / 4 + 2];
|
|
|
|
spi_flash_read((uint32)src_address, (uint32*)tmp_buf, len + src_offset);
|
|
|
|
memcpy(dst, ((uint8_t*)tmp_buf) + src_offset, len);
|
|
|
|
}
|
|
|
|
#else
|
|
|
|
#define readFlashUnaligned memcpy
|
|
|
|
#endif
|
|
|
|
|
|
|
|
// Returns flags of opened file.
|
|
|
|
int ICACHE_FLASH_ATTR espFsFlags(EspFsFile *fh)
|
|
|
|
{
|
|
|
|
if (fh == NULL) {
|
|
|
|
error("[EspFS] File handle not ready");
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
int8_t flags;
|
|
|
|
readFlashUnaligned((char*)&flags, (char*)&fh->header->flags, 1);
|
|
|
|
return (int)flags;
|
|
|
|
}
|
|
|
|
|
|
|
|
//Open a file and return a pointer to the file desc struct.
|
|
|
|
EspFsFile ICACHE_FLASH_ATTR *espFsOpen(const char *fileName)
|
|
|
|
{
|
|
|
|
dbg("[EspFS] Open file %s", fileName);
|
|
|
|
|
|
|
|
if (espFsData == NULL) {
|
|
|
|
error("[EspFS] Call espFsInit first!");
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
const char *p = espFsData;
|
|
|
|
const char *hpos;
|
|
|
|
char namebuf[256];
|
|
|
|
EspFsHeader h;
|
|
|
|
EspFsFile *r;
|
|
|
|
//Strip initial slashes
|
|
|
|
while (fileName[0] == '/') fileName++;
|
|
|
|
//Go find that file!
|
|
|
|
while (1) {
|
|
|
|
hpos = p;
|
|
|
|
//Grab the next file header.
|
|
|
|
spi_flash_read((uint32)p, (uint32*)&h, sizeof(EspFsHeader));
|
|
|
|
|
|
|
|
if (h.magic != ESPFS_MAGIC) {
|
|
|
|
error("[EspFS] Magic mismatch. EspFS image broken.");
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
if (h.flags & FLAG_LASTFILE) {
|
|
|
|
warn("[EspFS] File %s not found in EspFS.", fileName);
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
//Grab the name of the file.
|
|
|
|
p += sizeof(EspFsHeader);
|
|
|
|
spi_flash_read((uint32)p, (uint32*)&namebuf, sizeof(namebuf));
|
|
|
|
// httpd_printf("Found file '%s'. Namelen=%x fileLenComp=%x, compr=%d flags=%d\n",
|
|
|
|
// namebuf, (unsigned int)h.nameLen, (unsigned int)h.fileLenComp, h.compression, h.flags);
|
|
|
|
if (strcmp(namebuf, fileName) == 0) {
|
|
|
|
//Yay, this is the file we need!
|
|
|
|
p += h.nameLen; //Skip to content.
|
|
|
|
r = (EspFsFile *)malloc(sizeof(EspFsFile)); //Alloc file desc mem
|
|
|
|
// httpd_printf("Alloc %p\n", r);
|
|
|
|
if (r == NULL) return NULL;
|
|
|
|
r->header = (const EspFsHeader *)hpos;
|
|
|
|
r->decompressor = h.compression;
|
|
|
|
r->posComp = p;
|
|
|
|
r->posStart = p;
|
|
|
|
r->posDecomp = 0;
|
|
|
|
if (h.compression == COMPRESS_NONE) {
|
|
|
|
info("[EspFS] File found.");
|
|
|
|
r->decompData = NULL;
|
|
|
|
#ifdef ESPFS_HEATSHRINK
|
|
|
|
} else if (h.compression == COMPRESS_HEATSHRINK) {
|
|
|
|
//File is compressed with Heatshrink.
|
|
|
|
char parm;
|
|
|
|
heatshrink_decoder *dec;
|
|
|
|
//Decoder params are stored in 1st byte.
|
|
|
|
readFlashUnaligned(&parm, (char*)r->posComp, 1);
|
|
|
|
r->posComp++;
|
|
|
|
info("[EspFS] Heatshrink compressed file found; decode params = %x", parm);
|
|
|
|
dec = heatshrink_decoder_alloc(16, (parm >> 4) & 0xf, parm & 0xf);
|
|
|
|
r->decompData = dec;
|
|
|
|
#endif
|
|
|
|
} else {
|
|
|
|
error("[EspFS] File found, but has invalid compression: %d", h.compression);
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
return r;
|
|
|
|
}
|
|
|
|
//We don't need this file. Skip name and file
|
|
|
|
p += h.nameLen + h.fileLenComp;
|
|
|
|
if ((int)p & 3) p += 4 - ((int)p & 3); //align to next 32bit val
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
//Read len bytes from the given file into buff. Returns the actual amount of bytes read.
|
|
|
|
int ICACHE_FLASH_ATTR espFsRead(EspFsFile *fh, char *buff, int len)
|
|
|
|
{
|
|
|
|
int flen, fdlen;
|
|
|
|
if (fh == NULL) return 0;
|
|
|
|
|
|
|
|
readFlashUnaligned((char*)&flen, (char*)&fh->header->fileLenComp, 4);
|
|
|
|
//Cache file length.
|
|
|
|
//Do stuff depending on the way the file is compressed.
|
|
|
|
if (fh->decompressor == COMPRESS_NONE) {
|
|
|
|
int toRead;
|
|
|
|
toRead = flen - (fh->posComp - fh->posStart);
|
|
|
|
if (len > toRead) len = toRead;
|
|
|
|
// httpd_printf("Reading %d bytes from %x\n", len, (unsigned int)fh->posComp);
|
|
|
|
readFlashUnaligned(buff, (char*)fh->posComp, len);
|
|
|
|
fh->posDecomp += len;
|
|
|
|
fh->posComp += len;
|
|
|
|
// httpd_printf("Done reading %d bytes, pos=%x\n", len, fh->posComp);
|
|
|
|
return len;
|
|
|
|
#ifdef ESPFS_HEATSHRINK
|
|
|
|
} else if (fh->decompressor == COMPRESS_HEATSHRINK) {
|
|
|
|
readFlashUnaligned((char*)&fdlen, (char*)&fh->header->fileLenDecomp, 4);
|
|
|
|
int decoded = 0;
|
|
|
|
size_t elen, rlen;
|
|
|
|
char ebuff[16];
|
|
|
|
heatshrink_decoder *dec = (heatshrink_decoder *)fh->decompData;
|
|
|
|
// httpd_printf("Alloc %p\n", dec);
|
|
|
|
if (fh->posDecomp == fdlen) {
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
// 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 < len) {
|
|
|
|
//Feed data into the decompressor
|
|
|
|
//ToDo: Check ret val of heatshrink fns for errors
|
|
|
|
elen = flen - (fh->posComp - fh->posStart);
|
|
|
|
if (elen > 0) {
|
|
|
|
readFlashUnaligned(ebuff, (char*)fh->posComp, 16);
|
|
|
|
heatshrink_decoder_sink(dec, (uint8_t *)ebuff, (elen > 16) ? 16 : elen, &rlen);
|
|
|
|
fh->posComp += rlen;
|
|
|
|
}
|
|
|
|
//Grab decompressed data and put into buff
|
|
|
|
heatshrink_decoder_poll(dec, (uint8_t *)buff, len - decoded, &rlen);
|
|
|
|
fh->posDecomp += rlen;
|
|
|
|
buff += rlen;
|
|
|
|
decoded += rlen;
|
|
|
|
|
|
|
|
// httpd_printf("Elen %d rlen %d d %d pd %ld fdl %d\n",elen,rlen,decoded, fh->posDecomp, fdlen);
|
|
|
|
|
|
|
|
if (elen == 0) {
|
|
|
|
if (fh->posDecomp == fdlen) {
|
|
|
|
// httpd_printf("Decoder finish\n");
|
|
|
|
heatshrink_decoder_finish(dec);
|
|
|
|
}
|
|
|
|
return decoded;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return len;
|
|
|
|
#endif
|
|
|
|
}
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
//Close the file.
|
|
|
|
void ICACHE_FLASH_ATTR espFsClose(EspFsFile *fh)
|
|
|
|
{
|
|
|
|
if (fh == NULL) return;
|
|
|
|
#ifdef ESPFS_HEATSHRINK
|
|
|
|
if (fh->decompressor == COMPRESS_HEATSHRINK) {
|
|
|
|
heatshrink_decoder *dec = (heatshrink_decoder *)fh->decompData;
|
|
|
|
heatshrink_decoder_free(dec);
|
|
|
|
// httpd_printf("Freed %p\n", dec);
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
// httpd_printf("Freed %p\n", fh);
|
|
|
|
free(fh);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|