Skip to content

mtd: an alias is a partition others cover, not one that holds another - #265

Merged
dimerr merged 2 commits into
masterfrom
mtd-nested-partitions
Nov 4, 2026
Merged

dimerr merged 2 commits into
masterfrom
mtd-nested-partitions

Conversation

@dimerr

@dimerr dimerr commented Nov 4, 2026 •

Copy link
Copy Markdown
Collaborator

mtd_mark_aliases() dropped any partition that contained another whole. That is right for Novatek's all, but wrong when partitions nest without covering their parent.

What went wrong

Reported on a hi3516cv6xx camera with 8 MB of NOR:

mtd0: 00040000 00010000 "u-boot"
mtd1: 00010000 00010000 "env"
mtd2: 007b0000 00010000 "firmware"
mtd3: 00200000 00010000 "kernel"
mtd4: 005b0000 00010000 "rootfs"
mtd5: 00130000 00010000 "rootfs_data"

firmware is kernel + rootfs, and rootfs_data is the tail of rootfs. The old rule dropped firmware, which is correct, and also rootfs for holding rootfs_data, which leaves 0x250000-0x6d0000 unreachable. restore --raw refused with no partition covers 0x250000-0x6d0000.

Backups are affected, and this dates from #235. On any kernel that reports offsets, backup and upload --backup go through the same filter, so on this layout they silently left out the squashfs. Existing backups from such cameras are likely incomplete. Whether a camera is affected can be read from /proc/mtd: a partition that holds another but is not fully covered by the partitions inside it.

The fix

  • A partition is an alias only if smaller partitions inside it reach from its start to its end. firmware goes, rootfs stays.

  • raw_image_plan() takes the larger of two partitions starting at the same offset, so a parent that remains is written through instead of being taken for an overlap with its child.

  • The flash size is now the union of the partitions' ranges rather than the sum of their sizes, for the report, restore and upgrade. A partition with no known offset can only be added on, as before.

all on Novatek is still dropped, since its partitions cover it. An all with an unpartitioned hole in it is now kept rather than dropped, so a backup holds that flash twice instead of losing the hole.

Why the flash size had to change

Keeping a nested parent makes the sum larger than the chip. Beyond the report saying 9M for 8 MB, upgrade takes the end of the flash from that sum. With rootfs_data inside rootfs and no firmware parent, the settings region ran past the end, wrapped into rootfs_data, and erased the tail of the rootfs just written, while reporting success. This came from this PR's first commit, not from master.

Testing

Run on an emulated hi3516ev300 (kernel 4.9, 8 MB NOR) with the layout above, comparing the flash file from the host afterwards:

Case Before After
restore --raw, offsets from sysfs refused flash matches the image
restore --raw, offsets from the command line not run flash matches the image
restore --raw, offsets from the boot log not run flash matches the image
backup size 0x380296 0x9302d7

upgrade --wipe-settings with a full nor8m bundle, 7 MB of images:

Layout master First commit of this PR Now
With firmware parent refused, no room refused, offset error refused, offset error
rootfs_data in rootfs, no parent refused, no room 8 rootfs blocks erased kernel and rootfs intact

The report for the first layout says 3M on master, and 8M now.

The 21 scenarios from #264 also pass. The native test binaries pass, and restoring the old rule fails mtd_alias_test.

Not tested: real hardware, and the bytes of the new backup: it was checked by size only.

Still open

On this layout rootfs_data is in the backup twice, and plain restore refuses the backup with Offset algorithm error, leaving the flash unchanged. It did the same before this change. So these backups hold everything, but restore cannot put them back yet. That needs its own fix: placing blocks by real offset, as --raw does.

upgrade still refuses the layout with a firmware parent, without writing, because map_old_new_mtd() stops at the slot the dropped parent leaves. On the way it logs Keeping (0 KB) for that slot and prints bootargs with an empty partition and a shifted root=, which it never writes. The same fix would cover this.

mtd_mark_aliases() dropped any partition that contained another whole.
That is right for Novatek's "all", and wrong for a layout that nests
partitions without covering the parent: with firmware 0x50000-0x800000
split into kernel and rootfs, and rootfs_data as the tail of rootfs, it
dropped firmware and also rootfs for holding rootfs_data.

Since #235, on any kernel that reports offsets, that took
0x250000-0x6d0000 out of everything enumerated. `backup` and `upload
--backup` left out the squashfs without a word: 0x380296 bytes for an 8 MB
flash in an emulated run of that layout. `restore --raw` refused with
"no partition covers 0x250000-0x6d0000", which is how this was found, on a
hi3516cv6xx camera.

A partition is now an alias only if smaller ones inside it reach from its
start to its end. firmware goes, rootfs stays. raw_image_plan() takes the
larger of two partitions starting together, so a parent that remains is
written through rather than taken for an overlap with its child.

Run on an emulated hi3516ev300 with the layout above, placing partitions
from sysfs, the command line and the boot log: restore --raw leaves the
flash equal to the image, and the backup comes to 0x9302d7 bytes.
rootfs_data is in that backup twice, and plain `restore` refuses it with
"Offset algorithm error" and the flash unchanged, as it did before.
@qodo-free-for-open-source-projects

Copy link
Copy Markdown

PR Summary by Qodo

Preserve nested MTD partitions unless their children fully cover them

🐞 Bug fix 🧪 Tests 🕐 20-40 Minutes

Grey Divider

AI Description

• Exclude MTD aliases only when smaller partitions cover them end to end, preserving otherwise
 missing backup data.
• Prefer larger partitions at the same offset when planning raw restores.
• Add nested-layout tests; plain restore still cannot replay backups containing overlapping
 partitions.
Diagram

graph TD
  M["MTD layout"] --> E["MTD enumeration"] --> A["Coverage filter"] --> K["Kept partitions"] --> B["Backup data"]
  K --> P["Raw restore plan"] --> F["Flash writes"]
Loading
High-Level Assessment

The coverage-based alias rule addresses the missing-data case without discarding fully redundant partitions, and the raw planner's same-offset ordering handles retained parents. A sorted interval-union implementation was considered, but adds machinery for a small partition set without changing the behavior. Plain restore's inability to replay overlapping backup entries remains a separate problem.

Files changed (5) +89 / -12

Bug fix (2) +21 / -8
mtd_alias.cRequire complete child coverage before marking an alias +13/-6

Require complete child coverage before marking an alias

• Replaces the any-contained-partition rule with an offset-aware coverage walk. Parents with gaps remain available for backups; the fallback for partitions without known offsets is unchanged.

src/mtd_alias.c

raw_image.cPrefer larger partitions at equal offsets +8/-2

Prefer larger partitions at equal offsets

• Changes raw restore planning order so a retained parent is selected before a child starting at the same offset. This prevents the child from making its parent appear to overlap the selected plan.

src/raw_image.c

Tests (2) +62 / -1
mtd_alias_test.cTest nested and partially covered MTD layouts +33/-0

Test nested and partially covered MTD layouts

• Adds regression cases for the reported camera layout, covered parents of parents, parents with holes, and partitions listed out of offset order.

src/mtd_alias_test.c

raw_image_test.cTest raw restore planning with nested partitions +29/-1

Test raw restore planning with nested partitions

• Verifies that a child inside rootfs is not written twice and that a retained parent is chosen regardless of listing order. Updates one gap test's expected diagnostic to reflect the new ordering.

src/raw_image_test.c

Documentation (1) +6 / -3
mtd_alias.hClarify the alias-detection contract +6/-3

Clarify the alias-detection contract

• Documents end-to-end coverage as the criterion when offsets are known, using firmware and rootfs_data as examples. Clarifies the unchanged size-based fallback.

src/mtd_alias.h

@qodo-free-for-open-source-projects

qodo-free-for-open-source-projects Bot commented Nov 4, 2026 •

Copy link
Copy Markdown

Code Review by Qodo

🐞 Bugs (0) 📘 Rule violations (0) 📎 Requirement gaps (0) 🎨 UX issues (0) 🔗 Cross-repo conflicts (0) 📜 Skill insights (0)

Grey Divider


Remediation recommended

1. Camera reports show inflated flash size ✓ Resolved
Description
get_mtd_info() still calculates rom[].size by summing every retained partition, while
mtd_mark_aliases() now retains a partially covered parent alongside its children. On the described
8 MiB layout, the retained partitions total 9.1875 MiB, so reports and backup YAML label the flash
as 9M.

Grey Divider

Tip of the day
💡 Did you know, you can commit Qodo's fix in one click with committable suggestions (GitHub & GitLab)

More tips ↗ | Customize Qodo ↗ | Qodo docs ↗

Grey Divider

Qodo Logo

Partitions can nest now that a partially covered parent is kept, so the sum
stopped being the flash. The report said 9M for an 8 MB chip, as Qodo found
on this PR; it had said 3M before, with the parent dropped.

The sum is also where upgrade takes the end of the flash from, which was
worse than the report. With rootfs_data inside rootfs and no firmware
parent, the settings region ran 0x130000 past the end, those offsets mapped
into rootfs_data, which is the tail of the flash, and they erased what had
just been written there. In an emulated run of a full nor8m bundle, 8
blocks of the new rootfs at 0x6d0000-0x750000 came out as 0xff, with the
upgrade reporting success. Master refused that layout, so this was new in
the previous commit.

enum_mtd_info() now works out the union of the ranges it keeps, for the
report, restore and upgrade alike. A partition with no known offset can
only be added on, as before. The same run writes the rootfs intact.
@dimerr
dimerr merged commit e816803 into master Nov 4, 2026
5 checks passed
@dimerr
dimerr deleted the mtd-nested-partitions branch November 4, 2026 16:44
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant