Skip to content

feat: Add a component for parsing external partition tables (IEC-333) #529

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 4 commits into
base: master
Choose a base branch
from

Conversation

adokitkat
Copy link
Collaborator

@adokitkat adokitkat commented Jul 17, 2025

Checklist

  • Component contains License
  • Component contains README.md
  • Component contains idf_component.yml file with url field defined
  • Component was added to upload job
  • Component was added to build job
  • Optional: Component contains unit tests
  • CI passing

Description

This MR adds a component which can parse and generate external partition tables. Right now it only supports MBR. Adding support for another partition table like GPT is possible but not planned for now.

esp_err_t esp_mbr_parse(void* mbr_buf, esp_ext_part_list_t* part_list, esp_mbr_parse_extra_args_t* extra_args);

esp_err_t esp_mbr_generate(mbr_t* mbr, esp_ext_part_list_t* part_list, esp_mbr_generate_extra_args_t* extra_args);

Parsing a partition table outputs esp_ext_part_list_t structure filled with esp_ext_part_list_item_t items.

typedef struct {
    uint32_t data[1];
    esp_ext_part_signature_type_t type;
} esp_ext_part_list_signature_t;

typedef struct {
    uint64_t address; /*!< Start address in bytes */
    uint64_t size; /*!< Size in bytes */
    uint32_t extra; /*!< Extra information (e.g. LittleFS block size stored in CHS hack, etc.) */
    char* label;
    esp_ext_part_flags_t flags; /*!< Flags for the partition */
    esp_ext_part_type_known_t type; /*!< Known partition type for this component */
} esp_ext_part_t;

typedef struct esp_ext_part_list_item_ {
    esp_ext_part_t info;
    SLIST_ENTRY(esp_ext_part_list_item_) next;
} esp_ext_part_list_item_t;

typedef struct {
    esp_ext_part_list_signature_t signature; /*!< Disk signature or identifier */
    SLIST_HEAD(esp_ext_part_list_head_, esp_ext_part_list_item_) head; /*!< Head of the partition list */
    esp_ext_part_list_flags_t flags; /*!< Flags for the partition list */
    esp_ext_part_sector_size_t sector_size; /*!< Sector size (storage medium property) */
} esp_ext_part_list_t;

When generating MBR, user can decide on sector size (512B, 4KiB) and partition alignment (4KiB, 1MiB). The defaults are 512B sector size and 1MiB partition alignment.

Note: Generating MBR doesn't mean formatting any partition on given sector span, however helper function could be created which would combine this behavior - each partition type has it's own formatting function (FATFS formats differently than LittleFS, etc.).

These are recognized types:

typedef enum {
    ESP_EXT_PART_TYPE_UNKNOWN = 0,
    ESP_EXT_PART_TYPE_FAT_ANY, /*!< FAT partition (any type) */
    ESP_EXT_PART_TYPE_FAT12,
    ESP_EXT_PART_TYPE_FAT16, /*!< FAT16 with LBA addressing */
    ESP_EXT_PART_TYPE_FAT32, /*!< FAT32 with LBA addressing */
    ESP_EXT_PART_TYPE_LITTLEFS, /*!< Possibly LittleFS (MBR CHS field => LittleFS block size hack) */
// Note: The following types are not supported, but we can return a type for them
    ESP_EXT_PART_TYPE_LINUX_ANY, /*!< Linux partition (any type) */
    ESP_EXT_PART_TYPE_EXFAT_OR_NTFS, /*!< Not supported, but we can return a type for it */
    ESP_EXT_PART_TYPE_GPT_PROTECTIVE_MBR, /*!< Not supported, but we can return a type for it */
} esp_ext_part_type_known_t;

Future TODOs and could haves:

  • FATFS and this component is right now not coupled - it would be nice to add a function to FATFS or VFS, which could return FAT type of a partition, so it ca be used in this component.
  • Add support for BDL and generic BDL partition components, also probably PR to esp_littlefs repo
  • Support formatting along with partitioning?

@CLAassistant
Copy link

CLAassistant commented Jul 17, 2025

CLA assistant check
All committers have signed the CLA.

@adokitkat adokitkat force-pushed the feat/add_external_partition_table_parser_component branch from 0d3df95 to 1589048 Compare July 17, 2025 14:20
@github-actions github-actions bot changed the title feat: Add a component for parsing external partition tables feat: Add a component for parsing external partition tables (IEC-333) Jul 17, 2025
@espressif-bot espressif-bot added Status: Reviewing Type: Feature Request Feature request for a component and removed Status: Opened labels Jul 18, 2025
@adokitkat adokitkat force-pushed the feat/add_external_partition_table_parser_component branch from 1589048 to c13b7e6 Compare July 18, 2025 10:59
@adokitkat adokitkat marked this pull request as ready for review July 18, 2025 11:59
@adokitkat adokitkat force-pushed the feat/add_external_partition_table_parser_component branch from c13b7e6 to 9a5f764 Compare July 18, 2025 12:56
@adokitkat adokitkat force-pushed the feat/add_external_partition_table_parser_component branch 3 times, most recently from dbf974f to 2c836ab Compare July 18, 2025 13:45
@adokitkat
Copy link
Collaborator Author

@igrr @pacucha42 please take a look

@adokitkat adokitkat marked this pull request as draft July 22, 2025 19:25
@adokitkat adokitkat force-pushed the feat/add_external_partition_table_parser_component branch from a3c2056 to 8572c6f Compare July 23, 2025 11:27
case ESP_EXT_PART_TYPE_FAT16:
case ESP_EXT_PART_TYPE_FAT32:
// Set CHS values based on LBA start and end
esp_mbr_lba_to_chs_arr(partition->chs_start, lba_start);

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please, provide an explanation why you fill the CHS fields in for LBA file-system types (as discussed 1:1, this is given mostly by the FatFS support of FDISK functionality). Tbh, it would be great to know the reasons behind, otherwise this operation looks useless

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I do it for FAT partitions to stay as close to FATFS library behavior ESP-IDF is using. FATFS library can format partitions and generate MBR as well and it does fill CHS fields. However technically it isn't needed because modern systems should ignore it in favor of LBA.

// Set default arguments for MBR generation
esp_mbr_generate_extra_args_t args = {
.sector_size = part_list->sector_size != ESP_EXT_PART_SECTOR_SIZE_UNKNOWN ? part_list->sector_size : ESP_EXT_PART_SECTOR_SIZE_512B, // Default sector size
.alignment = ESP_EXT_PART_ALIGN_1MiB, // Default alignment
Copy link

@pacucha42 pacucha42 Jul 23, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do we enforce 1MB partition alignment? Isn't this option a good candidate for a parameter setting? If not, why don't we use 4kB which would fit much better into usual IoT storage device requirements?

Sorry, I have missed ESP_EXT_PART_ALIGN_1MiB is just a default. Other questions are valid. Thanks

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SD cards present themselves with minimal read/write size of 512B, which is 1 sector. However internally NAND page sizes can be from 4kB to 64kB. If the accessed data is misaligned on those boundaries, it can lead to slower performance. This sector and page sizes are also different for different storage media.

I used 1MiB as a default because it is dividable by all of those sizes and should be always properly aligned, plus this component is to be used with SD cards or maybe USB drives whose capacities are in from tens to thousands of gigabytes range. If this component is to be used with a media of smaller capacity, 4kiB still can be chosen.

@pacucha42
Copy link

pacucha42 commented Jul 23, 2025

The code base looks good to me, however, I left few questions here and there. Thanks for you efforts @adokitkat !

Generally, it would be great to provide more explanations and in-code comments.

@adokitkat adokitkat force-pushed the feat/add_external_partition_table_parser_component branch 2 times, most recently from 6f26de1 to 99bc9c1 Compare July 23, 2025 15:46
@adokitkat adokitkat force-pushed the feat/add_external_partition_table_parser_component branch 3 times, most recently from bb471c0 to 7fe043a Compare July 29, 2025 15:08
@adokitkat adokitkat force-pushed the feat/add_external_partition_table_parser_component branch 8 times, most recently from 9c1a86d to b901d2a Compare July 30, 2025 16:05
* - ESP_ERR_INVALID_ARG: dst or src is NULL.
* - ESP_ERR_NO_MEM: Memory allocation failed.
*/
esp_err_t esp_ext_part_list_deep_copy(esp_ext_part_list_t* dst, esp_ext_part_list_t* src);

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just wondering: what would be use-case for such a function?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To be honest I thought I need it for something in an example code but then I resolved the problem another way and the function stayed here. I just thought copying the structure is non-trivial due to pointers and malloc'd memory so I created this function (i.e. the logic is hidden here and if there were changes in the part list struct users wouldn't need to change anything in their code). However right now I am not sure if this functionality needs to be in the public API (if and why it is something users would need often), so I can remove it.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One use case may be if the user wanted to load the MBR and make changes to it, while also keeping the original part list created from the MBR as "a reference" / a backup, so the changes would be made to a copy.

switch (part_list->signature.type) {
case ESP_EXT_PART_LIST_SIGNATURE_MBR:
uint32_t out = (uint32_t) part_list->signature.data[0];
memcpy(signature, &out, sizeof(uint32_t));

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this line necessary?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I used memcpy in case void* signature points to to an array or something for some reason and out is just a temporary variable so the code is cleaner (will probably be optimized out anyway?).

@adokitkat adokitkat force-pushed the feat/add_external_partition_table_parser_component branch from b901d2a to ed56bd8 Compare July 31, 2025 13:25
@pacucha42
Copy link

LGTM, though the code still needs polishing. I left few comments, mostly minor things. Thanks @adokitkat

@adokitkat adokitkat force-pushed the feat/add_external_partition_table_parser_component branch 4 times, most recently from 867ba49 to 1353d2d Compare August 1, 2025 14:41
return ESP_ERR_INVALID_ARG;
}

memcpy(dst, src, sizeof(esp_ext_part_list_t)); // Copy the structure

Check warning

Code scanning / clang-tidy

Call to function 'memcpy' is insecure as it does not provide security checks introduced in the C11 standard. Replace with analogous functions that support length arguments or provides boundary checks such as 'memcpy_s' in case of C11 [clang-analyzer-security.insecureAPI.DeprecatedOrUnsafeBufferHandling] Warning

Call to function 'memcpy' is insecure as it does not provide security checks introduced in the C11 standard. Replace with analogous functions that support length arguments or provides boundary checks such as 'memcpy_s' in case of C11 [clang-analyzer-security.insecureAPI.DeprecatedOrUnsafeBufferHandling]
}

memcpy(dst, src, sizeof(esp_ext_part_list_t)); // Copy the structure
memset(&dst->head, 0, sizeof(dst->head)); // Reset the head of the destination list

Check warning

Code scanning / clang-tidy

Call to function 'memset' is insecure as it does not provide security checks introduced in the C11 standard. Replace with analogous functions that support length arguments or provides boundary checks such as 'memset_s' in case of C11 [clang-analyzer-security.insecureAPI.DeprecatedOrUnsafeBufferHandling] Warning

Call to function 'memset' is insecure as it does not provide security checks introduced in the C11 standard. Replace with analogous functions that support length arguments or provides boundary checks such as 'memset_s' in case of C11 [clang-analyzer-security.insecureAPI.DeprecatedOrUnsafeBufferHandling]
chs_bytes[2] = cylinder & 0xFF;

if (chs != NULL) {
memcpy(chs, chs_bytes, 3);

Check warning

Code scanning / clang-tidy

Call to function 'memcpy' is insecure as it does not provide security checks introduced in the C11 standard. Replace with analogous functions that support length arguments or provides boundary checks such as 'memcpy_s' in case of C11 [clang-analyzer-security.insecureAPI.DeprecatedOrUnsafeBufferHandling] Warning

Call to function 'memcpy' is insecure as it does not provide security checks introduced in the C11 standard. Replace with analogous functions that support length arguments or provides boundary checks such as 'memcpy_s' in case of C11 [clang-analyzer-security.insecureAPI.DeprecatedOrUnsafeBufferHandling]

// Check if the partition entry is empty and if so, skip it
if (item->info.type == ESP_EXT_PART_TYPE_NONE) {
memset(partition, 0, sizeof(mbr_partition_t));

Check warning

Code scanning / clang-tidy

Call to function 'memset' is insecure as it does not provide security checks introduced in the C11 standard. Replace with analogous functions that support length arguments or provides boundary checks such as 'memset_s' in case of C11 [clang-analyzer-security.insecureAPI.DeprecatedOrUnsafeBufferHandling] Warning

Call to function 'memset' is insecure as it does not provide security checks introduced in the C11 standard. Replace with analogous functions that support length arguments or provides boundary checks such as 'memset_s' in case of C11 [clang-analyzer-security.insecureAPI.DeprecatedOrUnsafeBufferHandling]
}
if (gap_index != i) {
// Move the partition to the next available index
memcpy(&mbr->partition_table[gap_index], partition, sizeof(mbr_partition_t));

Check warning

Code scanning / clang-tidy

Call to function 'memcpy' is insecure as it does not provide security checks introduced in the C11 standard. Replace with analogous functions that support length arguments or provides boundary checks such as 'memcpy_s' in case of C11 [clang-analyzer-security.insecureAPI.DeprecatedOrUnsafeBufferHandling] Warning

Call to function 'memcpy' is insecure as it does not provide security checks introduced in the C11 standard. Replace with analogous functions that support length arguments or provides boundary checks such as 'memcpy_s' in case of C11 [clang-analyzer-security.insecureAPI.DeprecatedOrUnsafeBufferHandling]
if (gap_index != i) {
// Move the partition to the next available index
memcpy(&mbr->partition_table[gap_index], partition, sizeof(mbr_partition_t));
memset(partition, 0, sizeof(mbr_partition_t)); // Clear the old entry

Check warning

Code scanning / clang-tidy

Call to function 'memset' is insecure as it does not provide security checks introduced in the C11 standard. Replace with analogous functions that support length arguments or provides boundary checks such as 'memset_s' in case of C11 [clang-analyzer-security.insecureAPI.DeprecatedOrUnsafeBufferHandling] Warning

Call to function 'memset' is insecure as it does not provide security checks introduced in the C11 standard. Replace with analogous functions that support length arguments or provides boundary checks such as 'memset_s' in case of C11 [clang-analyzer-security.insecureAPI.DeprecatedOrUnsafeBufferHandling]
@adokitkat adokitkat force-pushed the feat/add_external_partition_table_parser_component branch from 3c3fd55 to 322bf92 Compare August 7, 2025 16:10
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Status: Reviewing Type: Feature Request Feature request for a component
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants