[FL-2831] Resources cleanup in updater (#1796)
* updater: remove files from existing resources Manifest file before deploying new resources * toolbox: tar: single file extraction API Co-authored-by: あく <alleteam@gmail.com>
This commit is contained in:
		
							parent
							
								
									e25b424188
								
							
						
					
					
						commit
						f8b532f063
					
				| @ -10,7 +10,7 @@ extern "C" { | ||||
| #include <stdbool.h> | ||||
| #include <m-string.h> | ||||
| 
 | ||||
| #define UPDATE_DELAY_OPERATION_OK 300 | ||||
| #define UPDATE_DELAY_OPERATION_OK 10 | ||||
| #define UPDATE_DELAY_OPERATION_ERROR INT_MAX | ||||
| 
 | ||||
| typedef enum { | ||||
|  | ||||
| @ -9,6 +9,7 @@ | ||||
| #include <update_util/dfu_file.h> | ||||
| #include <update_util/lfs_backup.h> | ||||
| #include <update_util/update_operation.h> | ||||
| #include <update_util/resources/manifest.h> | ||||
| #include <toolbox/tar/tar_archive.h> | ||||
| #include <toolbox/crc32_calc.h> | ||||
| 
 | ||||
| @ -50,10 +51,46 @@ static bool update_task_resource_unpack_cb(const char* name, bool is_directory, | ||||
|     update_task_set_progress( | ||||
|         unpack_progress->update_task, | ||||
|         UpdateTaskStageProgress, | ||||
|         unpack_progress->processed_files * 100 / (unpack_progress->total_files + 1)); | ||||
|         /* For this stage, last 70% of progress = extraction */ | ||||
|         30 + (unpack_progress->processed_files * 70) / (unpack_progress->total_files + 1)); | ||||
|     return true; | ||||
| } | ||||
| 
 | ||||
| static void | ||||
|     update_task_cleanup_resources(UpdateTask* update_task, uint32_t n_approx_file_entries) { | ||||
|     ResourceManifestReader* manifest_reader = resource_manifest_reader_alloc(update_task->storage); | ||||
|     do { | ||||
|         FURI_LOG_I(TAG, "Cleaning up old manifest"); | ||||
|         if(!resource_manifest_reader_open(manifest_reader, EXT_PATH("Manifest"))) { | ||||
|             FURI_LOG_W(TAG, "No existing manifest"); | ||||
|             break; | ||||
|         } | ||||
| 
 | ||||
|         /* We got # of entries in TAR file. Approx 1/4th is dir entries, we skip them */ | ||||
|         n_approx_file_entries = n_approx_file_entries * 3 / 4 + 1; | ||||
|         uint32_t n_processed_files = 0; | ||||
| 
 | ||||
|         ResourceManifestEntry* entry_ptr = NULL; | ||||
|         while((entry_ptr = resource_manifest_reader_next(manifest_reader))) { | ||||
|             if(entry_ptr->type == ResourceManifestEntryTypeFile) { | ||||
|                 update_task_set_progress( | ||||
|                     update_task, | ||||
|                     UpdateTaskStageProgress, | ||||
|                     /* For this stage, first 30% of progress = cleanup */ | ||||
|                     (n_processed_files++ * 30) / (n_approx_file_entries + 1)); | ||||
| 
 | ||||
|                 string_t file_path; | ||||
|                 string_init(file_path); | ||||
|                 path_concat(STORAGE_EXT_PATH_PREFIX, string_get_cstr(entry_ptr->name), file_path); | ||||
|                 FURI_LOG_D(TAG, "Removing %s", string_get_cstr(file_path)); | ||||
|                 storage_simply_remove(update_task->storage, string_get_cstr(file_path)); | ||||
|                 string_clear(file_path); | ||||
|             } | ||||
|         } | ||||
|     } while(false); | ||||
|     resource_manifest_reader_free(manifest_reader); | ||||
| } | ||||
| 
 | ||||
| static bool update_task_post_update(UpdateTask* update_task) { | ||||
|     bool success = false; | ||||
| 
 | ||||
| @ -88,6 +125,8 @@ static bool update_task_post_update(UpdateTask* update_task) { | ||||
| 
 | ||||
|             progress.total_files = tar_archive_get_entries_count(archive); | ||||
|             if(progress.total_files > 0) { | ||||
|                 update_task_cleanup_resources(update_task, progress.total_files); | ||||
| 
 | ||||
|                 CHECK_RESULT(tar_archive_unpack_to(archive, STORAGE_EXT_PATH_PREFIX, NULL)); | ||||
|             } | ||||
|         } | ||||
|  | ||||
| @ -308,7 +308,7 @@ bool update_task_validate_optionbytes(UpdateTask* update_task) { | ||||
|                 } | ||||
|             } | ||||
|         } else { | ||||
|             FURI_LOG_I( | ||||
|             FURI_LOG_D( | ||||
|                 TAG, | ||||
|                 "OB MATCH: #%d: real %08X == %08X (exp.)", | ||||
|                 idx, | ||||
|  | ||||
| @ -2269,6 +2269,7 @@ Function,+,tar_archive_get_entries_count,int32_t,TarArchive* | ||||
| Function,+,tar_archive_open,_Bool,"TarArchive*, const char*, TarOpenMode" | ||||
| Function,+,tar_archive_set_file_callback,void,"TarArchive*, tar_unpack_file_cb, void*" | ||||
| Function,+,tar_archive_store_data,_Bool,"TarArchive*, const char*, const uint8_t*, const int32_t" | ||||
| Function,+,tar_archive_unpack_file,_Bool,"TarArchive*, const char*, const char*" | ||||
| Function,+,tar_archive_unpack_to,_Bool,"TarArchive*, const char*, Storage_name_converter" | ||||
| Function,-,tempnam,char*,"const char*, const char*" | ||||
| Function,+,text_box_alloc,TextBox*, | ||||
|  | ||||
| 
 | 
| @ -6,6 +6,9 @@ void furi_hal_cortex_init_early() { | ||||
|     CoreDebug->DEMCR |= CoreDebug_DEMCR_TRCENA_Msk; | ||||
|     DWT->CTRL |= DWT_CTRL_CYCCNTENA_Msk; | ||||
|     DWT->CYCCNT = 0U; | ||||
| 
 | ||||
|     /* Enable instruction prefetch */ | ||||
|     SET_BIT(FLASH->ACR, FLASH_ACR_PRFTEN); | ||||
| } | ||||
| 
 | ||||
| void furi_hal_cortex_delay_us(uint32_t microseconds) { | ||||
|  | ||||
| @ -168,7 +168,44 @@ typedef struct { | ||||
|     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; | ||||
| 
 | ||||
| @ -199,58 +236,22 @@ static int archive_extract_foreach_cb(mtar_t* tar, const mtar_header_t* header, | ||||
|         return 0; | ||||
|     } | ||||
| 
 | ||||
|     string_init(full_extracted_fname); | ||||
|     FURI_LOG_D(TAG, "Extracting %d bytes to '%s'", header->size, header->name); | ||||
| 
 | ||||
|     string_t converted_fname; | ||||
|     string_init_set(converted_fname, header->name); | ||||
|     if(op_params->converter) { | ||||
|         op_params->converter(converted_fname); | ||||
|     } | ||||
| 
 | ||||
|     string_init(full_extracted_fname); | ||||
|     path_concat(op_params->work_dir, string_get_cstr(converted_fname), full_extracted_fname); | ||||
| 
 | ||||
|     bool success = archive_extract_current_file(archive, string_get_cstr(full_extracted_fname)); | ||||
| 
 | ||||
|     string_clear(converted_fname); | ||||
| 
 | ||||
|     FURI_LOG_D(TAG, "Extracting %d bytes to '%s'", header->size, header->name); | ||||
|     File* out_file = storage_file_alloc(archive->storage); | ||||
|     uint8_t* readbuf = malloc(FILE_BLOCK_SIZE); | ||||
| 
 | ||||
|     bool failed = false; | ||||
|     uint8_t n_tries = FILE_OPEN_NTRIES; | ||||
|     do { | ||||
|         while(n_tries-- > 0) { | ||||
|             if(storage_file_open( | ||||
|                    out_file, | ||||
|                    string_get_cstr(full_extracted_fname), | ||||
|                    FSAM_WRITE, | ||||
|                    FSOM_CREATE_ALWAYS)) { | ||||
|                 break; | ||||
|             } | ||||
|             FURI_LOG_W( | ||||
|                 TAG, | ||||
|                 "Failed to open '%s', reties: %d", | ||||
|                 string_get_cstr(full_extracted_fname), | ||||
|                 n_tries); | ||||
|             storage_file_close(out_file); | ||||
|             furi_delay_ms(FILE_OPEN_RETRY_DELAY); | ||||
|         } | ||||
| 
 | ||||
|         if(!storage_file_is_open(out_file)) { | ||||
|             failed = true; | ||||
|             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)) { | ||||
|                 failed = true; | ||||
|                 break; | ||||
|             } | ||||
|         } | ||||
|     } while(false); | ||||
| 
 | ||||
|     storage_file_free(out_file); | ||||
|     free(readbuf); | ||||
|     string_clear(full_extracted_fname); | ||||
|     return failed ? -1 : 0; | ||||
|     return success ? 0 : -1; | ||||
| } | ||||
| 
 | ||||
| bool tar_archive_unpack_to( | ||||
| @ -369,3 +370,16 @@ bool tar_archive_add_dir(TarArchive* archive, const char* fs_full_path, const ch | ||||
|     storage_file_free(directory); | ||||
|     return success; | ||||
| } | ||||
| 
 | ||||
| bool tar_archive_unpack_file( | ||||
|     TarArchive* archive, | ||||
|     const char* archive_fname, | ||||
|     const char* destination) { | ||||
|     furi_assert(archive); | ||||
|     furi_assert(archive_fname); | ||||
|     furi_assert(destination); | ||||
|     if(mtar_find(&archive->tar, archive_fname) != MTAR_ESUCCESS) { | ||||
|         return false; | ||||
|     } | ||||
|     return archive_extract_current_file(archive, destination); | ||||
| } | ||||
| @ -41,6 +41,11 @@ bool tar_archive_add_dir(TarArchive* archive, const char* fs_full_path, const ch | ||||
| 
 | ||||
| int32_t tar_archive_get_entries_count(TarArchive* archive); | ||||
| 
 | ||||
| bool tar_archive_unpack_file( | ||||
|     TarArchive* archive, | ||||
|     const char* archive_fname, | ||||
|     const char* destination); | ||||
| 
 | ||||
| /* Optional per-entry callback on unpacking - return false to skip entry */ | ||||
| typedef bool (*tar_unpack_file_cb)(const char* name, bool is_directory, void* context); | ||||
| 
 | ||||
|  | ||||
							
								
								
									
										115
									
								
								lib/update_util/resources/manifest.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										115
									
								
								lib/update_util/resources/manifest.c
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,115 @@ | ||||
| #include "manifest.h" | ||||
| 
 | ||||
| #include <toolbox/stream/buffered_file_stream.h> | ||||
| #include <toolbox/hex.h> | ||||
| 
 | ||||
| struct ResourceManifestReader { | ||||
|     Storage* storage; | ||||
|     Stream* stream; | ||||
|     string_t linebuf; | ||||
|     ResourceManifestEntry entry; | ||||
| }; | ||||
| 
 | ||||
| ResourceManifestReader* resource_manifest_reader_alloc(Storage* storage) { | ||||
|     ResourceManifestReader* resource_manifest = | ||||
|         (ResourceManifestReader*)malloc(sizeof(ResourceManifestReader)); | ||||
|     resource_manifest->storage = storage; | ||||
|     resource_manifest->stream = buffered_file_stream_alloc(resource_manifest->storage); | ||||
|     memset(&resource_manifest->entry, 0, sizeof(ResourceManifestEntry)); | ||||
|     string_init(resource_manifest->entry.name); | ||||
|     string_init(resource_manifest->linebuf); | ||||
|     return resource_manifest; | ||||
| } | ||||
| 
 | ||||
| void resource_manifest_reader_free(ResourceManifestReader* resource_manifest) { | ||||
|     furi_assert(resource_manifest); | ||||
| 
 | ||||
|     string_clear(resource_manifest->linebuf); | ||||
|     string_clear(resource_manifest->entry.name); | ||||
|     buffered_file_stream_close(resource_manifest->stream); | ||||
|     stream_free(resource_manifest->stream); | ||||
|     free(resource_manifest); | ||||
| } | ||||
| 
 | ||||
| bool resource_manifest_reader_open(ResourceManifestReader* resource_manifest, const char* filename) { | ||||
|     furi_assert(resource_manifest); | ||||
| 
 | ||||
|     return buffered_file_stream_open( | ||||
|         resource_manifest->stream, filename, FSAM_READ, FSOM_OPEN_EXISTING); | ||||
| } | ||||
| 
 | ||||
| /* Read entries in format of
 | ||||
|  * F:<hash>:<size>:<name> | ||||
|  * D:<name>  | ||||
|  */ | ||||
| ResourceManifestEntry* resource_manifest_reader_next(ResourceManifestReader* resource_manifest) { | ||||
|     furi_assert(resource_manifest); | ||||
| 
 | ||||
|     string_reset(resource_manifest->entry.name); | ||||
|     resource_manifest->entry.type = ResourceManifestEntryTypeUnknown; | ||||
|     resource_manifest->entry.size = 0; | ||||
|     memset(resource_manifest->entry.hash, 0, sizeof(resource_manifest->entry.hash)); | ||||
| 
 | ||||
|     do { | ||||
|         if(!stream_read_line(resource_manifest->stream, resource_manifest->linebuf)) { | ||||
|             return NULL; | ||||
|         } | ||||
| 
 | ||||
|         /* Trim end of line */ | ||||
|         string_strim(resource_manifest->linebuf); | ||||
| 
 | ||||
|         char type_code = string_get_char(resource_manifest->linebuf, 0); | ||||
|         switch(type_code) { | ||||
|         case 'F': | ||||
|             resource_manifest->entry.type = ResourceManifestEntryTypeFile; | ||||
|             break; | ||||
|         case 'D': | ||||
|             resource_manifest->entry.type = ResourceManifestEntryTypeDirectory; | ||||
|             break; | ||||
|         default: /* Skip other entries - version, timestamp, etc */ | ||||
|             continue; | ||||
|         }; | ||||
| 
 | ||||
|         if(resource_manifest->entry.type == ResourceManifestEntryTypeFile) { | ||||
|             /* Parse file entry
 | ||||
|               F:<hash>:<size>:<name> */ | ||||
| 
 | ||||
|             /* Remove entry type code */ | ||||
|             string_right(resource_manifest->linebuf, 2); | ||||
| 
 | ||||
|             if(string_search_char(resource_manifest->linebuf, ':') != | ||||
|                sizeof(resource_manifest->entry.hash) * 2) { | ||||
|                 /* Invalid hash */ | ||||
|                 continue; | ||||
|             } | ||||
| 
 | ||||
|             /* Read hash */ | ||||
|             hex_chars_to_uint8( | ||||
|                 string_get_cstr(resource_manifest->linebuf), resource_manifest->entry.hash); | ||||
| 
 | ||||
|             /* Remove hash */ | ||||
|             string_right( | ||||
|                 resource_manifest->linebuf, sizeof(resource_manifest->entry.hash) * 2 + 1); | ||||
| 
 | ||||
|             resource_manifest->entry.size = atoi(string_get_cstr(resource_manifest->linebuf)); | ||||
| 
 | ||||
|             /* Remove size */ | ||||
|             size_t offs = string_search_char(resource_manifest->linebuf, ':'); | ||||
|             string_right(resource_manifest->linebuf, offs + 1); | ||||
| 
 | ||||
|             string_set(resource_manifest->entry.name, resource_manifest->linebuf); | ||||
|         } else if(resource_manifest->entry.type == ResourceManifestEntryTypeDirectory) { | ||||
|             /* Parse directory entry
 | ||||
|                D:<name> */ | ||||
| 
 | ||||
|             /* Remove entry type code */ | ||||
|             string_right(resource_manifest->linebuf, 2); | ||||
| 
 | ||||
|             string_set(resource_manifest->entry.name, resource_manifest->linebuf); | ||||
|         } | ||||
| 
 | ||||
|         return &resource_manifest->entry; | ||||
|     } while(true); | ||||
| 
 | ||||
|     return NULL; | ||||
| } | ||||
							
								
								
									
										58
									
								
								lib/update_util/resources/manifest.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										58
									
								
								lib/update_util/resources/manifest.h
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,58 @@ | ||||
| #pragma once | ||||
| 
 | ||||
| #include <storage/storage.h> | ||||
| 
 | ||||
| #include <m-string.h> | ||||
| #include <stdbool.h> | ||||
| #include <stdint.h> | ||||
| 
 | ||||
| #ifdef __cplusplus | ||||
| extern "C" { | ||||
| #endif | ||||
| 
 | ||||
| typedef enum { | ||||
|     ResourceManifestEntryTypeUnknown = 0, | ||||
|     ResourceManifestEntryTypeDirectory, | ||||
|     ResourceManifestEntryTypeFile, | ||||
| } ResourceManifestEntryType; | ||||
| 
 | ||||
| typedef struct { | ||||
|     ResourceManifestEntryType type; | ||||
|     string_t name; | ||||
|     uint32_t size; | ||||
|     uint8_t hash[16]; | ||||
| } ResourceManifestEntry; | ||||
| 
 | ||||
| typedef struct ResourceManifestReader ResourceManifestReader; | ||||
| 
 | ||||
| /**
 | ||||
|  * @brief Initialize resource manifest reader | ||||
|  * @param storage Storage API pointer | ||||
|  * @return allocated object | ||||
|  */ | ||||
| ResourceManifestReader* resource_manifest_reader_alloc(Storage* storage); | ||||
| 
 | ||||
| /**
 | ||||
|  * @brief Release resource manifest reader | ||||
|  * @param resource_manifest allocated object | ||||
|  */ | ||||
| void resource_manifest_reader_free(ResourceManifestReader* resource_manifest); | ||||
| 
 | ||||
| /**
 | ||||
|  * @brief Initialize resource manifest reader iteration | ||||
|  * @param resource_manifest allocated object | ||||
|  * @param filename manifest file name | ||||
|  * @return true if file opened | ||||
|  */ | ||||
| bool resource_manifest_reader_open(ResourceManifestReader* resource_manifest, const char* filename); | ||||
| 
 | ||||
| /**
 | ||||
|  * @brief Read next file/dir entry from manifest | ||||
|  * @param resource_manifest allocated object | ||||
|  * @return entry or NULL if end of file | ||||
|  */ | ||||
| ResourceManifestEntry* resource_manifest_reader_next(ResourceManifestReader* resource_manifest); | ||||
| 
 | ||||
| #ifdef __cplusplus | ||||
| } // extern "C"
 | ||||
| #endif | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user
	 hedger
						hedger