esp-idf/tools/test_apps/storage/partition_table_readonly/main/main.c

360 wiersze
12 KiB
C

/*
* SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Unlicense OR CC0-1.0
*/
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "unity.h"
#include <stdlib.h>
#include <stdio.h>
#include <stdint.h>
#include <string.h>
#include <assert.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>
#include <esp_log.h>
#include <esp_attr.h>
#include "esp_flash.h"
#include <esp_partition.h>
#include "nvs_flash.h"
#include "nvs.h"
#include "esp_vfs.h"
#include "esp_vfs_fat.h"
#include "esp_spiffs.h"
#include "esp_heap_caps.h"
#include "esp_flash_encrypt.h"
#include "esp_efuse_table.h"
static const char* TAG = "test_readonly_partition_feature";
#define NUM_OF_READONLY_PARTITIONS 4
const esp_partition_t* readonly_partitions[NUM_OF_READONLY_PARTITIONS];
// Partition names
const char *nvs_partition_name = "nvs_ro";
const char *fatfs_wl_partition_name = "fatfs_ro";
const char *fatfs_raw_partition_name = "fatfs_raw_ro";
const char *spiffs_partition_name = "spiffs_ro";
// Mount paths for partitions
#define FATFS_WL_BASE_PATH "/fatfs_wl"
#define FATFS_RAW_BASE_PATH "/fatfs_raw"
#define SPIFFS_BASE_PATH "/spiffs"
// Handle of the wear levelling library instance
static wl_handle_t s_wl_handle = WL_INVALID_HANDLE;
// Data in each filesystem partition
const char* cmp_string = "This is a file contained in the generated filesystem image on the host and flashed to the ESP device";
#define CMP_STRING_LEN 102 // 101 + '\0'
static void fill_array_of_readonly_data_partitions(void)
{
// This finds read-only partitions defined in the partition table
const esp_partition_t* part_nvs = esp_partition_find_first(ESP_PARTITION_TYPE_DATA,
ESP_PARTITION_SUBTYPE_ANY, nvs_partition_name);
const esp_partition_t* part_fatfs_wl = esp_partition_find_first(ESP_PARTITION_TYPE_DATA,
ESP_PARTITION_SUBTYPE_ANY, fatfs_wl_partition_name);
const esp_partition_t* part_fatfs_raw = esp_partition_find_first(ESP_PARTITION_TYPE_DATA,
ESP_PARTITION_SUBTYPE_ANY, fatfs_raw_partition_name);
const esp_partition_t* part_spiffs = esp_partition_find_first(ESP_PARTITION_TYPE_DATA,
ESP_PARTITION_SUBTYPE_ANY, spiffs_partition_name);
TEST_ASSERT_NOT_NULL(part_nvs); // NULL means partition table set wrong
TEST_ASSERT_NOT_NULL(part_fatfs_wl);
TEST_ASSERT_NOT_NULL(part_fatfs_raw);
TEST_ASSERT_NOT_NULL(part_spiffs);
readonly_partitions[0] = part_nvs;
readonly_partitions[1] = part_fatfs_wl;
readonly_partitions[2] = part_fatfs_raw;
readonly_partitions[3] = part_spiffs;
}
#if CONFIG_IDF_TARGET_ESP32
#define TARGET_CRYPT_CNT_EFUSE ESP_EFUSE_FLASH_CRYPT_CNT
#define TARGET_CRYPT_CNT_WIDTH 7
#else
#define TARGET_CRYPT_CNT_EFUSE ESP_EFUSE_SPI_BOOT_CRYPT_CNT
#define TARGET_CRYPT_CNT_WIDTH 3
#endif
static void example_print_flash_encryption_status(void)
{
uint32_t flash_crypt_cnt = 0;
esp_efuse_read_field_blob(TARGET_CRYPT_CNT_EFUSE, &flash_crypt_cnt, TARGET_CRYPT_CNT_WIDTH);
printf("FLASH_CRYPT_CNT eFuse value is %" PRIu32 "\n", flash_crypt_cnt);
esp_flash_enc_mode_t mode = esp_get_flash_encryption_mode();
if (mode == ESP_FLASH_ENC_MODE_DISABLED) {
printf("Flash encryption feature is disabled\n");
} else {
printf("Flash encryption feature is enabled in %s mode\n",
mode == ESP_FLASH_ENC_MODE_DEVELOPMENT ? "DEVELOPMENT" : "RELEASE");
}
}
void app_main(void)
{
example_print_flash_encryption_status();
fill_array_of_readonly_data_partitions();
unity_run_menu();
}
TEST_CASE("Read-only partition - SPI flash API", "[spi_flash]")
{
esp_err_t err;
char buf[11] = {0};
const char some_data[] = "0123456789";
for (int i = 0; i < NUM_OF_READONLY_PARTITIONS; i++) {
const esp_partition_t *part = readonly_partitions[i];
// Writing to the SPI flash on address overlapping read-only partition shouldn't be possible
// and should return ESP_ERR_NOT_ALLOWED error
err = esp_flash_write(part->flash_chip, some_data, part->address, strlen(some_data));
ESP_LOGD(TAG, "Writing %u bytes to partition %s at 0x%lx, should return %s and returned %s (0x%x)",
strlen(some_data), part->label, part->address, esp_err_to_name(ESP_ERR_NOT_ALLOWED), esp_err_to_name(err), err);
TEST_ASSERT_EQUAL(ESP_ERR_NOT_ALLOWED, err);
// Reading the SPI flash on address overlapping read-only partition should be possible without an error
TEST_ESP_OK(esp_flash_read(part->flash_chip, &buf, part->address, strlen(some_data)));
}
}
TEST_CASE("Read-only partition - Partition API", "[partition]")
{
esp_err_t err;
// Writing to the partition should not be possible and should return ESP_ERR_NOT_ALLOWED error
const char some_data[] = "0123456789";
for (int i = 0; i < NUM_OF_READONLY_PARTITIONS; i++) {
err = esp_partition_write(readonly_partitions[i], 0, some_data, strlen(some_data));
ESP_LOGD(TAG, "esp_partition_write on readonly_partitions[%d] should return %s and returned %s (0x%x)",
i, esp_err_to_name(ESP_ERR_NOT_ALLOWED), esp_err_to_name(err), err);
TEST_ASSERT_EQUAL(ESP_ERR_NOT_ALLOWED, err);
}
// Reading the partition should be possible without an error
char buf[strlen(some_data)];
for (int i = 0; i < NUM_OF_READONLY_PARTITIONS; i++) {
err = esp_partition_read(readonly_partitions[i], 0, buf, sizeof(buf));
TEST_ESP_OK(err);
}
}
TEST_CASE("Read-only partition - NVS API", "[nvs]")
{
nvs_handle_t handle;
esp_err_t err;
err = nvs_flash_init_partition(nvs_partition_name);
TEST_ESP_OK(err);
// NVS partition flagged as read-only should be possible to open in read-only mode
err = nvs_open_from_partition(nvs_partition_name, "storage", NVS_READONLY, &handle);
TEST_ESP_OK(err);
// Read test
int32_t i32_val = 0;
err = nvs_get_i32(handle, "i32_key", &i32_val);
TEST_ESP_OK(err);
TEST_ASSERT_EQUAL(-2147483648, i32_val);
nvs_close(handle);
// NVS partition flagged as read-only shouldn't be possible to open in read-write mode
err = nvs_open_from_partition(nvs_partition_name, "storage", NVS_READWRITE, &handle);
TEST_ASSERT_EQUAL(ESP_ERR_NOT_ALLOWED, err);
nvs_close(handle);
}
void test_c_api_common(const char* base_path)
{
char hello_txt[64];
char new_txt[64];
snprintf(hello_txt, sizeof(hello_txt), "%s%s", base_path, "/hello.txt");
snprintf(new_txt, sizeof(new_txt), "%s%s", base_path, "/new.txt");
FILE *f;
int fd, status;
char buf[CMP_STRING_LEN] = {0};
// Test write mode is not possible
f = fopen(hello_txt, "w");
TEST_ASSERT_NULL(f);
fd = open(hello_txt, O_CREAT|O_WRONLY, 0666);
TEST_ASSERT_EQUAL(-1, fd);
TEST_ASSERT_EQUAL(EROFS, errno);
f = fopen(hello_txt, "w+");
TEST_ASSERT_NULL(f);
fd = open(hello_txt, O_CREAT|O_RDWR, 0666);
TEST_ASSERT_EQUAL(-1, fd);
TEST_ASSERT_EQUAL(EROFS, errno);
f = fopen(hello_txt, "a");
TEST_ASSERT_NULL(f);
fd = open(hello_txt, O_CREAT|O_WRONLY|O_APPEND, 0666);
TEST_ASSERT_EQUAL(-1, fd);
TEST_ASSERT_EQUAL(EROFS, errno);
f = fopen(hello_txt, "a+");
TEST_ASSERT_NULL(f);
fd = open(hello_txt, O_CREAT|O_RDWR|O_APPEND, 0666);
TEST_ASSERT_EQUAL(-1, fd);
TEST_ASSERT_EQUAL(EROFS, errno);
f = fopen(hello_txt, "r+");
TEST_ASSERT_NULL(f);
fd = open(hello_txt, O_RDWR);
TEST_ASSERT_EQUAL(-1, fd);
TEST_ASSERT_EQUAL(EROFS, errno);
fd = creat(new_txt, 0666); // == open(new_txt, O_WRONLY|O_CREAT|O_TRUNC, 0666)
TEST_ASSERT_EQUAL(-1, fd);
TEST_ASSERT_EQUAL(EROFS, errno);
status = link(hello_txt, new_txt);
TEST_ASSERT_EQUAL(-1, status);
TEST_ASSERT_EQUAL(EROFS, errno);
status = rename(hello_txt, new_txt);
TEST_ASSERT_EQUAL(-1, status);
TEST_ASSERT_EQUAL(EROFS, errno);
status = unlink(hello_txt);
TEST_ASSERT_EQUAL(-1, status);
TEST_ASSERT_EQUAL(EROFS, errno);
status = truncate(hello_txt, 10);
TEST_ASSERT_EQUAL(-1, status);
TEST_ASSERT_EQUAL(EROFS, errno);
// Test read is still possible
fd = open(hello_txt, O_RDONLY);
TEST_ASSERT_GREATER_THAN(0, fd);
status = ftruncate(fd, 10);
TEST_ASSERT_EQUAL(-1, status);
TEST_ASSERT_EQUAL(EROFS, errno);
close(fd);
f = fopen(hello_txt, "r");
TEST_ASSERT_NOT_NULL(f);
fread(buf, 1, sizeof(buf) - 1, f);
ESP_LOGD(TAG, "Read from file: %s", buf);
TEST_ASSERT_EQUAL(0, strcmp(buf, cmp_string));
memset(buf, 0, sizeof(buf));
char str[] = "Should not be written";
fseek(f, 0, SEEK_SET);
status = fwrite(str, 1, sizeof(str), f); // Writing should do nothing
TEST_ASSERT_EQUAL(0, status);
TEST_ASSERT_EQUAL(EBADF, errno);
fread(buf, 1, sizeof(buf) - 1, f);
ESP_LOGD(TAG, "Read from file: %s", buf);
TEST_ASSERT_EQUAL(0, strcmp(buf, cmp_string)); // Test if the file content is still the same
fclose(f);
}
TEST_CASE("Read-only partition - C file I/O API (using FATFS WL)", "[vfs][fatfs]")
{
const esp_vfs_fat_mount_config_t mount_config = {
.max_files = 4,
.format_if_mount_failed = false,
.allocation_unit_size = CONFIG_WL_SECTOR_SIZE
};
esp_err_t err;
int status;
err = esp_vfs_fat_spiflash_mount_rw_wl(FATFS_WL_BASE_PATH, fatfs_wl_partition_name, &mount_config, &s_wl_handle);
TEST_ESP_OK(err);
// FATFS WL itself is read-write capable, but we are restricting it to read-only mode via esp_partition layer
// Opening a file in a write mode on read-only partition is checked in vfs
test_c_api_common(FATFS_WL_BASE_PATH);
// Test directories
DIR *dir;
status = mkdir(FATFS_WL_BASE_PATH "/dir1", S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH);
TEST_ASSERT_EQUAL(-1, status);
TEST_ASSERT_EQUAL(EROFS, errno);
dir = opendir(FATFS_WL_BASE_PATH "/dir1");
TEST_ASSERT_NULL(dir);
status = rmdir(FATFS_WL_BASE_PATH "/dir");
TEST_ASSERT_EQUAL(-1, status);
TEST_ASSERT_EQUAL(EROFS, errno);
dir = opendir(FATFS_WL_BASE_PATH "/dir");
TEST_ASSERT_NOT_NULL(dir);
closedir(dir);
TEST_ESP_OK(esp_vfs_fat_spiflash_unmount_rw_wl(FATFS_WL_BASE_PATH, s_wl_handle));
}
TEST_CASE("Read-only partition - C file I/O API (using FATFS RAW)", "[vfs][fatfs]")
{
const esp_vfs_fat_mount_config_t mount_config = {
.max_files = 4,
.format_if_mount_failed = false,
.allocation_unit_size = CONFIG_WL_SECTOR_SIZE
};
esp_err_t err;
int status;
err = esp_vfs_fat_spiflash_mount_ro(FATFS_RAW_BASE_PATH, fatfs_raw_partition_name, &mount_config);
TEST_ESP_OK(err);
// FATFS RAW is read-only itself, but esp_parition read-only adds another layer
// Opening a file in a write mode on read-only partition is checked in vfs
test_c_api_common(FATFS_RAW_BASE_PATH);
// Test directories
DIR *dir;
status = mkdir(FATFS_RAW_BASE_PATH "/dir1", S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH);
TEST_ASSERT_EQUAL(-1, status);
TEST_ASSERT_EQUAL(EROFS, errno);
dir = opendir(FATFS_RAW_BASE_PATH "/dir1");
TEST_ASSERT_NULL(dir);
status = rmdir(FATFS_RAW_BASE_PATH "/dir");
TEST_ASSERT_EQUAL(-1, status);
TEST_ASSERT_EQUAL(EROFS, errno);
dir = opendir(FATFS_RAW_BASE_PATH "/dir");
TEST_ASSERT_NOT_NULL(dir);
closedir(dir);
TEST_ESP_OK(esp_vfs_fat_spiflash_unmount_ro(FATFS_RAW_BASE_PATH, fatfs_raw_partition_name));
}
TEST_CASE("Read-only partition - C file I/O API (using SPIFFS)", "[vfs][spiffs]")
{
esp_vfs_spiffs_conf_t conf = {
.base_path = SPIFFS_BASE_PATH,
.partition_label = spiffs_partition_name,
.max_files = 5,
.format_if_mount_failed = false
};
esp_err_t err;
err = esp_vfs_spiffs_register(&conf);
TEST_ESP_OK(err);
// SPIFFS is read-write capable, but we are restricting it to read-only mode via esp_partition layer
test_c_api_common(SPIFFS_BASE_PATH);
// SPIFFS doesn't support directories
TEST_ESP_OK(esp_vfs_spiffs_unregister(spiffs_partition_name));
}