Skip to content

restore: write a plain image of the whole NOR with --raw - #264

Merged
dimerr merged 2 commits into
masterfrom
restore-raw
Nov 4, 2026
Merged

dimerr merged 2 commits into
masterfrom
restore-raw

Conversation

@dimerr

@dimerr dimerr commented Nov 4, 2026 •

Copy link
Copy Markdown
Collaborator

restore and upgrade only take ipctool's own containers, so a dump from a programmer or dd could not be put back without wrapping it by hand. restore --raw <file> writes a plain image of the whole NOR flash.

What it does

It cuts the image along the partitions that really lie on the flash and writes each to its device through do_flash(), so blocks are still read back as they are written and --skip-env still works. The file is mapped rather than read, as an upgrade bundle is, so a dump in /tmp is not in RAM twice.

An image describes nothing and carries no checksums, so fitting this flash exactly is what makes it safe to write. These checks run before anything is stopped, and --force skips only the question, not them:

  • NOR only: a raw NAND image has no answer for bad blocks or UBI
  • every partition's position known
  • partitions that cover the image exactly once: a partial overlap or a gap is refused, and one lying wholly inside the others (all, a second name) is left out
  • one erase size, and partitions that are whole erase blocks
  • image size equal to what the partitions cover

The image's SHA1 and the partition map are printed before the prompt. Size and SHA1 are checked again after every other process is stopped and before anything is erased, because the mapping pins pages, not the file's contents. If either changed, the camera reboots with the flash untouched.

Where partition offsets come from

/sys/class/mtd/mtdN/offset exists only from 4.4. Refusing older kernels would remove --raw from every 3.x camera, so mtd_flash_offset() falls back to:

  1. mtdparts= on the kernel command line
  2. the 0x%012llx-0x%012llx : "name" line the kernel logs per partition, which also covers tables built into the kernel, and is the same format in the 3.0, 3.4 and 3.10 sources checked

A partition is matched by name and size, and not at all if two places claim it. If nothing can place a partition, --raw refuses and says to reboot and run it again while the log still has the lines.

This changes more than --raw: partitions are enumerated through this lookup, so on 3.x kernels alias detection for backup and the report now uses real offsets rather than comparing sizes. The kernel log is also read on those kernels when the command line does not settle it.

Testing

Run on an emulated hi3516ev300 (kernel 4.9, 8 MB NOR), comparing the flash file from the host afterwards. To reach each source, PID 1 left /sys unmounted, bound an empty file over /proc/cmdline, and flooded /dev/kmsg to rotate the boot log away.

Offsets from Case Result
sysfs Full write flash matches the image
sysfs --skip-env with a valid env matches the image, env block preserved
sysfs Overlapping all first or last flash matches the image
sysfs Device numbers out of flash order flash matches the image
command line Full write, all first flash matches the image
boot log Full write, all first, out of order flash matches the image
boot log --skip-env matches the image, env block preserved
none Log rotated away refused, flash unchanged
sysfs, command line, boot log, none Overlap whose sizes add up to the image refused, flash unchanged
sysfs Short image, declined prompt, upgrade --raw refused, flash unchanged
sysfs File changed or truncated after the prompt camera reboots, flash unchanged

raw_image_test covers the planning and mtd_alias_test the two parsers, including torn log lines and ambiguous names.

Not tested: real hardware, booting from a written image (the images were random data), and a real 3.x kernel. The fallbacks ran on 4.9 with sysfs hidden. Their log parser is checked against the format string in the 3.x sources, not against a captured 3.x boot log.

Limits

  • Like restore, it does not unlock the flash, so write-protected regions could stop it partway through. The camera then reboots with a partly written flash.
  • A partition that contains another is always dropped, so boot plus all with nothing else is refused as not covering the image, even though all alone could carry it.
  • A file edited on an NFS server during the write is not caught: the second check runs just before erasing.

Possibly related, not changed here

From reading the code, plain restore may misplace data on Anjoy boards: enum_mtd_info() lists their partitions in flash order, but map_old_new_mtd() walks part[] by device number. This is untested. --raw now places by real offset, and its out-of-order case passes in the emulator.

restore and upgrade only took ipctool's own containers, so a dump from a
programmer or dd could not be put back without wrapping it by hand. Given
a raw file, restore stopped with "Broken backup" only after it had freed
resources and asked to proceed.

--raw cuts the image along the camera's partitions and hands them to the
same do_flash() path, so blocks are still read back as they are written
and --skip-env still works. The file is mapped, not read, as an upgrade
bundle is: a dump in /tmp would otherwise be in RAM twice.

An image says nothing about itself, so fitting this flash exactly is what
makes it safe to write. All of that is settled before anything is stopped
and --force does not override it: NOR only, device numbers consecutive and
in flash order, one erase size, sysfs offsets contiguous where the kernel
has them, and the size equal to what the partitions cover. Device order
matters because do_flash() writes by device number and places blocks by
adding up sizes, and a partition enumeration could not open leaves a hole
in that numbering. An "all" alias at mtd0 is refused for the same reason.
The SHA1 is printed for comparing by hand, there being none to check.

The checks are in raw_image.c so they can be tested on a host. Run on an
emulated hi3516ev300 with an 8 MB NOR, comparing the flash file afterwards
from the host: a full write matches the image, --skip-env keeps a valid
env block, an "all" partition last is written through, and a short image,
"all" first, a declined prompt and upgrade --raw leave the flash as it was.
@qodo-free-for-open-source-projects

Copy link
Copy Markdown

PR Summary by Qodo

Restore whole-NOR images with restore --raw

✨ Enhancement 🧪 Tests 📝 Documentation 🕐 40+ Minutes

Grey Divider

AI Description

• Add restore --raw to write programmer or dd images through the existing flash path.
• Reject incompatible NOR layouts and image sizes before stopping services; preserve --skip-env
 behavior.
• Document the safety checks and add host-side validation tests to CI.
Diagram

graph TD
  CLI["restore --raw"] --> Enum["MTD discovery"] --> Check{"Image fits?"} -->|yes| Map["Mapped image"] --> Prompt["Prompt or force"] --> Flash["Shared do_flash"] --> Nor["NOR partitions"]
  Check -->|no| Stop["Abort safely"]
Loading
High-Level Assessment

The following are alternative approaches to this PR:

1. Wrap raw images as backup containers
  • ➕ Could use the existing backup-restore entry point without a separate raw mode.
  • ➖ Requires conversion before restore.
  • ➖ Would need care to keep image-fit checks ahead of service shutdown and avoid another in-memory copy.

Recommendation: Keep the dedicated raw path: it validates the device-specific layout before disruption while reusing do_flash() for writing. Review the remaining operational risk of a write-protected region causing a partial restore; the PR does not change flash unlocking.

Files changed (10) +369 / -9

Enhancement (3) +222 / -0
backup.cRoute raw images through validated restore +130/-0

Route raw images through validated restore

• Adds 'restore --raw' handling, gathers partition metadata, validates and maps the image, and prints its SHA1 and partition map before confirmation. Slices the image for the existing 'do_flash()' path, retaining '--skip-env', and rejects 'upgrade --raw'.

src/backup.c

raw_image.cValidate exact raw-image-to-partition fit +59/-0

Validate exact raw-image-to-partition fit

• Rejects empty or non-NOR layouts, device-order gaps, inconsistent erase geometry, known offset gaps, and image sizes that differ from partition coverage.

src/raw_image.c

raw_image.hDefine testable raw partition metadata +33/-0

Define testable raw partition metadata

• Introduces the partition description and validation interface used by raw restore and its host-side tests.

src/raw_image.h

Refactor (2) +17 / -8
mtd.cExpose MTD flash-offset lookup +15/-8

Expose MTD flash-offset lookup

• Extracts sysfs offset reading from partition enumeration so raw restore can use the same offset lookup.

src/mtd.c

mtd.hDeclare shared flash-offset lookup +2/-0

Declare shared flash-offset lookup

• Publishes 'mtd_flash_offset()' for callers that need the kernel-reported partition start.

src/mtd.h

Documentation (2) +18 / -0
README.mdDocument whole-NOR raw restore +15/-0

Document whole-NOR raw restore

• Adds command usage and explains the exact-size and partition-order requirements, manual SHA1 comparison, and NAND refusal.

README.md

main.cShow the raw restore option in CLI help +3/-0

Show the raw restore option in CLI help

• Describes '--raw' as an exact-size, whole-NOR image rather than an ipctool backup.

src/main.c

Other (3) +112 / -1
pr-build-check.ymlRun raw-image validation tests in CI +2/-1

Run raw-image validation tests in CI

• Builds and executes 'raw_image_test' alongside the existing native test binaries.

.github/workflows/pr-build-check.yml

CMakeLists.txtBuild the raw-image module and host test +6/-0

Build the raw-image module and host test

• Adds the raw-image source and header to ipctool and defines a standalone 'raw_image_test' target.

CMakeLists.txt

raw_image_test.cCover raw-image acceptance and refusal cases +104/-0

Cover raw-image acceptance and refusal cases

• Tests matching layouts, kernels without offsets, and rejection of wrong sizes, NAND, device-order gaps, offset holes, and invalid erase geometry.

src/raw_image_test.c

@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


Action required

1. Raw restores can write to wrong regions ✓ Resolved
Description
raw_image_fits() skips the physical-adjacency check for every partition whose sysfs offset is
unavailable, while still counting its size toward image coverage. On a kernel with missing offsets,
overlapping partitions can pass the size and device-number checks, so sequential image slices are
written through MTD devices that address the same physical region.


Remediation recommended

2. The flashed image can differ from its SHA1 ✓ Resolved
Description
restore_raw() hashes a read-only, file-backed MAP_PRIVATE mapping, then retains that mapping as
the flash source without making an immutable snapshot. If another process modifies or truncates the
input during the prompt or preparation, the later write can consume different bytes or fault;
pin_to_ram() runs later and does not protect against changes to the backing file.

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

Sysfs offsets exist from 4.4 on, and on anything older --raw only compared
sizes. Overlapping partitions that happened to add up to the image passed
that, and each block's read-back would then have passed too. Refusing those
kernels outright would have taken --raw away from every 3.x camera, so
mtd_flash_offset() now asks the command line's mtdparts= and then the line
the kernel logs per partition, which also covers tables built into the
kernel. The format of that line is the same from 3.0 on. A partition is
matched by name and size, and not at all if two places claim it. Partitions
are enumerated through this lookup, so alias detection gets real offsets on
those kernels too, in place of comparing sizes.

With offsets to go on, restore --raw picks the partitions that cover the
image exactly once and writes each straight to its device, instead of
adding up sizes along device numbers. That also takes in what used to be
refused: "all" as mtd0 and numbers out of flash order, as on Anjoy.
Overlaps that are not whole, gaps, and a partition nothing can place are
refused, the last with a hint to reboot while the log still has it.

The mapping keeps pages in memory but does not freeze the file, so one
changed after the prompt would have been written unnoticed and one
truncated would have raised SIGBUS mid-flash. Size and SHA1 are now checked
again once nothing else is running and nothing is erased yet; a copy would
have put a dump in /tmp in RAM twice.

Both follow the review of the previous commit. Run on an emulated
hi3516ev300 with /sys unmounted, /proc/cmdline hidden and the log rotated
away, to reach each source in turn, comparing the flash file from the host.
@dimerr
dimerr merged commit 1fd4d85 into master Nov 4, 2026
5 checks passed
@dimerr
dimerr deleted the restore-raw branch November 4, 2026 15:41
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