#include "tar_archive.h" #include #include #include #include #define TAG "TarArch" #define MAX_NAME_LEN 255 #define FILE_BLOCK_SIZE 512 #define FILE_OPEN_NTRIES 10 #define FILE_OPEN_RETRY_DELAY 25 typedef struct TarArchive { Storage* storage; mtar_t tar; tar_unpack_file_cb unpack_cb; void* unpack_cb_context; } TarArchive; /* API WRAPPER */ static int mtar_storage_file_write(void* stream, const void* data, unsigned size) { uint16_t bytes_written = storage_file_write(stream, data, size); return (bytes_written == size) ? bytes_written : MTAR_EWRITEFAIL; } static int mtar_storage_file_read(void* stream, void* data, unsigned size) { uint16_t bytes_read = storage_file_read(stream, data, size); return (bytes_read == size) ? bytes_read : MTAR_EREADFAIL; } static int mtar_storage_file_seek(void* stream, unsigned offset) { bool res = storage_file_seek(stream, offset, true); return res ? MTAR_ESUCCESS : MTAR_ESEEKFAIL; } static int mtar_storage_file_close(void* stream) { if(stream) { storage_file_close(stream); storage_file_free(stream); } return MTAR_ESUCCESS; } const struct mtar_ops filesystem_ops = { .read = mtar_storage_file_read, .write = mtar_storage_file_write, .seek = mtar_storage_file_seek, .close = mtar_storage_file_close, }; TarArchive* tar_archive_alloc(Storage* storage) { furi_check(storage); TarArchive* archive = malloc(sizeof(TarArchive)); archive->storage = storage; archive->unpack_cb = NULL; return archive; } bool tar_archive_open(TarArchive* archive, const char* path, TarOpenMode mode) { furi_check(archive); FS_AccessMode access_mode; FS_OpenMode open_mode; int mtar_access = 0; switch(mode) { case TAR_OPEN_MODE_READ: mtar_access = MTAR_READ; access_mode = FSAM_READ; open_mode = FSOM_OPEN_EXISTING; break; case TAR_OPEN_MODE_WRITE: mtar_access = MTAR_WRITE; access_mode = FSAM_WRITE; open_mode = FSOM_CREATE_ALWAYS; break; default: return false; } File* stream = storage_file_alloc(archive->storage); if(!storage_file_open(stream, path, access_mode, open_mode)) { storage_file_free(stream); return false; } mtar_init(&archive->tar, mtar_access, &filesystem_ops, stream); return true; } void tar_archive_free(TarArchive* archive) { furi_check(archive); if(mtar_is_open(&archive->tar)) { mtar_close(&archive->tar); } free(archive); } void tar_archive_set_file_callback(TarArchive* archive, tar_unpack_file_cb callback, void* context) { furi_check(archive); archive->unpack_cb = callback; archive->unpack_cb_context = context; } static int tar_archive_entry_counter(mtar_t* tar, const mtar_header_t* header, void* param) { UNUSED(tar); UNUSED(header); furi_assert(param); int32_t* counter = param; (*counter)++; return 0; } int32_t tar_archive_get_entries_count(TarArchive* archive) { furi_check(archive); int32_t counter = 0; if(mtar_foreach(&archive->tar, tar_archive_entry_counter, &counter) != MTAR_ESUCCESS) { counter = -1; } return counter; } bool tar_archive_dir_add_element(TarArchive* archive, const char* dirpath) { furi_check(archive); return (mtar_write_dir_header(&archive->tar, dirpath) == MTAR_ESUCCESS); } bool tar_archive_finalize(TarArchive* archive) { furi_check(archive); return (mtar_finalize(&archive->tar) == MTAR_ESUCCESS); } bool tar_archive_store_data( TarArchive* archive, const char* path, const uint8_t* data, const int32_t data_len) { furi_check(archive); return ( tar_archive_file_add_header(archive, path, data_len) && tar_archive_file_add_data_block(archive, data, data_len) && tar_archive_file_finalize(archive)); } bool tar_archive_file_add_header(TarArchive* archive, const char* path, const int32_t data_len) { furi_check(archive); return (mtar_write_file_header(&archive->tar, path, data_len) == MTAR_ESUCCESS); } bool tar_archive_file_add_data_block( TarArchive* archive, const uint8_t* data_block, const int32_t block_len) { furi_check(archive); return (mtar_write_data(&archive->tar, data_block, block_len) == block_len); } bool tar_archive_file_finalize(TarArchive* archive) { furi_check(archive); return (mtar_end_data(&archive->tar) == MTAR_ESUCCESS); } typedef struct { TarArchive* archive; const char* work_dir; Storage_name_converter converter; } TarArchiveDirectoryOpParams; static bool archive_extract_current_file(TarArchive* archive, const char* dst_path) { mtar_t* tar = &archive->tar; File* out_file = storage_file_alloc(archive->storage); uint8_t* readbuf = malloc(FILE_BLOCK_SIZE); bool success = true; uint8_t n_tries = FILE_OPEN_NTRIES; do { while(n_tries-- > 0) { if(storage_file_open(out_file, dst_path, FSAM_WRITE, FSOM_CREATE_ALWAYS)) { break; } FURI_LOG_W(TAG, "Failed to open '%s', reties: %d", dst_path, n_tries); storage_file_close(out_file); furi_delay_ms(FILE_OPEN_RETRY_DELAY); } if(!storage_file_is_open(out_file)) { success = false; break; } while(!mtar_eof_data(tar)) { int32_t readcnt = mtar_read_data(tar, readbuf, FILE_BLOCK_SIZE); if(!readcnt || !storage_file_write(out_file, readbuf, readcnt)) { success = false; break; } } } while(false); storage_file_free(out_file); free(readbuf); return success; } static int archive_extract_foreach_cb(mtar_t* tar, const mtar_header_t* header, void* param) { UNUSED(tar); TarArchiveDirectoryOpParams* op_params = param; TarArchive* archive = op_params->archive; bool skip_entry = false; if(archive->unpack_cb) { skip_entry = !archive->unpack_cb( header->name, header->type == MTAR_TDIR, archive->unpack_cb_context); } if(skip_entry) { FURI_LOG_W(TAG, "filter: skipping entry \"%s\"", header->name); return 0; } FuriString* full_extracted_fname; if(header->type == MTAR_TDIR) { full_extracted_fname = furi_string_alloc(); path_concat(op_params->work_dir, header->name, full_extracted_fname); bool create_res = storage_simply_mkdir(archive->storage, furi_string_get_cstr(full_extracted_fname)); furi_string_free(full_extracted_fname); return create_res ? 0 : -1; } if(header->type != MTAR_TREG) { FURI_LOG_W(TAG, "not extracting unsupported type \"%s\"", header->name); return 0; } FURI_LOG_D(TAG, "Extracting %u bytes to '%s'", header->size, header->name); FuriString* converted_fname = furi_string_alloc_set(header->name); if(op_params->converter) { op_params->converter(converted_fname); } full_extracted_fname = furi_string_alloc(); path_concat(op_params->work_dir, furi_string_get_cstr(converted_fname), full_extracted_fname); bool success = archive_extract_current_file(archive, furi_string_get_cstr(full_extracted_fname)); furi_string_free(converted_fname); furi_string_free(full_extracted_fname); return success ? 0 : -1; } bool tar_archive_unpack_to( TarArchive* archive, const char* destination, Storage_name_converter converter) { furi_check(archive); TarArchiveDirectoryOpParams param = { .archive = archive, .work_dir = destination, .converter = converter, }; FURI_LOG_I(TAG, "Restoring '%s'", destination); return (mtar_foreach(&archive->tar, archive_extract_foreach_cb, ¶m) == MTAR_ESUCCESS); }; bool tar_archive_add_file( TarArchive* archive, const char* fs_file_path, const char* archive_fname, const int32_t file_size) { furi_check(archive); uint8_t* file_buffer = malloc(FILE_BLOCK_SIZE); bool success = false; File* src_file = storage_file_alloc(archive->storage); uint8_t n_tries = FILE_OPEN_NTRIES; do { while(n_tries-- > 0) { if(storage_file_open(src_file, fs_file_path, FSAM_READ, FSOM_OPEN_EXISTING)) { break; } FURI_LOG_W(TAG, "Failed to open '%s', reties: %d", fs_file_path, n_tries); storage_file_close(src_file); furi_delay_ms(FILE_OPEN_RETRY_DELAY); } if(!storage_file_is_open(src_file) || !tar_archive_file_add_header(archive, archive_fname, file_size)) { break; } success = true; // if file is empty, that's not an error uint16_t bytes_read = 0; while((bytes_read = storage_file_read(src_file, file_buffer, FILE_BLOCK_SIZE))) { success = tar_archive_file_add_data_block(archive, file_buffer, bytes_read); if(!success) { break; } } success = success && tar_archive_file_finalize(archive); } while(false); storage_file_free(src_file); free(file_buffer); return success; } bool tar_archive_add_dir(TarArchive* archive, const char* fs_full_path, const char* path_prefix) { furi_check(archive); furi_check(path_prefix); File* directory = storage_file_alloc(archive->storage); FileInfo file_info; FURI_LOG_I(TAG, "Backing up '%s', '%s'", fs_full_path, path_prefix); char* name = malloc(MAX_NAME_LEN); bool success = false; do { if(!storage_dir_open(directory, fs_full_path)) { break; } while(true) { if(!storage_dir_read(directory, &file_info, name, MAX_NAME_LEN)) { success = true; /* empty dir / no more files */ break; } FuriString* element_name = furi_string_alloc(); FuriString* element_fs_abs_path = furi_string_alloc(); path_concat(fs_full_path, name, element_fs_abs_path); if(strlen(path_prefix)) { path_concat(path_prefix, name, element_name); } else { furi_string_set(element_name, name); } if(file_info_is_dir(&file_info)) { success = tar_archive_dir_add_element(archive, furi_string_get_cstr(element_name)) && tar_archive_add_dir( archive, furi_string_get_cstr(element_fs_abs_path), furi_string_get_cstr(element_name)); } else { success = tar_archive_add_file( archive, furi_string_get_cstr(element_fs_abs_path), furi_string_get_cstr(element_name), file_info.size); } furi_string_free(element_name); furi_string_free(element_fs_abs_path); if(!success) { break; } } } while(false); free(name); storage_file_free(directory); return success; } bool tar_archive_unpack_file( TarArchive* archive, const char* archive_fname, const char* destination) { furi_check(archive); furi_check(archive_fname); furi_check(destination); if(mtar_find(&archive->tar, archive_fname) != MTAR_ESUCCESS) { return false; } return archive_extract_current_file(archive, destination); }