[FL-2527] Updater: Migrating to new manifest path convention (#1213)
* Updater: Migrating to new manifest path convention * RPC: Added update preparation status to RPC * RPC: bumped protobuf submodule * Bumped protobuf_version.h * FuriCore: add missing include. Lib: make mlib smaller * Explicitly tell where we have doubles and fix random in animations * makefile: added -DLFS_NO_DEBUG * Updater: path len constant dedup * Updater: checking for hardware version match before parsing manifest * LD: moved _DRIVER_CONTEXT sections to .bss, where they belong. * LD: avoiding PROBGITS warning, moved _CONTEXT to data * Updater: Added version check on update package - refusing to install outdated Co-authored-by: あく <alleteam@gmail.com>
This commit is contained in:
@@ -26,6 +26,7 @@ UpdateManifest* update_manifest_alloc() {
|
||||
string_init(update_manifest->staged_loader_file);
|
||||
string_init(update_manifest->resource_bundle);
|
||||
update_manifest->target = 0;
|
||||
update_manifest->manifest_version = 0;
|
||||
memset(update_manifest->ob_reference.bytes, 0, FURI_HAL_FLASH_OB_RAW_SIZE_BYTES);
|
||||
memset(update_manifest->ob_compare_mask.bytes, 0, FURI_HAL_FLASH_OB_RAW_SIZE_BYTES);
|
||||
memset(update_manifest->ob_write_mask.bytes, 0, FURI_HAL_FLASH_OB_RAW_SIZE_BYTES);
|
||||
@@ -49,12 +50,11 @@ static bool
|
||||
furi_assert(flipper_file);
|
||||
|
||||
string_t filetype;
|
||||
uint32_t version = 0;
|
||||
|
||||
// TODO: compare filetype?
|
||||
string_init(filetype);
|
||||
update_manifest->valid =
|
||||
flipper_format_read_header(flipper_file, filetype, &version) &&
|
||||
flipper_format_read_header(flipper_file, filetype, &update_manifest->manifest_version) &&
|
||||
flipper_format_read_string(flipper_file, MANIFEST_KEY_INFO, update_manifest->version) &&
|
||||
flipper_format_read_uint32(
|
||||
flipper_file, MANIFEST_KEY_TARGET, &update_manifest->target, 1) &&
|
||||
@@ -68,7 +68,7 @@ static bool
|
||||
string_clear(filetype);
|
||||
|
||||
if(update_manifest->valid) {
|
||||
/* Optional fields - we can have dfu, radio, or both */
|
||||
/* Optional fields - we can have dfu, radio, resources, or any combination */
|
||||
flipper_format_read_string(
|
||||
flipper_file, MANIFEST_KEY_DFU_FILE, update_manifest->firmware_dfu_image);
|
||||
flipper_format_read_string(
|
||||
@@ -131,8 +131,7 @@ static bool ob_data_check_masked_values_valid(
|
||||
const FuriHalFlashRawOptionByteData* mask) {
|
||||
bool valid = true;
|
||||
for(size_t idx = 0; valid && (idx < FURI_HAL_FLASH_OB_TOTAL_VALUES); ++idx) {
|
||||
valid &= (data->obs[idx]. dword & mask->obs[idx].dword) ==
|
||||
data->obs[idx].dword;
|
||||
valid &= (data->obs[idx].dword & mask->obs[idx].dword) == data->obs[idx].dword;
|
||||
}
|
||||
return valid;
|
||||
}
|
||||
|
@@ -12,6 +12,7 @@ extern "C" {
|
||||
/* Paths don't include /ext -- because at startup SD card is mounted as root */
|
||||
#define UPDATE_DIR_DEFAULT_REL_PATH "/update"
|
||||
#define UPDATE_MANIFEST_DEFAULT_NAME "update.fuf"
|
||||
#define UPDATE_MANIFEST_POINTER_FILE_NAME ".fupdate"
|
||||
|
||||
typedef union {
|
||||
uint8_t raw[6];
|
||||
@@ -27,6 +28,7 @@ typedef union {
|
||||
_Static_assert(sizeof(UpdateManifestRadioVersion) == 6, "UpdateManifestRadioVersion size error");
|
||||
|
||||
typedef struct {
|
||||
uint32_t manifest_version;
|
||||
string_t version;
|
||||
uint32_t target;
|
||||
string_t staged_loader_file;
|
||||
|
@@ -12,7 +12,6 @@
|
||||
#define UPDATE_ROOT_DIR "/ext" UPDATE_DIR_DEFAULT_REL_PATH
|
||||
#define UPDATE_PREFIX "/ext" UPDATE_DIR_DEFAULT_REL_PATH "/"
|
||||
#define UPDATE_SUFFIX "/" UPDATE_MANIFEST_DEFAULT_NAME
|
||||
#define MAX_DIR_NAME_LEN 250
|
||||
|
||||
static const char* update_prepare_result_descr[] = {
|
||||
[UpdatePrepareResultOK] = "OK",
|
||||
@@ -21,6 +20,8 @@ static const char* update_prepare_result_descr[] = {
|
||||
[UpdatePrepareResultManifestInvalid] = "Invalid manifest data",
|
||||
[UpdatePrepareResultStageMissing] = "Missing Stage2 loader",
|
||||
[UpdatePrepareResultStageIntegrityError] = "Corrupted Stage2 loader",
|
||||
[UpdatePrepareResultManifestPointerError] = "Failed to create update pointer file",
|
||||
[UpdatePrepareResultOutdatedManifestVersion] = "Update package is too old",
|
||||
};
|
||||
|
||||
const char* update_operation_describe_preparation_result(const UpdatePrepareResult value) {
|
||||
@@ -31,65 +32,7 @@ const char* update_operation_describe_preparation_result(const UpdatePrepareResu
|
||||
}
|
||||
}
|
||||
|
||||
bool update_operation_get_package_dir_name(const char* full_path, string_t out_manifest_dir) {
|
||||
bool path_ok = false;
|
||||
string_t full_path_str;
|
||||
string_init_set(full_path_str, full_path);
|
||||
string_reset(out_manifest_dir);
|
||||
bool start_end_ok = string_start_with_str_p(full_path_str, UPDATE_PREFIX) &&
|
||||
string_end_with_str_p(full_path_str, UPDATE_SUFFIX);
|
||||
int16_t dir_name_len =
|
||||
strlen(full_path) - strlen(UPDATE_PREFIX) - strlen(UPDATE_MANIFEST_DEFAULT_NAME) - 1;
|
||||
if(dir_name_len == -1) {
|
||||
path_ok = true;
|
||||
} else if(start_end_ok && (dir_name_len > 0)) {
|
||||
string_set_n(out_manifest_dir, full_path_str, strlen(UPDATE_PREFIX), dir_name_len);
|
||||
path_ok = true;
|
||||
if(string_search_char(out_manifest_dir, '/') != STRING_FAILURE) {
|
||||
string_reset(out_manifest_dir);
|
||||
path_ok = false;
|
||||
}
|
||||
}
|
||||
string_clear(full_path_str);
|
||||
return path_ok;
|
||||
}
|
||||
|
||||
int32_t update_operation_get_package_index(Storage* storage, const char* update_package_dir) {
|
||||
furi_assert(storage);
|
||||
furi_assert(update_package_dir);
|
||||
|
||||
if(strlen(update_package_dir) == 0) {
|
||||
return UPDATE_OPERATION_ROOT_DIR_PACKAGE_MAGIC;
|
||||
}
|
||||
|
||||
bool found = false;
|
||||
int32_t index = 0;
|
||||
File* dir = storage_file_alloc(storage);
|
||||
FileInfo fi = {0};
|
||||
char* name_buffer = malloc(MAX_DIR_NAME_LEN);
|
||||
do {
|
||||
if(!storage_dir_open(dir, UPDATE_ROOT_DIR)) {
|
||||
break;
|
||||
}
|
||||
|
||||
while(storage_dir_read(dir, &fi, name_buffer, MAX_DIR_NAME_LEN)) {
|
||||
index++;
|
||||
if(strcmp(name_buffer, update_package_dir)) {
|
||||
continue;
|
||||
} else {
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
} while(false);
|
||||
|
||||
free(name_buffer);
|
||||
storage_file_free(dir);
|
||||
|
||||
return found ? index : -1;
|
||||
}
|
||||
|
||||
bool update_operation_get_current_package_path(Storage* storage, string_t out_path) {
|
||||
static bool update_operation_get_current_package_path_rtc(Storage* storage, string_t out_path) {
|
||||
const uint32_t update_index = furi_hal_rtc_get_register(FuriHalRtcRegisterUpdateFolderFSIndex);
|
||||
string_set_str(out_path, UPDATE_ROOT_DIR);
|
||||
if(update_index == UPDATE_OPERATION_ROOT_DIR_PACKAGE_MAGIC) {
|
||||
@@ -100,13 +43,13 @@ bool update_operation_get_current_package_path(Storage* storage, string_t out_pa
|
||||
uint32_t iter_index = 0;
|
||||
File* dir = storage_file_alloc(storage);
|
||||
FileInfo fi = {0};
|
||||
char* name_buffer = malloc(MAX_DIR_NAME_LEN);
|
||||
char* name_buffer = malloc(UPDATE_OPERATION_MAX_MANIFEST_PATH_LEN);
|
||||
do {
|
||||
if(!storage_dir_open(dir, UPDATE_ROOT_DIR)) {
|
||||
break;
|
||||
}
|
||||
|
||||
while(storage_dir_read(dir, &fi, name_buffer, MAX_DIR_NAME_LEN)) {
|
||||
while(storage_dir_read(dir, &fi, name_buffer, UPDATE_OPERATION_MAX_MANIFEST_PATH_LEN)) {
|
||||
if(++iter_index == update_index) {
|
||||
found = true;
|
||||
path_append(out_path, name_buffer);
|
||||
@@ -124,79 +67,148 @@ bool update_operation_get_current_package_path(Storage* storage, string_t out_pa
|
||||
return found;
|
||||
}
|
||||
|
||||
#define UPDATE_FILE_POINTER_FN "/ext/" UPDATE_MANIFEST_POINTER_FILE_NAME
|
||||
#define UPDATE_MANIFEST_MAX_PATH_LEN 256u
|
||||
|
||||
bool update_operation_get_current_package_manifest_path(Storage* storage, string_t out_path) {
|
||||
string_reset(out_path);
|
||||
if(storage_common_stat(storage, UPDATE_FILE_POINTER_FN, NULL) == FSE_OK) {
|
||||
char* manifest_name_buffer = malloc(UPDATE_MANIFEST_MAX_PATH_LEN);
|
||||
File* upd_file = NULL;
|
||||
do {
|
||||
upd_file = storage_file_alloc(storage);
|
||||
if(!storage_file_open(
|
||||
upd_file, UPDATE_FILE_POINTER_FN, FSAM_READ, FSOM_OPEN_EXISTING)) {
|
||||
break;
|
||||
}
|
||||
uint16_t bytes_read =
|
||||
storage_file_read(upd_file, manifest_name_buffer, UPDATE_MANIFEST_MAX_PATH_LEN);
|
||||
if((bytes_read == 0) || (bytes_read == UPDATE_MANIFEST_MAX_PATH_LEN)) {
|
||||
break;
|
||||
}
|
||||
if(storage_common_stat(storage, manifest_name_buffer, NULL) != FSE_OK) {
|
||||
break;
|
||||
}
|
||||
string_set_str(out_path, manifest_name_buffer);
|
||||
} while(0);
|
||||
free(manifest_name_buffer);
|
||||
storage_file_free(upd_file);
|
||||
} else {
|
||||
/* legacy, will be deprecated */
|
||||
string_t rtcpath;
|
||||
string_init(rtcpath);
|
||||
do {
|
||||
if(!update_operation_get_current_package_path_rtc(storage, rtcpath)) {
|
||||
break;
|
||||
}
|
||||
path_concat(string_get_cstr(rtcpath), UPDATE_MANIFEST_DEFAULT_NAME, out_path);
|
||||
} while(0);
|
||||
string_clear(rtcpath);
|
||||
}
|
||||
return !string_empty_p(out_path);
|
||||
}
|
||||
|
||||
static bool update_operation_persist_manifest_path(Storage* storage, const char* manifest_path) {
|
||||
const uint16_t manifest_path_len = strlen(manifest_path);
|
||||
furi_check(manifest_path && manifest_path_len);
|
||||
bool success = false;
|
||||
File* file = storage_file_alloc(storage);
|
||||
do {
|
||||
if(manifest_path_len >= UPDATE_OPERATION_MAX_MANIFEST_PATH_LEN) {
|
||||
break;
|
||||
}
|
||||
|
||||
if(!storage_file_open(file, UPDATE_FILE_POINTER_FN, FSAM_WRITE, FSOM_CREATE_ALWAYS)) {
|
||||
break;
|
||||
}
|
||||
|
||||
if(storage_file_write(file, manifest_path, manifest_path_len) != manifest_path_len) {
|
||||
break;
|
||||
}
|
||||
|
||||
success = true;
|
||||
} while(0);
|
||||
storage_file_free(file);
|
||||
return success;
|
||||
}
|
||||
|
||||
UpdatePrepareResult update_operation_prepare(const char* manifest_file_path) {
|
||||
string_t update_folder;
|
||||
string_init(update_folder);
|
||||
if(!update_operation_get_package_dir_name(manifest_file_path, update_folder)) {
|
||||
string_clear(update_folder);
|
||||
return UpdatePrepareResultManifestPathInvalid;
|
||||
}
|
||||
|
||||
UpdatePrepareResult result = UpdatePrepareResultManifestFolderNotFound;
|
||||
Storage* storage = furi_record_open("storage");
|
||||
int32_t update_index =
|
||||
update_operation_get_package_index(storage, string_get_cstr(update_folder));
|
||||
string_clear(update_folder);
|
||||
|
||||
if(update_index < 0) {
|
||||
furi_record_close("storage");
|
||||
return UpdatePrepareResultManifestFolderNotFound;
|
||||
}
|
||||
|
||||
string_t update_dir_path;
|
||||
string_init(update_dir_path);
|
||||
path_extract_dirname(manifest_file_path, update_dir_path);
|
||||
|
||||
UpdatePrepareResult result = UpdatePrepareResultManifestInvalid;
|
||||
UpdateManifest* manifest = update_manifest_alloc();
|
||||
if(update_manifest_init(manifest, manifest_file_path)) {
|
||||
result = UpdatePrepareResultStageMissing;
|
||||
File* file = storage_file_alloc(storage);
|
||||
File* file = storage_file_alloc(storage);
|
||||
|
||||
string_t stage_path;
|
||||
string_init(stage_path);
|
||||
do {
|
||||
if(storage_common_stat(storage, manifest_file_path, NULL) != FSE_OK) {
|
||||
break;
|
||||
}
|
||||
|
||||
if(!update_manifest_init(manifest, manifest_file_path)) {
|
||||
result = UpdatePrepareResultManifestInvalid;
|
||||
break;
|
||||
}
|
||||
|
||||
if(manifest->manifest_version < UPDATE_OPERATION_MIN_MANIFEST_VERSION) {
|
||||
result = UpdatePrepareResultOutdatedManifestVersion;
|
||||
break;
|
||||
}
|
||||
|
||||
if(furi_hal_version_get_hw_target() != manifest->target) {
|
||||
result = UpdatePrepareResultTargetMismatch;
|
||||
break;
|
||||
}
|
||||
|
||||
string_t stage_path;
|
||||
string_init(stage_path);
|
||||
path_extract_dirname(manifest_file_path, stage_path);
|
||||
path_append(stage_path, string_get_cstr(manifest->staged_loader_file));
|
||||
|
||||
uint32_t crc = 0;
|
||||
do {
|
||||
if(!storage_file_open(
|
||||
file, string_get_cstr(stage_path), FSAM_READ, FSOM_OPEN_EXISTING)) {
|
||||
break;
|
||||
}
|
||||
|
||||
result = UpdatePrepareResultStageIntegrityError;
|
||||
crc = crc32_calc_file(file, NULL, NULL);
|
||||
} while(false);
|
||||
|
||||
string_clear(stage_path);
|
||||
storage_file_free(file);
|
||||
|
||||
if(crc == manifest->staged_loader_crc) {
|
||||
furi_hal_rtc_set_boot_mode(FuriHalRtcBootModePreUpdate);
|
||||
update_operation_persist_package_index(update_index);
|
||||
result = UpdatePrepareResultOK;
|
||||
if(!storage_file_open(file, string_get_cstr(stage_path), FSAM_READ, FSOM_OPEN_EXISTING)) {
|
||||
result = UpdatePrepareResultStageMissing;
|
||||
break;
|
||||
}
|
||||
}
|
||||
furi_record_close("storage");
|
||||
|
||||
uint32_t crc = crc32_calc_file(file, NULL, NULL);
|
||||
if(crc != manifest->staged_loader_crc) {
|
||||
result = UpdatePrepareResultStageIntegrityError;
|
||||
break;
|
||||
}
|
||||
|
||||
if(!update_operation_persist_manifest_path(storage, manifest_file_path)) {
|
||||
result = UpdatePrepareResultManifestPointerError;
|
||||
break;
|
||||
}
|
||||
|
||||
result = UpdatePrepareResultOK;
|
||||
furi_hal_rtc_set_boot_mode(FuriHalRtcBootModePreUpdate);
|
||||
} while(false);
|
||||
|
||||
string_clear(stage_path);
|
||||
storage_file_free(file);
|
||||
|
||||
update_manifest_free(manifest);
|
||||
furi_record_close("storage");
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
bool update_operation_is_armed() {
|
||||
FuriHalRtcBootMode boot_mode = furi_hal_rtc_get_boot_mode();
|
||||
const uint32_t rtc_upd_index =
|
||||
furi_hal_rtc_get_register(FuriHalRtcRegisterUpdateFolderFSIndex);
|
||||
Storage* storage = furi_record_open("storage");
|
||||
const bool upd_fn_ptr_exists =
|
||||
(storage_common_stat(storage, UPDATE_FILE_POINTER_FN, NULL) == FSE_OK);
|
||||
furi_record_close("storage");
|
||||
return (boot_mode >= FuriHalRtcBootModePreUpdate) &&
|
||||
(boot_mode <= FuriHalRtcBootModePostUpdate) &&
|
||||
(furi_hal_rtc_get_register(FuriHalRtcRegisterUpdateFolderFSIndex) > 0);
|
||||
((rtc_upd_index != INT_MAX) || upd_fn_ptr_exists);
|
||||
}
|
||||
|
||||
void update_operation_disarm() {
|
||||
furi_hal_rtc_set_boot_mode(FuriHalRtcBootModeNormal);
|
||||
furi_hal_rtc_set_register(
|
||||
FuriHalRtcRegisterUpdateFolderFSIndex, INT_MAX);
|
||||
}
|
||||
|
||||
void update_operation_persist_package_index(int32_t index) {
|
||||
furi_check(index >= 0);
|
||||
furi_hal_rtc_set_register(FuriHalRtcRegisterUpdateFolderFSIndex, index);
|
||||
}
|
||||
furi_hal_rtc_set_register(FuriHalRtcRegisterUpdateFolderFSIndex, INT_MAX);
|
||||
Storage* storage = furi_record_open("storage");
|
||||
storage_simply_remove(storage, UPDATE_FILE_POINTER_FN);
|
||||
furi_record_close("storage");
|
||||
}
|
@@ -9,6 +9,8 @@ extern "C" {
|
||||
#endif
|
||||
|
||||
#define UPDATE_OPERATION_ROOT_DIR_PACKAGE_MAGIC 0
|
||||
#define UPDATE_OPERATION_MAX_MANIFEST_PATH_LEN 255u
|
||||
#define UPDATE_OPERATION_MIN_MANIFEST_VERSION 2
|
||||
|
||||
/*
|
||||
* Checks if supplied full manifest path is valid
|
||||
@@ -19,6 +21,7 @@ extern "C" {
|
||||
*/
|
||||
bool update_operation_get_package_dir_name(const char* full_path, string_t out_manifest_dir);
|
||||
|
||||
/* When updating this enum, also update assets/protobuf/system.proto */
|
||||
typedef enum {
|
||||
UpdatePrepareResultOK,
|
||||
UpdatePrepareResultManifestPathInvalid,
|
||||
@@ -26,6 +29,9 @@ typedef enum {
|
||||
UpdatePrepareResultManifestInvalid,
|
||||
UpdatePrepareResultStageMissing,
|
||||
UpdatePrepareResultStageIntegrityError,
|
||||
UpdatePrepareResultManifestPointerError,
|
||||
UpdatePrepareResultTargetMismatch,
|
||||
UpdatePrepareResultOutdatedManifestVersion,
|
||||
} UpdatePrepareResult;
|
||||
|
||||
const char* update_operation_describe_preparation_result(const UpdatePrepareResult value);
|
||||
@@ -37,27 +43,13 @@ const char* update_operation_describe_preparation_result(const UpdatePrepareResu
|
||||
*/
|
||||
UpdatePrepareResult update_operation_prepare(const char* manifest_file_path);
|
||||
|
||||
/*
|
||||
* Gets update package index to pass in RTC registers
|
||||
* @param storage Storage API
|
||||
* @param update_package_dir Package directory name
|
||||
* @return int32_t <=0 - error, >0 - update index value
|
||||
*/
|
||||
int32_t update_operation_get_package_index(Storage* storage, const char* update_package_dir);
|
||||
|
||||
/*
|
||||
* Gets filesystem path for current update package
|
||||
* @param storage Storage API
|
||||
* @param out_path Path to directory with manifest & related files. Must be initialized
|
||||
* @param out_path Path to manifest. Must be initialized
|
||||
* @return true if path was restored successfully
|
||||
*/
|
||||
bool update_operation_get_current_package_path(Storage* storage, string_t out_path);
|
||||
|
||||
/*
|
||||
* Stores given update index in RTC registers
|
||||
* @param index Value to store
|
||||
*/
|
||||
void update_operation_persist_package_index(int32_t index);
|
||||
bool update_operation_get_current_package_manifest_path(Storage* storage, string_t out_path);
|
||||
|
||||
/*
|
||||
* Checks if an update operation step is pending after reset
|
||||
|
Reference in New Issue
Block a user