- Notifications
You must be signed in to change notification settings - Fork16
🗜️ An Arduino library to handle tar, gz, and tar.gz files on ESP32, ESP8266 and RP2040, with support for gzip/deflate compression.
License
tobozo/ESP32-targz
Folders and files
Name | Name | Last commit message | Last commit date | |
---|---|---|---|---|
Repository files navigation
ESP32-targz enables the channeling of gz ⬅️➡️ tar ⬅️➡️ filesystem data in both directions.
Parental advisory: this project was made under the influence of hyperfocus and its code may contain comments that are unfit for children.
- Compressing to
.tar.gz
- Decompressing from
tar.gz
- Compressing to
gz
- Decompressing from
gz
- Packing files/folders to
tar
- Unpacking
tar
- Supports any fs::FS filesystem (SD, SD_MMC, FFat, LittleFS) and Stream (HTTP, HTTPS, UDP, CAN, Ethernet)
- This is experimental, expect bugs!
- Contributions and feedback are more than welcome :-)
When decompressing to the filesystem (e.g. NOT when streaming to TAR), gzip can work without the dictionary.Disabling the dictionary can cause huge slowdowns but saves ~36KB of ram.
TinyUntar requires 512bytes only so its memory footprint is negligible.
- ESP32-targz decompression can only have oneoutput filesystem (seeSupport Matrix), and it must be set at compilation time (seeUsage).This limitation does not apply to theinput filesystem/stream.
fs::FS | SPIFFS | LittleFS | SD | SD_MMC | FFAT |
---|---|---|---|---|---|
ESP32 | 1.0 | 3.1.0 | 1.0.5 | 1.0 | 1.0 |
ESP8266 | builtin | 0.1.0 | 0.1.0 | n/a | n/a |
RP2040 | n/a | 0.1.0 | 2.0.0 | n/a | n/a |
#define
before including<ESP32-targz.h>
will alias a default flash filesystem totarGzFS
.
// Set **destination** filesystem by uncommenting one of these://#define DEST_FS_USES_SPIFFS//#define DEST_FS_USES_FFAT//#define DEST_FS_USES_SD//#define DEST_FS_USES_SD_MMC#defineDEST_FS_USES_LITTLEFS#include<ESP32-targz.h>// filesystem object will be available as "tarGzFS"
// mount spiffs (or any other filesystem)tarGzFS.begin();GzUnpacker*GZUnpacker=newGzUnpacker();GZUnpacker->haltOnError( true );// stop on fail (manual restart/reset required)GZUnpacker->setupFSCallbacks(targzTotalBytesFn,targzFreeBytesFn );// prevent the partition from exploding, recommendedGZUnpacker->setGzProgressCallback(BaseUnpacker::defaultProgressCallback );// targzNullProgressCallback or defaultProgressCallbackGZUnpacker->setLoggerCallback(BaseUnpacker::targzPrintLoggerCallback );// gz log verbosity// expand one fileif( !GZUnpacker->gzExpander(tarGzFS,"/gz_example.gz",tarGzFS,"/gz_example.jpg") ) {Serial.printf("gzExpander failed with return code #%d",GZUnpacker->tarGzGetError() ); }// expand another fileif( !gzExpander(tarGzFS,"/blah.gz",tarGzFS,"/blah.jpg") ) {Serial.printf("operation failed with return code #%d",GZUnpacker->tarGzGetError() ); }
// mount spiffs (or any other filesystem)tarGzFS.begin();TarUnpacker*TARUnpacker=newTarUnpacker();TARUnpacker->haltOnError( true );// stop on fail (manual restart/reset required)TARUnpacker->setTarVerify( true );// true = enables health checks but slows down the overall processTARUnpacker->setupFSCallbacks(targzTotalBytesFn,targzFreeBytesFn );// prevent the partition from exploding, recommendedTARUnpacker->setTarProgressCallback(BaseUnpacker::defaultProgressCallback );// prints the untarring progress for each individual fileTARUnpacker->setTarStatusProgressCallback(BaseUnpacker::defaultTarStatusProgressCallback );// print the filenames as they're expandedTARUnpacker->setTarMessageCallback(BaseUnpacker::targzPrintLoggerCallback );// tar log verbosityif( !TARUnpacker->tarExpander(tarGzFS,"/tar_example.tar",tarGzFS,"/") ) {Serial.printf("tarExpander failed with return code #%d\n",TARUnpacker->tarGzGetError() ); }
// mount spiffs (or any other filesystem)tarGzFS.begin();TarGzUnpacker*TARGZUnpacker=newTarGzUnpacker();TARGZUnpacker->haltOnError( true );// stop on fail (manual restart/reset required)TARGZUnpacker->setTarVerify( true );// true = enables health checks but slows down the overall processTARGZUnpacker->setupFSCallbacks(targzTotalBytesFn,targzFreeBytesFn );// prevent the partition from exploding, recommendedTARGZUnpacker->setGzProgressCallback(BaseUnpacker::defaultProgressCallback );// targzNullProgressCallback or defaultProgressCallbackTARGZUnpacker->setLoggerCallback(BaseUnpacker::targzPrintLoggerCallback );// gz log verbosityTARGZUnpacker->setTarProgressCallback(BaseUnpacker::defaultProgressCallback );// prints the untarring progress for each individual fileTARGZUnpacker->setTarStatusProgressCallback(BaseUnpacker::defaultTarStatusProgressCallback );// print the filenames as they're expandedTARGZUnpacker->setTarMessageCallback(BaseUnpacker::targzPrintLoggerCallback );// tar log verbosity// using an intermediate file (default is /tmp/tmp.tar)if( !TARGZUnpacker->tarGzExpander(tarGzFS,"/targz_example.tar.gz",tarGzFS,"/tmp") ) {Serial.printf("tarGzExpander+intermediate file failed with return code #%d\n",TARGZUnpacker->tarGzGetError() ); }// or without intermediate fileif( !TARGZUnpacker->tarGzExpander(tarGzFS,"/targz_example.tar.gz",tarGzFS,"/tmp",nullptr ) ) {Serial.printf("tarGzExpander+intermediate file failed with return code #%d\n",TARGZUnpacker->tarGzGetError() ); }
// mount spiffs (or any other filesystem)tarGzFS.begin();GzUnpacker*GZUnpacker=newGzUnpacker();GZUnpacker->haltOnError( true );// stop on fail (manual restart/reset required)GZUnpacker->setupFSCallbacks(targzTotalBytesFn,targzFreeBytesFn );// prevent the partition from exploding, recommendedGZUnpacker->setGzProgressCallback(BaseUnpacker::defaultProgressCallback );// targzNullProgressCallback or defaultProgressCallbackGZUnpacker->setLoggerCallback(BaseUnpacker::targzPrintLoggerCallback );// gz log verbosityif( !GZUnpacker->gzUpdater(tarGzFS,firmwareFile,U_FLASH,/*don't restart after update*/false ) ) {Serial.printf("gzUpdater failed with return code #%d\n",GZUnpacker->tarGzGetError() ); }
// mount spiffs (or any other filesystem)tarGzFS.begin();fs::Filefile=tarGzFS.open("/example_firmware.gz","r" );if (!file) {Serial.println("Can't open file");return; }GzUnpacker*GZUnpacker=newGzUnpacker();GZUnpacker->haltOnError( true );// stop on fail (manual restart/reset required)GZUnpacker->setupFSCallbacks(targzTotalBytesFn,targzFreeBytesFn );// prevent the partition from exploding, recommendedGZUnpacker->setGzProgressCallback(BaseUnpacker::defaultProgressCallback );// targzNullProgressCallback or defaultProgressCallbackGZUnpacker->setLoggerCallback(BaseUnpacker::targzPrintLoggerCallback );// gz log verbosityif( !GZUnpacker->gzStreamUpdater( (Stream*)&file,UPDATE_SIZE_UNKNOWN ) ) {Serial.printf("gzStreamUpdater failed with return code #%d\n",GZUnpacker->tarGzGetError() ); }
// mount spiffs (or any other filesystem)tarGzFS.begin();fs::Filefile=tarGzFS.open("/example_archive.tgz","r" );if (!file) {Serial.println("Can't open file");return; }TarGzUnpacker*TARGZUnpacker=newTarGzUnpacker();TARGZUnpacker->haltOnError( true );// stop on fail (manual restart/reset required)TARGZUnpacker->setTarVerify( true );// true = enables health checks but slows down the overall processTARGZUnpacker->setupFSCallbacks(targzTotalBytesFn,targzFreeBytesFn );// prevent the partition from exploding, recommendedTARGZUnpacker->setGzProgressCallback(BaseUnpacker::defaultProgressCallback );// targzNullProgressCallback or defaultProgressCallbackTARGZUnpacker->setLoggerCallback(BaseUnpacker::targzPrintLoggerCallback );// gz log verbosityTARGZUnpacker->setTarProgressCallback(BaseUnpacker::defaultProgressCallback );// prints the untarring progress for each individual fileTARGZUnpacker->setTarStatusProgressCallback(BaseUnpacker::defaultTarStatusProgressCallback );// print the filenames as they're expandedTARGZUnpacker->setTarMessageCallback(BaseUnpacker::targzPrintLoggerCallback );// tar log verbosityif( !TARGZUnpacker->tarGzStreamExpander( (Stream*)&file,tarGzFS ) ) {Serial.printf("tarGzStreamExpander failed with return code #%d\n",TARGZUnpacker->tarGzGetError() ); }
TarGzUnpacker*TARGZUnpacker=newTarGzUnpacker();TARGZUnpacker->haltOnError( true );// stop on fail (manual restart/reset required)TARGZUnpacker->setTarVerify( false );// nothing to verify as we're writing a partitionTARGZUnpacker->setGzProgressCallback(BaseUnpacker::targzNullProgressCallback );// don't care about gz progressTARGZUnpacker->setTarProgressCallback(BaseUnpacker::defaultProgressCallback );// prints the untarring progress for each individual partitionTARGZUnpacker->setTarStatusProgressCallback(BaseUnpacker::defaultTarStatusProgressCallback );// print the filenames as they're expandedTARGZUnpacker->setTarMessageCallback(myTarMessageCallback/*BaseUnpacker::targzPrintLoggerCallback*/ );// tar log verbosity// mount SDSD.begin();// this .tar.gz file has both the "app.ino.bin" and "app.spiffs.bin" partitionsfs::Filefile=SD.open("/bundle_firmware.tar.gz","r" );if (!file) {Serial.println("Can't open file");return; }// this could also be a HTTP/HTTPS/UDP/Ethernet StreamStream*streamptr=&file;if( !TARGZUnpacker->tarGzStreamUpdater(streamptr ) ) {Serial.printf("tarGzStreamUpdater failed with return code #%d\n",TARGZUnpacker->tarGzGetError() ); }else {Serial.println("Flashing successful, now restarting" );ESP.restart(); }
// buffer to stream (best compression)size_tcompress(uint8_t* srcBuf,size_t srcBufLen, Stream* dstStream );// buffer to buffer (best compression)size_tcompress(uint8_t* srcBuf,size_t srcBufLen,uint8_t** dstBufPtr );// stream to buffersize_tcompress( Stream* srcStream,size_t srcLen,uint8_t** dstBufPtr );// stream to streamsize_tcompress( Stream* srcStream,size_t srcLen, Stream* dstStream );// stream to filesize_tcompress( Stream* srcStream,size_t srcLen, fs::FS*dstFS,constchar* dstFilename );// file to filesize_tcompress( fs::FS *srcFS,constchar* srcFilename, fs::FS*dstFS,constchar* dstFilename );// file to streamsize_tcompress( fs::FS *srcFS,constchar* srcFilename, Stream* dstStream );
constchar*json="{\"hello\":\"world\"}";// input bufferFileout=LittleFS.open("/out.gz","w");// output streamsize_tcompressedSize=LZPacker::compress( (uint8_t*)json,strlen(json),&out );out.close();
constchar*json="{\"hello\":\"world\"}";// input bufferuint8_t*compressedBytes;// output buffersize_tcompressedSize=LZPacker::compress( (uint8_t*)json,strlen(json),&compressedBytes);// do something with compressedBytesfree(compressedBytes);
Filein=LittleFS.open("/my.uncompressed.file.txt");// input streamuint8_t*compressedBytes;// output buffersize_tcompressedSize=LZPacker::compress(&in,in.size(),&compressedBytes );// do something with compressedBytesfree(compressedBytes);in.close();
Filein=LittleFS.open("/my.uncompressed.file.txt");// input streamFileout=LittleFS.open("/out.gz","w");// output streamsize_tcompressedSize=LZPacker::compress(&in,in.size(),&out );out.close();in.close();
intpack_files(fs::FS *srcFS, std::vector<dir_entity_t> dirEntities, Stream* dstStream,constchar* tar_prefix=nullptr);intpack_files(fs::FS *srcFS, std::vector<dir_entity_t> dirEntities, fs::FS *dstFS,constchar*tar_output_file_path,constchar* tar_prefix=nullptr);
std::vector<TAR::dir_entity_t>dirEntities;TarPacker::collectDirEntities(&dirEntities,&LittleFS,"/folder/to/pack"); autopackedSize=TarPacker::pack_files(&LittleFS,dirEntities,&LittleFS, "/my.archive.tar");
std::vector<TAR::dir_entity_t>dirEntities;TarPacker::collectDirEntities(&dirEntities,&LittleFS,"/folder/to/pack");FiletarOutfile=LittleFS.open("/my.archive.tar","w");size_tpackedSize=TarPacker::pack_files(&LittleFS,dirEntities,&tarOutfile);tarOutfile.close();
intcompress(fs::FS *srcFS,constchar* srcDir, Stream* dstStream,constchar* tar_prefix=nullptr);intcompress(fs::FS *srcFS,constchar* srcDir, fs::FS *dstFS,constchar* tgz_name,constchar* tar_prefix=nullptr);intcompress(fs::FS *srcFS, std::vector<dir_entity_t> dirEntities, Stream* dstStream,constchar* tar_prefix=nullptr);intcompress(fs::FS *srcFS, std::vector<dir_entity_t> dirEntities, fs::FS *dstFS,constchar* tgz_name,constchar* tar_prefix=nullptr);
Pack & compress to.tar.gz
file/stream (no filtering on source files/folders list, recursion applies)
FileTarGzOutFile=LittleFS.open("/my.archive.tar.gz","w");size_tcompressedSize=TarGzPacker::compress(&LittleFS/*source*/,"/folder/to/compress",&TarGzOutFile);TarGzOutFile.close();
std::vector<TAR::dir_entity_t>dirEntities;TarPacker::collectDirEntities(&dirEntities,&LittleFS/*source*/,"/folder/to/compress");// eventually filter content from dirEntitiesFileTarGzOutFile=LittleFS.open("/my.archive.tar.gz","w");size_tcompressedSize=TarGzPacker::compress(&LittleFS/*source*/,dirEntities,&TarGzOutFile);TarGzOutFile.close();
FileTarGzOutFile=LittleFS.open("/my.archive.tar.gz","w");size_tcompressedSize=TarGzPacker::compress(&LittleFS/*source*/,"/folder/to/compress",&LittleFS/*destination*/,"/my.archive.tar.gz");TarGzOutFile.close();
std::vector<TAR::dir_entity_t>dirEntities;TarPacker::collectDirEntities(&dirEntities,&LittleFS/*source*/,"/folder/to/compress");// eventually filter content from dirEntitiesFileTarGzOutFile=LittleFS.open("/my.archive.tar.gz","w");size_tcompressedSize=TarGzPacker::compress(&LittleFS/*source*/,dirEntities,&LittleFS/*destination*/,"/my.archive.tar.gz");TarGzOutFile.close();
// basic progress callback (valid for tar or gzip)voidmyBasicProgressCallback(uint8_tprogress ) {Serial.printf("Progress: %d\n",progress ); }// complex progress callback (valid for tar or gzip)voidmyProgressCallback(uint8_tprogress ) {staticint8_tmyLastProgress=-1;if(myLastProgress!=progress ) {if(myLastProgress==-1 ) {Serial.print("Progress: "); }myLastProgress=progress;switch(progress ) {case0:Serial.print("0% ▓");break;case25:Serial.print(" 25% ");break;case50:Serial.print(" 50% ");break;case75:Serial.print(" 75% ");break;case100:Serial.print("▓ 100%\n");myLastProgress=-1;break;default:if(progress<100)Serial.print("▓" );break; } } }// General Error/Warning/Info loggervoidmyLogger(constchar*format, ...) {va_listargs;va_start(args,format);vprintf(format,args);va_end(args); }// status callback for TAR (fired at file creation)voidmyTarStatusProgressCallback(constchar*name,size_tsize,size_ttotal_unpacked ) {Serial.printf("[TAR] %-64s %8d bytes - %8d Total bytes\n",name,size,total_unpacked ); }
*Unpacker->tarGzGetError()
returns a value when a problem occured:
General library error codes
0
: Yay no error!-1
: Filesystem error-6
: Same a Filesystem error-7
: Update not finished? Something went wrong-38
: Logic error during deflating-39
: Logic error during gzip read-40
: Logic error during file creation-100
: No space left on device-101
: No space left on device-102
: No space left on device-103
: Not enough heap-104
: Gzip dictionnary needs to be enabled-105
: Gz Error when parsing header-106
: Gz Error when allocating memory-107
: General error, file integrity check fail
UZLIB: forwarding error values from uzlib.h as is (no offset)
-2
: Not a valid gzip file-3
: Gz Error TINF_DATA_ERROR-4
: Gz Error TINF_CHKSUM_ERROR-5
: Gz Error TINF_DICT_ERROR-41
: Gz error, can't guess filename
UPDATE: applying -20 offset to forwarded error values from Update.h
-8
: Updater Error UPDATE_ERROR_ABORT-9
: Updater Error UPDATE_ERROR_BAD_ARGUMENT-10
: Updater Error UPDATE_ERROR_NO_PARTITION-11
: Updater Error UPDATE_ERROR_ACTIVATE-12
: Updater Error UPDATE_ERROR_MAGIC_BYTE-13
: Updater Error UPDATE_ERROR_MD5-14
: Updater Error UPDATE_ERROR_STREAM-15
: Updater Error UPDATE_ERROR_SIZE-16
: Updater Error UPDATE_ERROR_SPACE-17
: Updater Error UPDATE_ERROR_READ-18
: Updater Error UPDATE_ERROR_ERASE-19
: Updater Error UPDATE_ERROR_WRITE
TAR: applying -30 offset to forwarded error values from untar.h
32
: Tar Error TAR_ERR_DATACB_FAIL33
: Tar Error TAR_ERR_HEADERCB_FAIL34
: Tar Error TAR_ERR_FOOTERCB_FAIL35
: Tar Error TAR_ERR_READBLOCK_FAIL36
: Tar Error TAR_ERR_HEADERTRANS_FAIL37
: Tar Error TAR_ERR_HEADERPARSE_FAIL38
: Tar Error TAR_ERROR_HEAP
- SPIFFS is deprecated, migrate to LittleFS!
- tarGzExpander/tarExpander: symlinks or long filename/path not supported, path limit is 100 chars
- tarGzExpander without intermediate file uses a lot of heap
- tarGzExpander/gzExpander on ESP8266 : while the provided examples will work, the 32Kb dynamic allocation for gzip dictionary is unlikely to work in real world scenarios (e.g. with a webserver) and would probably require static allocation
- ESP32: use all of the "Debug level" values from the boards menu
- ESP8266: Warning/Error when "Debug Port:Serial" is used, and Debug/Verbose when "Debug Level:Core" is selected from the boards menu
- RP2040: only "Debug port: Serial" and "Debug Level: Core" enable logging
- ESP8266 Sketch Data Upload tool for LittleFS
- ESP32 Sketch Data Upload tool for FFat/LittleFS/SPIFFS
- Pico LittlsFS Data Upload tool
- https://github.com/vortigont/esp32-flashz OTA-Update your ESP32 from zlib compressed binaries (not gzip)
- https://github.com/chrisjoyce911/esp32FOTA OTA-Update your ESP32 from zlib or gzip compressed binaries
- https://github.com/laukik-hase/esp_compression inflate/deflate miniz/uzlib based esp-idf implementation
- pfalcon (uzlib maintainer)
- dsoprea (TinyUntar maintainer)
- lorol (LittleFS-ESP32 + fs plugin)
- me-no-dev (inspiration and support)
- atanisoft (motivation and support)
- lbernstone (motivation and support)
- scubachristopher (contribution and support)
- infrafast (feedback fueler)
- vortigont (inspiration and support)
- hitecSmartHome (feedback fueler)
About
🗜️ An Arduino library to handle tar, gz, and tar.gz files on ESP32, ESP8266 and RP2040, with support for gzip/deflate compression.