[FL-2578] Updater fixes related to /int handling (#1359)
* Updater fixes related to /int handling updater: performing factory reset on update, checking for LFS free space before updating, fixed improper error handling on backup/restore operations, rebalanced update stage weights for better progress visuals scripts: added CLI output validation for selfupdate.py storage: added pointer validation in storage_int_common_fs_info desktop: fixed crash on rendering invalid slideshows * Typo fix * rpc: Updated protobuf to 0.9 * rpc: removed updater status conversion Co-authored-by: あく <alleteam@gmail.com>
This commit is contained in:
		
							parent
							
								
									4a1695ba1c
								
							
						
					
					
						commit
						b95cd2df14
					
				| @ -12,6 +12,7 @@ | |||||||
| struct Slideshow { | struct Slideshow { | ||||||
|     Icon icon; |     Icon icon; | ||||||
|     uint32_t current_frame; |     uint32_t current_frame; | ||||||
|  |     bool loaded; | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| #pragma pack(push, 1) | #pragma pack(push, 1) | ||||||
| @ -34,6 +35,7 @@ _Static_assert(sizeof(SlideshowFrameHeader) == 2, "Incorrect SlideshowFrameHeade | |||||||
| 
 | 
 | ||||||
| Slideshow* slideshow_alloc() { | Slideshow* slideshow_alloc() { | ||||||
|     Slideshow* ret = malloc(sizeof(Slideshow)); |     Slideshow* ret = malloc(sizeof(Slideshow)); | ||||||
|  |     ret->loaded = false; | ||||||
|     return ret; |     return ret; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| @ -52,7 +54,7 @@ void slideshow_free(Slideshow* slideshow) { | |||||||
| bool slideshow_load(Slideshow* slideshow, const char* fspath) { | bool slideshow_load(Slideshow* slideshow, const char* fspath) { | ||||||
|     Storage* storage = furi_record_open("storage"); |     Storage* storage = furi_record_open("storage"); | ||||||
|     File* slideshow_file = storage_file_alloc(storage); |     File* slideshow_file = storage_file_alloc(storage); | ||||||
|     bool load_success = false; |     slideshow->loaded = false; | ||||||
|     do { |     do { | ||||||
|         if(!storage_file_open(slideshow_file, fspath, FSAM_READ, FSOM_OPEN_EXISTING)) { |         if(!storage_file_open(slideshow_file, fspath, FSAM_READ, FSOM_OPEN_EXISTING)) { | ||||||
|             break; |             break; | ||||||
| @ -80,12 +82,16 @@ bool slideshow_load(Slideshow* slideshow, const char* fspath) { | |||||||
|                frame_header.size) { |                frame_header.size) { | ||||||
|                 break; |                 break; | ||||||
|             } |             } | ||||||
|             load_success = (frame_idx + 1) == header.frame_count; |             slideshow->loaded = (frame_idx + 1) == header.frame_count; | ||||||
|         } |         } | ||||||
|     } while(false); |     } while(false); | ||||||
|     storage_file_free(slideshow_file); |     storage_file_free(slideshow_file); | ||||||
|     furi_record_close("storage"); |     furi_record_close("storage"); | ||||||
|     return load_success; |     return slideshow->loaded; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | bool slideshow_is_loaded(Slideshow* slideshow) { | ||||||
|  |     return slideshow->loaded; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| bool slideshow_advance(Slideshow* slideshow) { | bool slideshow_advance(Slideshow* slideshow) { | ||||||
|  | |||||||
| @ -8,6 +8,7 @@ Slideshow* slideshow_alloc(); | |||||||
| 
 | 
 | ||||||
| void slideshow_free(Slideshow* slideshow); | void slideshow_free(Slideshow* slideshow); | ||||||
| bool slideshow_load(Slideshow* slideshow, const char* fspath); | bool slideshow_load(Slideshow* slideshow, const char* fspath); | ||||||
|  | bool slideshow_is_loaded(Slideshow* slideshow); | ||||||
| void slideshow_goback(Slideshow* slideshow); | void slideshow_goback(Slideshow* slideshow); | ||||||
| bool slideshow_advance(Slideshow* slideshow); | bool slideshow_advance(Slideshow* slideshow); | ||||||
| void slideshow_draw(Slideshow* slideshow, Canvas* canvas, uint8_t x, uint8_t y); | void slideshow_draw(Slideshow* slideshow, Canvas* canvas, uint8_t x, uint8_t y); | ||||||
|  | |||||||
| @ -21,7 +21,9 @@ static void desktop_view_slideshow_draw(Canvas* canvas, void* model) { | |||||||
|     DesktopSlideshowViewModel* m = model; |     DesktopSlideshowViewModel* m = model; | ||||||
| 
 | 
 | ||||||
|     canvas_clear(canvas); |     canvas_clear(canvas); | ||||||
|     slideshow_draw(m->slideshow, canvas, 0, 0); |     if(slideshow_is_loaded(m->slideshow)) { | ||||||
|  |         slideshow_draw(m->slideshow, canvas, 0, 0); | ||||||
|  |     } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| static bool desktop_view_slideshow_input(InputEvent* event, void* context) { | static bool desktop_view_slideshow_input(InputEvent* event, void* context) { | ||||||
|  | |||||||
| @ -277,10 +277,6 @@ static void rpc_system_system_update_request_process(const PB_Main* request, voi | |||||||
| 
 | 
 | ||||||
|     UpdatePrepareResult update_prepare_result = |     UpdatePrepareResult update_prepare_result = | ||||||
|         update_operation_prepare(request->content.system_update_request.update_manifest); |         update_operation_prepare(request->content.system_update_request.update_manifest); | ||||||
|     /* RPC enum does not have such entry; setting to closest one */ |  | ||||||
|     if(update_prepare_result == UpdatePrepareResultOutdatedManifestVersion) { |  | ||||||
|         update_prepare_result = UpdatePrepareResultManifestInvalid; |  | ||||||
|     } |  | ||||||
| 
 | 
 | ||||||
|     PB_Main* response = malloc(sizeof(PB_Main)); |     PB_Main* response = malloc(sizeof(PB_Main)); | ||||||
|     response->command_id = request->command_id; |     response->command_id = request->command_id; | ||||||
|  | |||||||
| @ -655,11 +655,13 @@ static FS_Error storage_int_common_fs_info( | |||||||
|     lfs_t* lfs = lfs_get_from_storage(storage); |     lfs_t* lfs = lfs_get_from_storage(storage); | ||||||
|     LFSData* lfs_data = lfs_data_get_from_storage(storage); |     LFSData* lfs_data = lfs_data_get_from_storage(storage); | ||||||
| 
 | 
 | ||||||
|     *total_space = lfs_data->config.block_size * lfs_data->config.block_count; |     if(total_space) { | ||||||
|  |         *total_space = lfs_data->config.block_size * lfs_data->config.block_count; | ||||||
|  |     } | ||||||
| 
 | 
 | ||||||
|     lfs_ssize_t result = lfs_fs_size(lfs); |     lfs_ssize_t result = lfs_fs_size(lfs); | ||||||
|     if(result >= 0) { |     if(free_space && (result >= 0)) { | ||||||
|         *free_space = *total_space - (result * lfs_data->config.block_size); |         *free_space = (lfs_data->config.block_count - result) * lfs_data->config.block_size; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     return storage_int_parse_error(result); |     return storage_int_parse_error(result); | ||||||
|  | |||||||
| @ -44,17 +44,17 @@ static const UpdateTaskStageGroupMap update_task_stage_progress[] = { | |||||||
|     [UpdateTaskStageReadManifest] = STAGE_DEF(UpdateTaskStageGroupPreUpdate, 5), |     [UpdateTaskStageReadManifest] = STAGE_DEF(UpdateTaskStageGroupPreUpdate, 5), | ||||||
|     [UpdateTaskStageLfsBackup] = STAGE_DEF(UpdateTaskStageGroupPreUpdate, 15), |     [UpdateTaskStageLfsBackup] = STAGE_DEF(UpdateTaskStageGroupPreUpdate, 15), | ||||||
| 
 | 
 | ||||||
|     [UpdateTaskStageRadioImageValidate] = STAGE_DEF(UpdateTaskStageGroupRadio, 10), |     [UpdateTaskStageRadioImageValidate] = STAGE_DEF(UpdateTaskStageGroupRadio, 15), | ||||||
|     [UpdateTaskStageRadioErase] = STAGE_DEF(UpdateTaskStageGroupRadio, 50), |     [UpdateTaskStageRadioErase] = STAGE_DEF(UpdateTaskStageGroupRadio, 60), | ||||||
|     [UpdateTaskStageRadioWrite] = STAGE_DEF(UpdateTaskStageGroupRadio, 90), |     [UpdateTaskStageRadioWrite] = STAGE_DEF(UpdateTaskStageGroupRadio, 80), | ||||||
|     [UpdateTaskStageRadioInstall] = STAGE_DEF(UpdateTaskStageGroupRadio, 15), |     [UpdateTaskStageRadioInstall] = STAGE_DEF(UpdateTaskStageGroupRadio, 60), | ||||||
|     [UpdateTaskStageRadioBusy] = STAGE_DEF(UpdateTaskStageGroupRadio, 60), |     [UpdateTaskStageRadioBusy] = STAGE_DEF(UpdateTaskStageGroupRadio, 80), | ||||||
| 
 | 
 | ||||||
|     [UpdateTaskStageOBValidation] = STAGE_DEF(UpdateTaskStageGroupOptionBytes, 10), |     [UpdateTaskStageOBValidation] = STAGE_DEF(UpdateTaskStageGroupOptionBytes, 10), | ||||||
| 
 | 
 | ||||||
|     [UpdateTaskStageValidateDFUImage] = STAGE_DEF(UpdateTaskStageGroupFirmware, 100), |     [UpdateTaskStageValidateDFUImage] = STAGE_DEF(UpdateTaskStageGroupFirmware, 50), | ||||||
|     [UpdateTaskStageFlashWrite] = STAGE_DEF(UpdateTaskStageGroupFirmware, 200), |     [UpdateTaskStageFlashWrite] = STAGE_DEF(UpdateTaskStageGroupFirmware, 200), | ||||||
|     [UpdateTaskStageFlashValidate] = STAGE_DEF(UpdateTaskStageGroupFirmware, 50), |     [UpdateTaskStageFlashValidate] = STAGE_DEF(UpdateTaskStageGroupFirmware, 30), | ||||||
| 
 | 
 | ||||||
|     [UpdateTaskStageLfsRestore] = STAGE_DEF(UpdateTaskStageGroupPostUpdate, 30), |     [UpdateTaskStageLfsRestore] = STAGE_DEF(UpdateTaskStageGroupPostUpdate, 30), | ||||||
| 
 | 
 | ||||||
| @ -214,6 +214,7 @@ UpdateTask* update_task_alloc() { | |||||||
|     update_task->storage = furi_record_open("storage"); |     update_task->storage = furi_record_open("storage"); | ||||||
|     update_task->file = storage_file_alloc(update_task->storage); |     update_task->file = storage_file_alloc(update_task->storage); | ||||||
|     update_task->status_change_cb = NULL; |     update_task->status_change_cb = NULL; | ||||||
|  |     update_task->boot_mode = furi_hal_rtc_get_boot_mode(); | ||||||
|     string_init(update_task->update_path); |     string_init(update_task->update_path); | ||||||
| 
 | 
 | ||||||
|     FuriThread* thread = update_task->thread = furi_thread_alloc(); |     FuriThread* thread = update_task->thread = furi_thread_alloc(); | ||||||
|  | |||||||
| @ -1,6 +1,7 @@ | |||||||
| #pragma once | #pragma once | ||||||
| 
 | 
 | ||||||
| #include <storage/storage.h> | #include <storage/storage.h> | ||||||
|  | #include <furi_hal.h> | ||||||
| 
 | 
 | ||||||
| #define UPDATE_TASK_NOERR 0 | #define UPDATE_TASK_NOERR 0 | ||||||
| #define UPDATE_TASK_FAILED -1 | #define UPDATE_TASK_FAILED -1 | ||||||
| @ -14,6 +15,7 @@ typedef struct UpdateTask { | |||||||
|     File* file; |     File* file; | ||||||
|     updateProgressCb status_change_cb; |     updateProgressCb status_change_cb; | ||||||
|     void* status_change_cb_state; |     void* status_change_cb_state; | ||||||
|  |     FuriHalRtcBootMode boot_mode; | ||||||
| } UpdateTask; | } UpdateTask; | ||||||
| 
 | 
 | ||||||
| void update_task_set_progress(UpdateTask* update_task, UpdateTaskStage stage, uint8_t progress); | void update_task_set_progress(UpdateTask* update_task, UpdateTaskStage stage, uint8_t progress); | ||||||
|  | |||||||
| @ -67,7 +67,6 @@ static bool update_task_post_update(UpdateTask* update_task) { | |||||||
|             string_get_cstr(update_task->update_path), LFS_BACKUP_DEFAULT_FILENAME, file_path); |             string_get_cstr(update_task->update_path), LFS_BACKUP_DEFAULT_FILENAME, file_path); | ||||||
| 
 | 
 | ||||||
|         update_task_set_progress(update_task, UpdateTaskStageLfsRestore, 0); |         update_task_set_progress(update_task, UpdateTaskStageLfsRestore, 0); | ||||||
|         update_operation_disarm(); |  | ||||||
| 
 | 
 | ||||||
|         CHECK_RESULT(lfs_backup_unpack(update_task->storage, string_get_cstr(file_path))); |         CHECK_RESULT(lfs_backup_unpack(update_task->storage, string_get_cstr(file_path))); | ||||||
| 
 | 
 | ||||||
| @ -117,28 +116,32 @@ static bool update_task_post_update(UpdateTask* update_task) { | |||||||
| int32_t update_task_worker_backup_restore(void* context) { | int32_t update_task_worker_backup_restore(void* context) { | ||||||
|     furi_assert(context); |     furi_assert(context); | ||||||
|     UpdateTask* update_task = context; |     UpdateTask* update_task = context; | ||||||
|     bool success = false; |  | ||||||
| 
 | 
 | ||||||
|     FuriHalRtcBootMode boot_mode = furi_hal_rtc_get_boot_mode(); |     FuriHalRtcBootMode boot_mode = update_task->boot_mode; | ||||||
|     if((boot_mode != FuriHalRtcBootModePreUpdate) && (boot_mode != FuriHalRtcBootModePostUpdate)) { |     if((boot_mode != FuriHalRtcBootModePreUpdate) && (boot_mode != FuriHalRtcBootModePostUpdate)) { | ||||||
|         /* no idea how we got here. Clear to normal boot */ |         /* no idea how we got here. Do nothing */ | ||||||
|         update_operation_disarm(); |  | ||||||
|         return UPDATE_TASK_NOERR; |         return UPDATE_TASK_NOERR; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     if(!update_task_parse_manifest(update_task)) { |     bool success = false; | ||||||
|         return UPDATE_TASK_FAILED; |     do { | ||||||
|     } |         if(!update_task_parse_manifest(update_task)) { | ||||||
|  |             break; | ||||||
|  |         } | ||||||
| 
 | 
 | ||||||
|     /* Waiting for BT service to 'start', so we don't race for boot mode flag */ |         /* Waiting for BT service to 'start', so we don't race for boot mode flag */ | ||||||
|     furi_record_open("bt"); |         furi_record_open("bt"); | ||||||
|     furi_record_close("bt"); |         furi_record_close("bt"); | ||||||
| 
 | 
 | ||||||
|     if(boot_mode == FuriHalRtcBootModePreUpdate) { |         if(boot_mode == FuriHalRtcBootModePreUpdate) { | ||||||
|         success = update_task_pre_update(update_task); |             success = update_task_pre_update(update_task); | ||||||
|     } else if(boot_mode == FuriHalRtcBootModePostUpdate) { |         } else if(boot_mode == FuriHalRtcBootModePostUpdate) { | ||||||
|         success = update_task_post_update(update_task); |             success = update_task_post_update(update_task); | ||||||
|     } |             if(success) { | ||||||
|  |                 update_operation_disarm(); | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |     } while(false); | ||||||
| 
 | 
 | ||||||
|     if(!success) { |     if(!success) { | ||||||
|         update_task_set_progress(update_task, UpdateTaskStageError, 0); |         update_task_set_progress(update_task, UpdateTaskStageError, 0); | ||||||
|  | |||||||
| @ -340,6 +340,8 @@ int32_t update_task_worker_flash_writer(void* context) { | |||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         furi_hal_rtc_set_boot_mode(FuriHalRtcBootModePostUpdate); |         furi_hal_rtc_set_boot_mode(FuriHalRtcBootModePostUpdate); | ||||||
|  |         // Format LFS before restoring backup on next boot
 | ||||||
|  |         furi_hal_rtc_set_flag(FuriHalRtcFlagFactoryReset); | ||||||
| 
 | 
 | ||||||
|         update_task_set_progress(update_task, UpdateTaskStageCompleted, 100); |         update_task_set_progress(update_task, UpdateTaskStageCompleted, 100); | ||||||
|         success = true; |         success = true; | ||||||
|  | |||||||
| @ -1 +1 @@ | |||||||
| Subproject commit e3d9cdb66ce789f84f6f8e0bdd6d022187964425 | Subproject commit 6c1b8ae66a85bcd7e79e993a0b5573c38c302db5 | ||||||
| @ -12,6 +12,8 @@ | |||||||
| #define UPDATE_ROOT_DIR "/ext" UPDATE_DIR_DEFAULT_REL_PATH | #define UPDATE_ROOT_DIR "/ext" UPDATE_DIR_DEFAULT_REL_PATH | ||||||
| #define UPDATE_PREFIX "/ext" UPDATE_DIR_DEFAULT_REL_PATH "/" | #define UPDATE_PREFIX "/ext" UPDATE_DIR_DEFAULT_REL_PATH "/" | ||||||
| #define UPDATE_SUFFIX "/" UPDATE_MANIFEST_DEFAULT_NAME | #define UPDATE_SUFFIX "/" UPDATE_MANIFEST_DEFAULT_NAME | ||||||
|  | /* Need at least 4 free LFS pages before update */ | ||||||
|  | #define UPDATE_MIN_INT_FREE_SPACE 4 * 4 * 1024 | ||||||
| 
 | 
 | ||||||
| static const char* update_prepare_result_descr[] = { | static const char* update_prepare_result_descr[] = { | ||||||
|     [UpdatePrepareResultOK] = "OK", |     [UpdatePrepareResultOK] = "OK", | ||||||
| @ -22,6 +24,7 @@ static const char* update_prepare_result_descr[] = { | |||||||
|     [UpdatePrepareResultStageIntegrityError] = "Corrupted Stage2 loader", |     [UpdatePrepareResultStageIntegrityError] = "Corrupted Stage2 loader", | ||||||
|     [UpdatePrepareResultManifestPointerError] = "Failed to create update pointer file", |     [UpdatePrepareResultManifestPointerError] = "Failed to create update pointer file", | ||||||
|     [UpdatePrepareResultOutdatedManifestVersion] = "Update package is too old", |     [UpdatePrepareResultOutdatedManifestVersion] = "Update package is too old", | ||||||
|  |     [UpdatePrepareResultIntFull] = "Need more free space in internal storage", | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| const char* update_operation_describe_preparation_result(const UpdatePrepareResult value) { | const char* update_operation_describe_preparation_result(const UpdatePrepareResult value) { | ||||||
| @ -133,15 +136,22 @@ static bool update_operation_persist_manifest_path(Storage* storage, const char* | |||||||
| } | } | ||||||
| 
 | 
 | ||||||
| UpdatePrepareResult update_operation_prepare(const char* manifest_file_path) { | UpdatePrepareResult update_operation_prepare(const char* manifest_file_path) { | ||||||
|     UpdatePrepareResult result = UpdatePrepareResultManifestFolderNotFound; |     UpdatePrepareResult result = UpdatePrepareResultIntFull; | ||||||
|     Storage* storage = furi_record_open("storage"); |     Storage* storage = furi_record_open("storage"); | ||||||
|     UpdateManifest* manifest = update_manifest_alloc(); |     UpdateManifest* manifest = update_manifest_alloc(); | ||||||
|     File* file = storage_file_alloc(storage); |     File* file = storage_file_alloc(storage); | ||||||
| 
 | 
 | ||||||
|  |     uint64_t free_int_space; | ||||||
|     string_t stage_path; |     string_t stage_path; | ||||||
|     string_init(stage_path); |     string_init(stage_path); | ||||||
|     do { |     do { | ||||||
|  |         if((storage_common_fs_info(storage, "/int", NULL, &free_int_space) != FSE_OK) || | ||||||
|  |            (free_int_space < UPDATE_MIN_INT_FREE_SPACE)) { | ||||||
|  |             break; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|         if(storage_common_stat(storage, manifest_file_path, NULL) != FSE_OK) { |         if(storage_common_stat(storage, manifest_file_path, NULL) != FSE_OK) { | ||||||
|  |             result = UpdatePrepareResultManifestFolderNotFound; | ||||||
|             break; |             break; | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -32,6 +32,8 @@ typedef enum { | |||||||
|     UpdatePrepareResultManifestPointerError, |     UpdatePrepareResultManifestPointerError, | ||||||
|     UpdatePrepareResultTargetMismatch, |     UpdatePrepareResultTargetMismatch, | ||||||
|     UpdatePrepareResultOutdatedManifestVersion, |     UpdatePrepareResultOutdatedManifestVersion, | ||||||
|  |     UpdatePrepareResultIntFull, | ||||||
|  |     UpdatePrepareResultUnspecifiedError, | ||||||
| } UpdatePrepareResult; | } UpdatePrepareResult; | ||||||
| 
 | 
 | ||||||
| const char* update_operation_describe_preparation_result(const UpdatePrepareResult value); | const char* update_operation_describe_preparation_result(const UpdatePrepareResult value); | ||||||
|  | |||||||
| @ -76,12 +76,15 @@ class Main(App): | |||||||
|             manifest_name, pkg_name = manifest_path.parts[-1], manifest_path.parts[-2] |             manifest_name, pkg_name = manifest_path.parts[-1], manifest_path.parts[-2] | ||||||
| 
 | 
 | ||||||
|             pkg_dir_name = self.args.pkg_dir_name or pkg_name |             pkg_dir_name = self.args.pkg_dir_name or pkg_name | ||||||
|             flipper_update_path = f"/ext/update/{pkg_dir_name}" |             update_root = "/ext/update" | ||||||
|  |             flipper_update_path = f"{update_root}/{pkg_dir_name}" | ||||||
| 
 | 
 | ||||||
|             self.logger.info(f'Installing "{pkg_name}" from {flipper_update_path}') |             self.logger.info(f'Installing "{pkg_name}" from {flipper_update_path}') | ||||||
|             # if not os.path.exists(self.args.manifest_path): |             # if not os.path.exists(self.args.manifest_path): | ||||||
|             # self.logger.error("Error: package not found") |             # self.logger.error("Error: package not found") | ||||||
|             if not self.mkdir_on_storage(storage, flipper_update_path): |             if not self.mkdir_on_storage( | ||||||
|  |                 storage, update_root | ||||||
|  |             ) or not self.mkdir_on_storage(storage, flipper_update_path): | ||||||
|                 self.logger.error(f"Error: cannot create {storage.last_error}") |                 self.logger.error(f"Error: cannot create {storage.last_error}") | ||||||
|                 return -2 |                 return -2 | ||||||
| 
 | 
 | ||||||
| @ -99,6 +102,14 @@ class Main(App): | |||||||
|                 storage.send_and_wait_eol( |                 storage.send_and_wait_eol( | ||||||
|                     f"update install {flipper_update_path}/{manifest_name}\r" |                     f"update install {flipper_update_path}/{manifest_name}\r" | ||||||
|                 ) |                 ) | ||||||
|  |                 result = storage.read.until(storage.CLI_EOL) | ||||||
|  |                 if not b"Verifying" in result: | ||||||
|  |                     self.logger.error(f"Unexpected response: {result.decode('ascii')}") | ||||||
|  |                     return -4 | ||||||
|  |                 result = storage.read.until(storage.CLI_EOL) | ||||||
|  |                 if not result.startswith(b"OK"): | ||||||
|  |                     self.logger.error(result.decode("ascii")) | ||||||
|  |                     return -5 | ||||||
|                 break |                 break | ||||||
|             return 0 |             return 0 | ||||||
|         finally: |         finally: | ||||||
|  | |||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user
	 hedger
						hedger