From 15c7b62ce331d46859f60ace2f4a2ec5fa3deb9c Mon Sep 17 00:00:00 2001 From: Dmitry Ermakov <7954832+dimerr@users.noreply.github.com> Date: Wed, 4 Nov 2026 15:23:54 +0000 Subject: [PATCH 1/2] restore: write a plain image of the whole NOR with --raw 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. --- .github/workflows/pr-build-check.yml | 3 +- CMakeLists.txt | 6 ++ README.md | 15 ++++ src/backup.c | 130 +++++++++++++++++++++++++++ src/main.c | 3 + src/mtd.c | 23 +++-- src/mtd.h | 2 + src/raw_image.c | 59 ++++++++++++ src/raw_image.h | 33 +++++++ src/raw_image_test.c | 104 +++++++++++++++++++++ 10 files changed, 369 insertions(+), 9 deletions(-) create mode 100644 src/raw_image.c create mode 100644 src/raw_image.h create mode 100644 src/raw_image_test.c diff --git a/.github/workflows/pr-build-check.yml b/.github/workflows/pr-build-check.yml index 8898cb8a..309cc516 100644 --- a/.github/workflows/pr-build-check.yml +++ b/.github/workflows/pr-build-check.yml @@ -150,7 +150,7 @@ jobs: sensors_test anyka_test hal_hisi_test \ report_test http_test mtd_alias_test \ nand_layout_test upgrade_env_test \ - jffs2_test + jffs2_test raw_image_test ./build/cYAML_test ./build/report_test ./build/http_test @@ -158,6 +158,7 @@ jobs: ./build/upgrade_env_test ./build/jffs2_test ./build/nand_layout_test + ./build/raw_image_test ./build/reginfo_test ./build/longse_test ./build/anyka_test diff --git a/CMakeLists.txt b/CMakeLists.txt index 5744aacb..ac3ff5ce 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -334,6 +334,8 @@ set(IPCTOOL_SRC src/nand_layout.h src/nand_migrate.c src/nand_migrate.h + src/raw_image.c + src/raw_image.h src/bootrom.c src/bootrom.h src/clocks.c @@ -550,4 +552,8 @@ if(NOT ONLY_LIBRARY) # What nand-migrate decides before it touches the flash. add_executable(nand_layout_test src/nand_layout_test.c src/nand_layout.c) target_include_directories(nand_layout_test PRIVATE include src) + + # What restore --raw decides before it touches the flash. + add_executable(raw_image_test src/raw_image_test.c src/raw_image.c) + target_include_directories(raw_image_test PRIVATE include src) endif() diff --git a/README.md b/README.md index b677e374..2f4b86e7 100644 --- a/README.md +++ b/README.md @@ -119,6 +119,9 @@ Where: restore restore from a backup file [-s, --skip-env] skip environment [-f, --force] enforce + [--raw] the file is a plain image of the whole + NOR flash, not a backup; must match the + flash size exactly upgrade upgrade to OpenIPC firmware (experimental! use only on cameras with UART) [-f, --force] enforce @@ -287,6 +290,18 @@ sensors: # sync ``` +* Write a plain image of the whole NOR flash, from a programmer or `dd`: + + ```console + # ipctool restore --raw /tmp/flash.bin + ``` + + A raw image describes nothing and carries no checksums, so `--raw` prints + its SHA1 to compare by hand, and refuses before anything is stopped unless + the image is exactly the size of the flash and the partitions lie in order, + one after the other. `--force` skips the question, not those checks. NAND is + refused: a raw NAND image has no answer for bad blocks or UBI. + ### Moving a NOR camera to another layout `upgrade` writes a kernel and a rootfs in a new partition layout over the diff --git a/src/backup.c b/src/backup.c index 9462b5e1..66df8878 100644 --- a/src/backup.c +++ b/src/backup.c @@ -36,6 +36,7 @@ #include "jffs2.h" #include "mtd.h" #include "network.h" +#include "raw_image.h" #include "sha1.h" #include "tools.h" #include "uboot.h" @@ -1168,6 +1169,124 @@ static int restore_backup(const char *arg, bool skip_env, bool force) { return 1; } +typedef struct { + mtd_restore_ctx_t mtd; // first: cb_mtd_restore() is handed this struct + raw_part_t part[MAX_MTDBLOCKS]; + size_t nparts; +} raw_ctx_t; + +// Called in flash order, which is why the order is kept here too: the table +// in mtd_restore_ctx_t is by device number. +static bool cb_mtd_raw(int i, const char *name, struct mtd_info_user *mtd, + void *ctx) { + raw_ctx_t *c = (raw_ctx_t *)ctx; + if (c->nparts == MAX_MTDBLOCKS || i >= MAX_MTDBLOCKS) + return false; + raw_part_t *p = &c->part[c->nparts++]; + *p = (raw_part_t){.num = i, + .size = mtd->size, + .erasesize = mtd->erasesize, + .nor = mtd->type == MTD_NORFLASH}; + p->has_offset = mtd_flash_offset(i, &p->offset); + return cb_mtd_restore(i, name, mtd, ctx); +} + +/* + * A plain image of the whole NOR flash, from a programmer or `dd`. It says + * nothing about itself, so what makes it safe to write is that it fits this + * flash exactly, and all of that is settled before anything is stopped. + */ +static int restore_raw(const char *arg, bool skip_env, bool force) { + if (arg == NULL || access(arg, R_OK)) { + fprintf(stderr, "restore --raw needs a flash image: " + "ipctool restore --raw \n"); + return 1; + } + + static raw_ctx_t ctx; + ctx.mtd.env_dev = skip_env ? -1 : 0; + ctx.mtd.skip_env = skip_env; + enum_mtd_info(&ctx, cb_mtd_raw); + if (skip_env && ctx.mtd.env_dev == -1) { + fprintf(stderr, "No U-Boot env found, aborting...\n"); + return 1; + } + + // Mapped rather than read, like an upgrade bundle: a dump in /tmp would + // be in RAM twice. pin_to_ram() holds it from then on. + int fd = open(arg, O_RDONLY); + struct stat st; + if (fd < 0 || fstat(fd, &st)) { + fprintf(stderr, "Cannot read '%s': %s\n", arg, strerror(errno)); + if (fd >= 0) + close(fd); + return 1; + } + char why[160]; + if (!raw_image_fits(ctx.part, ctx.nparts, st.st_size, why, sizeof(why))) { + fprintf(stderr, "Not writing '%s': %s\n", arg, why); + close(fd); + return 1; + } + char *image = mmap(NULL, st.st_size, PROT_READ, MAP_PRIVATE, fd, 0); + close(fd); + if (image == MAP_FAILED) { + fprintf(stderr, "Cannot map '%s': %s\n", arg, strerror(errno)); + return 1; + } + + // Nothing in the image can be checked, so show what would be written + char digest[20]; + SHA1(digest, image, st.st_size); + printf("Raw image %s, 0x%llx bytes, SHA1 ", arg, + (unsigned long long)st.st_size); + for (size_t i = 0; i < sizeof(digest); i++) + printf("%02x", (unsigned char)digest[i]); + printf("\n%s", skip_env ? " U-Boot env overwrite will be skipped\n" : ""); + + static stored_mtd_t stored[MAX_MTDBLOCKS]; + size_t off = 0; + for (size_t k = 0; k < ctx.nparts; k++) { + const raw_part_t *p = &ctx.part[k]; + // A name is what makes do_flash() write an entry at all + snprintf(stored[k].name, sizeof(stored[k].name), "mtd%d %s", p->num, + ctx.mtd.part[p->num].name); + stored[k].off_flashb = off; + stored[k].size = p->size; + stored[k].data = image + off; + off += p->size; + printf("[%zu] 0x%.8zx\t0x%.8zx\t%s\n", k, stored[k].off_flashb, + stored[k].size, stored[k].name); + } + + if (!force) { + char c = 0; + fprintf(stderr, "This image replaces the whole flash, bootloader " + "included.\nAre you sure to proceed? (y/n)? "); + int ret = scanf(" %c", &c); + (void)ret; + if (c != 'y') + goto bailout; + } + + if (!free_resources(force)) + goto bailout; + if (!do_flash("Analyzing", stored, &ctx.mtd, skip_env, true)) + goto bailout; + if (!pin_to_ram()) + goto bailout; + if (!isolate_from_flash(NULL, NULL)) + reboot_after_failure("Preparing the flash"); + if (!do_flash("Restoring", stored, &ctx.mtd, skip_env, false)) + reboot_after_failure("Restoring"); + + reboot_with_msg(); + +bailout: + munmap(image, st.st_size); + return 1; +} + #define MAX_MTDPARTS 1024 static void add_mtdpart(char *dst, const char *name, uint32_t size) { size_t len = strlen(dst); @@ -1847,11 +1966,13 @@ int upgrade_restore_cmd(int argc, char **argv) { {"force", no_argument, NULL, 'f'}, {"skip-env", no_argument, NULL, 's'}, {"wipe-settings", no_argument, NULL, 'w'}, + {"raw", no_argument, NULL, 'r'}, {NULL, 0, NULL, 0}, }; bool skip_env = false; bool force = false; bool wipe_settings = false; + bool raw = false; int res; int option_index; @@ -1867,6 +1988,9 @@ int upgrade_restore_cmd(int argc, char **argv) { case 'w': wipe_settings = true; break; + case 'r': + raw = true; + break; case '?': print_usage(); @@ -1877,6 +2001,12 @@ int upgrade_restore_cmd(int argc, char **argv) { const char *optarg = argv[optind]; bool restore_mode = argv[0][0] == 'r'; + if (raw && !restore_mode) { + fprintf(stderr, "--raw is an option of restore\n"); + return EXIT_FAILURE; + } + if (raw) + return restore_raw(optarg, skip_env, force); if (restore_mode) return restore_backup(optarg, skip_env, force); else diff --git a/src/main.c b/src/main.c index 3f703e74..32781d87 100644 --- a/src/main.c +++ b/src/main.c @@ -89,6 +89,9 @@ void print_usage() { " restore restore from a backup file\n" " [-s, --skip-env] skip environment\n" " [-f, --force] enforce\n" + " [--raw] the file is a plain image of the whole\n" + " NOR flash, not a backup; must match the\n" + " flash size exactly\n" " upgrade upgrade to OpenIPC firmware\n" " (experimental! use only on cameras with " "UART)\n" diff --git a/src/mtd.c b/src/mtd.c index fcba9f1e..f3ba08ae 100644 --- a/src/mtd.c +++ b/src/mtd.c @@ -403,6 +403,19 @@ struct mtd_entry { bool valid; }; +bool mtd_flash_offset(int num, uint64_t *offset) { + char path[64]; + snprintf(path, sizeof(path), "/sys/class/mtd/mtd%d/offset", num); + FILE *f = fopen(path, "r"); + unsigned long long o = 0; + bool known = f && fscanf(f, "%llu", &o) == 1; + if (f) + fclose(f); + if (known) + *offset = o; + return known; +} + void enum_mtd_info(void *ctx, cb_mtd cb) { FILE *fp; char dev[80]; @@ -466,14 +479,8 @@ void enum_mtd_info(void *ctx, cb_mtd cb) { for (int i = 0; i < n; i++) { if (!mtds[i].valid) continue; - char path[64]; - snprintf(path, sizeof(path), "/sys/class/mtd/mtd%d/offset", mtds[i].i); - FILE *f = fopen(path, "r"); - unsigned long long o = 0; - has_off[m] = f && fscanf(f, "%llu", &o) == 1; - if (f) - fclose(f); - offs[m] = o; + offs[m] = 0; + has_off[m] = mtd_flash_offset(mtds[i].i, &offs[m]); sizes[m] = mtds[i].mtd.size; idx[m++] = i; } diff --git a/src/mtd.h b/src/mtd.h index f3459764..45e05f56 100644 --- a/src/mtd.h +++ b/src/mtd.h @@ -10,6 +10,8 @@ typedef bool (*cb_mtd)(int i, const char *name, struct mtd_info_user *mtd, cJSON *get_mtd_info(); char *open_mtdblock(int i, int *fd, uint32_t size, int flags); void enum_mtd_info(void *ctx, cb_mtd cb); +// Where partition `num` starts on the flash; old kernels do not say +bool mtd_flash_offset(int num, uint64_t *offset); bool mtd_write(int mtd, uint32_t offset, uint32_t erasesize, const char *data, size_t size); int mtd_unlock_cmd(); diff --git a/src/raw_image.c b/src/raw_image.c new file mode 100644 index 00000000..49215643 --- /dev/null +++ b/src/raw_image.c @@ -0,0 +1,59 @@ +#include +#include + +#include "raw_image.h" + +bool raw_image_fits(const raw_part_t *parts, size_t nparts, uint64_t image_size, + char *why, size_t whylen) { + if (!nparts) { + snprintf(why, whylen, "no flash partitions found"); + return false; + } + + uint64_t covered = 0; + for (size_t k = 0; k < nparts; k++) { + const raw_part_t *p = &parts[k]; + if (!p->nor) { + snprintf(why, whylen, + "mtd%d is not NOR: a raw NAND image has no bad block or " + "UBI handling here", + p->num); + return false; + } + // Partitions are written by device number and placed by adding up + // sizes, so the two have to agree. A gap is also what a partition + // that cannot be opened for writing leaves behind. + if (p->num != (int)k) { + snprintf(why, whylen, + "found mtd%d where mtd%zu belongs: a partition is " + "missing, read-only or out of flash order", + p->num, k); + return false; + } + if (!p->erasesize || p->erasesize != parts[0].erasesize || + p->size % p->erasesize) { + snprintf(why, whylen, + "mtd%d is not a whole number of 0x%" PRIx32 " erase " + "blocks", + p->num, parts[0].erasesize); + return false; + } + if (p->has_offset && p->offset != covered) { + snprintf(why, whylen, + "mtd%d starts at 0x%" PRIx64 ", not 0x%" PRIx64 ": the " + "partitions do not follow one another", + p->num, p->offset, covered); + return false; + } + covered += p->size; + } + + if (image_size != covered) { + snprintf(why, whylen, + "the image is 0x%" PRIx64 " bytes, the partitions cover " + "0x%" PRIx64, + image_size, covered); + return false; + } + return true; +} diff --git a/src/raw_image.h b/src/raw_image.h new file mode 100644 index 00000000..b1dae5c5 --- /dev/null +++ b/src/raw_image.h @@ -0,0 +1,33 @@ +#ifndef RAW_IMAGE_H +#define RAW_IMAGE_H + +/* + * The pure half of `ipctool restore --raw`: whether an image with no + * description of its own can be cut along this camera's partitions. Kept + * apart so it can be tested on a host (raw_image_test.c). + */ + +#include +#include +#include + +typedef struct { + int num; // N of /dev/mtdN + uint64_t size; + uint32_t erasesize; + bool nor; + // Where the kernel says the partition starts, on kernels that say + bool has_offset; + uint64_t offset; +} raw_part_t; + +/* + * `parts` in the order they lie on the flash. True when an image of + * `image_size` bytes maps onto them one to one; otherwise `why` says what is + * in the way. There is no override: an image that does not fit is the wrong + * image, or a layout the offsets cannot be trusted on. + */ +bool raw_image_fits(const raw_part_t *parts, size_t nparts, uint64_t image_size, + char *why, size_t whylen); + +#endif /* RAW_IMAGE_H */ diff --git a/src/raw_image_test.c b/src/raw_image_test.c new file mode 100644 index 00000000..68ed9c75 --- /dev/null +++ b/src/raw_image_test.c @@ -0,0 +1,104 @@ +/* + * raw_image_test: when `ipctool restore --raw` agrees to write. The checks are + * all that stands between a wrong file and a camera, and restore itself cannot + * run on a host. + */ +#include +#include + +#include "raw_image.h" + +static int failed; +#define CHECK(cond, ...) \ + do { \ + if (!(cond)) { \ + fprintf(stderr, "FAIL %s:%d: ", __FILE__, __LINE__); \ + fprintf(stderr, __VA_ARGS__); \ + fprintf(stderr, "\n"); \ + failed = 1; \ + } \ + } while (0) + +#define ES 0x10000u +#define FLASH 0x800000u + +// 8 MB: boot, env, kernel, rootfs, as OpenIPC lays out a NOR +static void layout(raw_part_t *p) { + static const uint64_t sizes[] = {0x40000, 0x10000, 0x200000, 0x5b0000}; + uint64_t off = 0; + for (int i = 0; i < 4; i++) { + p[i] = (raw_part_t){.num = i, + .size = sizes[i], + .erasesize = ES, + .nor = true, + .has_offset = true, + .offset = off}; + off += sizes[i]; + } +} + +static void refused(const raw_part_t *p, size_t n, uint64_t size, + const char *expect) { + char why[128] = ""; + CHECK(!raw_image_fits(p, n, size, why, sizeof why), "accepted, wanted '%s'", + expect); + CHECK(strstr(why, expect), "'%s' does not say '%s'", why, expect); +} + +int main(void) { + raw_part_t p[4]; + char why[128]; + + layout(p); + CHECK(raw_image_fits(p, 4, FLASH, why, sizeof why), "whole image: %s", why); + + // Old kernels have no sysfs offset; the layout still adds up + for (int i = 0; i < 4; i++) + p[i].has_offset = false; + CHECK(raw_image_fits(p, 4, FLASH, why, sizeof why), "no offsets: %s", why); + + // The file is wrong. A zero-length file on a machine with no MTD at all + // has to stop here too: the next step stops every process. + layout(p); + refused(p, 4, FLASH - 1, "the image is"); + refused(p, 4, FLASH + 1, "the image is"); + refused(p, 4, FLASH * 2, "the image is"); + refused(p, 4, 0, "the image is"); + refused(p, 0, 0, "no flash partitions"); + + layout(p); + p[2].nor = false; + refused(p, 4, FLASH, "not NOR"); + + // "all" at mtd0, or a partition enumeration could not open + layout(p); + for (int i = 0; i < 4; i++) + p[i].num = i + 1; + refused(p, 4, FLASH, "found mtd1 where mtd0"); + layout(p); + p[2].num = 3; + refused(p, 4, FLASH, "found mtd3 where mtd2"); + + // Anjoy: UBOOT is mtd3 and lies second, whatever order it is listed in + layout(p); + p[1].num = 3; + p[3].num = 1; + refused(p, 4, FLASH, "found mtd3 where mtd1"); + + // A hole in the middle of the flash that the sizes alone do not show + layout(p); + p[2].offset += ES; + refused(p, 4, FLASH, "mtd2 starts at"); + + layout(p); + p[1].size += 1; + refused(p, 4, FLASH, "erase blocks"); + layout(p); + p[3].erasesize = 0x1000; + refused(p, 4, FLASH, "erase blocks"); + layout(p); + p[0].erasesize = 0; + refused(p, 4, FLASH, "erase blocks"); + + return failed; +} From 8dd5225e730f6000c50fbe57526c9eed1e7db283 Mon Sep 17 00:00:00 2001 From: Dmitry Ermakov <7954832+dimerr@users.noreply.github.com> Date: Wed, 4 Nov 2026 15:38:26 +0000 Subject: [PATCH 2/2] restore --raw: place partitions by where they really lie 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. --- README.md | 14 +++-- src/backup.c | 74 ++++++++++++++++++++------- src/mtd.c | 27 ++++++++-- src/mtd.h | 6 ++- src/mtd_alias.c | 118 +++++++++++++++++++++++++++++++++++++++++++ src/mtd_alias.h | 16 ++++++ src/mtd_alias_test.c | 81 +++++++++++++++++++++++++++++ src/raw_image.c | 81 +++++++++++++++++++---------- src/raw_image.h | 23 +++++---- src/raw_image_test.c | 84 +++++++++++++++++++++--------- src/tools.h | 2 + 11 files changed, 438 insertions(+), 88 deletions(-) diff --git a/README.md b/README.md index 2f4b86e7..4a79d6e9 100644 --- a/README.md +++ b/README.md @@ -297,10 +297,16 @@ sensors: ``` A raw image describes nothing and carries no checksums, so `--raw` prints - its SHA1 to compare by hand, and refuses before anything is stopped unless - the image is exactly the size of the flash and the partitions lie in order, - one after the other. `--force` skips the question, not those checks. NAND is - refused: a raw NAND image has no answer for bad blocks or UBI. + its SHA1 to compare by hand, and checks that SHA1 again just before erasing. + It refuses before anything is stopped unless the image is exactly the size + the partitions cover, each byte by one partition. `--force` skips the + question, not those checks. NAND is refused: a raw NAND image has no answer + for bad blocks or UBI. + + Partitions are placed by where they really lie, so one that overlaps the + others whole, such as `all`, is left out. Kernels before 4.4 do not report + that, so it comes from `mtdparts=` on the command line or the boot log. If + both have nothing, reboot and run it again while the log still has it. ### Moving a NOR camera to another layout diff --git a/src/backup.c b/src/backup.c index 66df8878..723e6509 100644 --- a/src/backup.c +++ b/src/backup.c @@ -235,6 +235,10 @@ typedef struct { int ubi_device; int vol_id; char vol_name[64]; + // Written straight to /dev/mtd from its start, for an image that + // knows where each partition really lies + bool to_dev; + int dev; } stored_mtd_t; static int yaml_parseblock(char *start, int indent, stored_mtd_t *mi) { @@ -934,9 +938,11 @@ static bool do_flash(const char *phase, stored_mtd_t *mtdbackup, } int cnt = mtdbackup[i].size / chunk; for (int c = 0; c < cnt; c++) { - size_t this_offset; - int newi = - map_old_new_mtd(i, c * chunk, &this_offset, mtdbackup, mtd); + size_t this_offset = c * chunk; + int newi = mtdbackup[i].to_dev + ? mtdbackup[i].dev + : map_old_new_mtd(i, c * chunk, &this_offset, + mtdbackup, mtd); if (newi == -1) { fprintf(stderr, "\nOffset algorithm error, aborting...\n"); return false; @@ -1175,8 +1181,6 @@ typedef struct { size_t nparts; } raw_ctx_t; -// Called in flash order, which is why the order is kept here too: the table -// in mtd_restore_ctx_t is by device number. static bool cb_mtd_raw(int i, const char *name, struct mtd_info_user *mtd, void *ctx) { raw_ctx_t *c = (raw_ctx_t *)ctx; @@ -1187,10 +1191,22 @@ static bool cb_mtd_raw(int i, const char *name, struct mtd_info_user *mtd, .size = mtd->size, .erasesize = mtd->erasesize, .nor = mtd->type == MTD_NORFLASH}; - p->has_offset = mtd_flash_offset(i, &p->offset); + p->has_offset = mtd_flash_offset(i, name, mtd->size, &p->offset); return cb_mtd_restore(i, name, mtd, ctx); } +static void image_sha1(const char *image, size_t size, char digest[20]) { + SHA1_CTX sha; + SHA1Init(&sha); + for (size_t off = 0; off < size;) { + size_t n = MIN(size - off, 1024 * 1024); + SHA1Update(&sha, (const unsigned char *)image + off, n); + off += n; + feed_watchdog(); + } + SHA1Final((unsigned char *)digest, &sha); +} + /* * A plain image of the whole NOR flash, from a programmer or `dd`. It says * nothing about itself, so what makes it safe to write is that it fits this @@ -1213,7 +1229,8 @@ static int restore_raw(const char *arg, bool skip_env, bool force) { } // Mapped rather than read, like an upgrade bundle: a dump in /tmp would - // be in RAM twice. pin_to_ram() holds it from then on. + // be in RAM twice. pin_to_ram() holds its pages, not their contents, so + // what was shown is checked again once nothing else is left to change it. int fd = open(arg, O_RDONLY); struct stat st; if (fd < 0 || fstat(fd, &st)) { @@ -1223,21 +1240,24 @@ static int restore_raw(const char *arg, bool skip_env, bool force) { return 1; } char why[160]; - if (!raw_image_fits(ctx.part, ctx.nparts, st.st_size, why, sizeof(why))) { + static raw_part_t plan[MAX_MTDBLOCKS]; + size_t nplan = raw_image_plan(ctx.part, ctx.nparts, st.st_size, plan, why, + sizeof(why)); + if (!nplan) { fprintf(stderr, "Not writing '%s': %s\n", arg, why); close(fd); return 1; } char *image = mmap(NULL, st.st_size, PROT_READ, MAP_PRIVATE, fd, 0); - close(fd); if (image == MAP_FAILED) { fprintf(stderr, "Cannot map '%s': %s\n", arg, strerror(errno)); + close(fd); return 1; } // Nothing in the image can be checked, so show what would be written char digest[20]; - SHA1(digest, image, st.st_size); + image_sha1(image, st.st_size, digest); printf("Raw image %s, 0x%llx bytes, SHA1 ", arg, (unsigned long long)st.st_size); for (size_t i = 0; i < sizeof(digest); i++) @@ -1245,16 +1265,16 @@ static int restore_raw(const char *arg, bool skip_env, bool force) { printf("\n%s", skip_env ? " U-Boot env overwrite will be skipped\n" : ""); static stored_mtd_t stored[MAX_MTDBLOCKS]; - size_t off = 0; - for (size_t k = 0; k < ctx.nparts; k++) { - const raw_part_t *p = &ctx.part[k]; + for (size_t k = 0; k < nplan; k++) { + const raw_part_t *p = &plan[k]; // A name is what makes do_flash() write an entry at all snprintf(stored[k].name, sizeof(stored[k].name), "mtd%d %s", p->num, ctx.mtd.part[p->num].name); - stored[k].off_flashb = off; + stored[k].off_flashb = p->offset; stored[k].size = p->size; - stored[k].data = image + off; - off += p->size; + stored[k].data = image + p->offset; + stored[k].to_dev = true; + stored[k].dev = p->num; printf("[%zu] 0x%.8zx\t0x%.8zx\t%s\n", k, stored[k].off_flashb, stored[k].size, stored[k].name); } @@ -1275,14 +1295,34 @@ static int restore_raw(const char *arg, bool skip_env, bool force) { goto bailout; if (!pin_to_ram()) goto bailout; - if (!isolate_from_flash(NULL, NULL)) + if (!isolate_from_flash(NULL, NULL)) { reboot_after_failure("Preparing the flash"); + goto bailout; + } + + // Size first: reading past the end of a truncated file is a SIGBUS, and + // from here that would leave a camera with no services and no reboot + struct stat now; + char again[sizeof(digest)]; + if (fstat(fd, &now) || now.st_size != st.st_size) { + fprintf(stderr, "The image changed size\n"); + reboot_after_failure("Checking the image"); + goto bailout; + } + image_sha1(image, st.st_size, again); + if (memcmp(digest, again, sizeof(digest))) { + fprintf(stderr, "The image is not the one shown\n"); + reboot_after_failure("Checking the image"); + goto bailout; + } + if (!do_flash("Restoring", stored, &ctx.mtd, skip_env, false)) reboot_after_failure("Restoring"); reboot_with_msg(); bailout: + close(fd); munmap(image, st.st_size); return 1; } diff --git a/src/mtd.c b/src/mtd.c index f3ba08ae..d2077616 100644 --- a/src/mtd.c +++ b/src/mtd.c @@ -403,7 +403,8 @@ struct mtd_entry { bool valid; }; -bool mtd_flash_offset(int num, uint64_t *offset) { +bool mtd_flash_offset(int num, const char *name, uint64_t size, + uint64_t *offset) { char path[64]; snprintf(path, sizeof(path), "/sys/class/mtd/mtd%d/offset", num); FILE *f = fopen(path, "r"); @@ -411,8 +412,27 @@ bool mtd_flash_offset(int num, uint64_t *offset) { bool known = f && fscanf(f, "%llu", &o) == 1; if (f) fclose(f); - if (known) + if (known) { *offset = o; + return true; + } + + // Before 4.4 the kernel keeps this to itself + char cmdline[4096]; + f = fopen("/proc/cmdline", "r"); + if (f) { + size_t n = fread(cmdline, 1, sizeof(cmdline) - 1, f); + fclose(f); + cmdline[n] = 0; + cmdline[strcspn(cmdline, "\n")] = 0; + if (mtd_offset_from_cmdline(cmdline, name, size, offset)) + return true; + } + + char *log = NULL; + ssize_t len = get_dmesg_buf(&log); + known = log && len > 0 && mtd_offset_from_log(log, len, name, size, offset); + free(log); return known; } @@ -480,7 +500,8 @@ void enum_mtd_info(void *ctx, cb_mtd cb) { if (!mtds[i].valid) continue; offs[m] = 0; - has_off[m] = mtd_flash_offset(mtds[i].i, &offs[m]); + has_off[m] = mtd_flash_offset(mtds[i].i, mtds[i].name, mtds[i].mtd.size, + &offs[m]); sizes[m] = mtds[i].mtd.size; idx[m++] = i; } diff --git a/src/mtd.h b/src/mtd.h index 45e05f56..e870c498 100644 --- a/src/mtd.h +++ b/src/mtd.h @@ -10,8 +10,10 @@ typedef bool (*cb_mtd)(int i, const char *name, struct mtd_info_user *mtd, cJSON *get_mtd_info(); char *open_mtdblock(int i, int *fd, uint32_t size, int flags); void enum_mtd_info(void *ctx, cb_mtd cb); -// Where partition `num` starts on the flash; old kernels do not say -bool mtd_flash_offset(int num, uint64_t *offset); +// Where partition `num` starts on the flash: the kernel's word where it gives +// one, then the command line and the boot log, which a long uptime can lose +bool mtd_flash_offset(int num, const char *name, uint64_t size, + uint64_t *offset); bool mtd_write(int mtd, uint32_t offset, uint32_t erasesize, const char *data, size_t size); int mtd_unlock_cmd(); diff --git a/src/mtd_alias.c b/src/mtd_alias.c index 43b2393c..2eec197c 100644 --- a/src/mtd_alias.c +++ b/src/mtd_alias.c @@ -1,3 +1,7 @@ +#include +#include +#include + #include "mtd_alias.h" void mtd_mark_aliases(int n, const uint64_t *off, const bool *has_off, @@ -22,3 +26,117 @@ void mtd_mark_aliases(int n, const uint64_t *off, const bool *has_off, alias[i] = n > 1 && others == size[i]; } } + +// The kernel's memparse(): strtoull with a size suffix +static uint64_t memparse(const char *s, const char **end) { + char *e; + uint64_t v = strtoull(s, &e, 0); + if (e != s && *e && strchr("kKmMgG", *e)) { + v <<= *e == 'k' || *e == 'K' ? 10 : *e == 'm' || *e == 'M' ? 20 : 30; + e++; + } + *end = e; + return v; +} + +// Every partition of one :,... A part with no offset follows +// the one before it. Returns where the definition ends, or NULL if it is not +// one. +static const char *scan_mtddef(const char *s, const char *name, uint64_t size, + uint64_t *offset, int *found) { + uint64_t next = 0; + for (;;) { + bool rest = *s == '-'; + uint64_t psize = 0, off = next; + const char *e = s + 1; + if (!rest) { + psize = memparse(s, &e); + if (e == s) + return NULL; + } + s = e; + if (*s == '@') { + off = memparse(s + 1, &e); + if (e == s + 1) + return NULL; + s = e; + } + if (*s == '(') { + const char *close = strchr(s, ')'); + if (!close) + return NULL; + size_t n = close - s - 1; + if (n == strlen(name) && !strncmp(s + 1, name, n) && + (rest || psize == size)) { + if (*found && *offset != off) + *found = 2; + else if (!*found) + *found = 1; + *offset = off; + } + s = close + 1; + } + s += strcspn(s, ",; "); // the ro and lk flags + next = off + psize; + if (*s != ',') + return s; + s++; + } +} + +bool mtd_offset_from_cmdline(const char *cmdline, const char *name, + uint64_t size, uint64_t *offset) { + // The kernel keeps the last one it is given + const char *def = NULL; + for (const char *p = cmdline; (p = strstr(p, "mtdparts=")); p++) + if (p == cmdline || p[-1] == ' ') + def = p + strlen("mtdparts="); + if (!def) + return false; + + int found = 0; + while (def && *def && *def != ' ') { + const char *colon = strchr(def, ':'); + if (!colon) + break; + def = scan_mtddef(colon + 1, name, size, offset, &found); + if (def && *def == ';') + def++; + else + break; + } + return found == 1; +} + +bool mtd_offset_from_log(const char *log, size_t len, const char *name, + uint64_t size, uint64_t *offset) { + int found = 0; + const char *end = log + len; + while (log < end) { + const char *nl = memchr(log, '\n', end - log); + size_t n = (nl ? nl : end) - log; + char line[256], part[128]; + unsigned long long from, to; + if (n < sizeof(line)) { + memcpy(line, log, n); + line[n] = 0; + // Behind a <5> level and a timestamp, or neither + const char *p = strstr(line, "0x"); + int end_of_line = -1; + if (p) + sscanf(p, "0x%llx-0x%llx : \"%127[^\"]\"%n", &from, &to, part, + &end_of_line); + // %n is only reached past the closing quote: a line the ring + // buffer tore in two has a shorter name in it + if (end_of_line >= 0 && !strcmp(part, name) && to - from == size) { + if (found && *offset != from) + found = 2; + else if (!found) + found = 1; + *offset = from; + } + } + log += n + 1; + } + return found == 1; +} diff --git a/src/mtd_alias.h b/src/mtd_alias.h index ca1834f6..5f98fce5 100644 --- a/src/mtd_alias.h +++ b/src/mtd_alias.h @@ -2,6 +2,7 @@ #define MTD_ALIAS_H #include +#include #include /* Marks alias[i] for each partition that is another name for flash the @@ -12,4 +13,19 @@ void mtd_mark_aliases(int n, const uint64_t *off, const bool *has_off, const uint64_t *size, bool *alias); +/* + * Where a partition starts, for kernels before 4.4: they have no + * /sys/class/mtd/mtdN/offset. A partition is found by name and size, and is + * not found if two places claim it. + * + * From the `mtdparts=` on a kernel command line. Only says something when the + * kernel took its partitions from there, which the size match stands in for. + */ +bool mtd_offset_from_cmdline(const char *cmdline, const char *name, + uint64_t size, uint64_t *offset); +/* From the line the kernel logs per partition as it adds it, which also covers + * tables built into the kernel. `log` need not be NUL-terminated. */ +bool mtd_offset_from_log(const char *log, size_t len, const char *name, + uint64_t size, uint64_t *offset); + #endif /* MTD_ALIAS_H */ diff --git a/src/mtd_alias_test.c b/src/mtd_alias_test.c index b35a2c23..dfb0c7a6 100644 --- a/src/mtd_alias_test.c +++ b/src/mtd_alias_test.c @@ -56,5 +56,86 @@ int main(void) { if (!failed) printf("mtd_alias_test: ok\n"); + // Where a partition lies, as a kernel without sysfs offsets tells it + uint64_t at; + const char *hi = "mem=32M console=ttyAMA0,115200 root=/dev/mtdblock3 " + "mtdparts=hi_sfc:256k(boot),64k(env),2048k(kernel)," + "5120k(rootfs),-(rootfs_data) panic=20"; + CHECK(mtd_offset_from_cmdline(hi, "boot", 0x40000, &at) && at == 0, "boot"); + CHECK(mtd_offset_from_cmdline(hi, "rootfs", 0x500000, &at) && + at == 0x250000, + "rootfs at %llx", (unsigned long long)at); + // "-" says nothing of the size, and the size is not asked of it + CHECK(mtd_offset_from_cmdline(hi, "rootfs_data", 0x2b0000, &at) && + at == 0x750000, + "rootfs_data at %llx", (unsigned long long)at); + // The size is what says this table is the one the kernel used + CHECK(!mtd_offset_from_cmdline(hi, "boot", 0x20000, &at), "wrong size"); + CHECK(!mtd_offset_from_cmdline(hi, "nope", 0x40000, &at), "wrong name"); + CHECK(!mtd_offset_from_cmdline("root=/dev/ram", "boot", 0x40000, &at), + "no mtdparts"); + + const char *over = "mtdparts=spi0.0:1M@0x10000(a)ro,0x20000(b)," + "16M@0(all),8m@2M(c)lk;nand:1g(d)"; + CHECK(mtd_offset_from_cmdline(over, "a", 0x100000, &at) && at == 0x10000, + "@ and ro"); + CHECK(mtd_offset_from_cmdline(over, "b", 0x20000, &at) && at == 0x110000, + "after @"); + CHECK(mtd_offset_from_cmdline(over, "all", 0x1000000, &at) && at == 0, + "all"); + CHECK(mtd_offset_from_cmdline(over, "c", 0x800000, &at) && at == 0x200000, + "suffixed @ and lk"); + CHECK(mtd_offset_from_cmdline(over, "d", 0x40000000, &at) && at == 0, + "second chip"); + + // The last mtdparts= is the one the kernel keeps, not a lookalike + CHECK(mtd_offset_from_cmdline("mtdparts=a:1M(x) mtdparts=a:1M@2M(x)", "x", + 0x100000, &at) && + at == 0x200000, + "last wins"); + CHECK(!mtd_offset_from_cmdline("xmtdparts=a:1M(x)", "x", 0x100000, &at), + "lookalike"); + // Two chips, one name and size: which is it + CHECK(!mtd_offset_from_cmdline("mtdparts=a:1M(x);b:1M@1M(x)", "x", 0x100000, + &at), + "ambiguous"); + CHECK(!mtd_offset_from_cmdline("mtdparts=a:zz(x)", "x", 0x100000, &at), + "garbage"); + CHECK(!mtd_offset_from_cmdline("mtdparts=a:1M(x", "x", 0x100000, &at), + "unterminated"); + + // The kernel's own format, on 3.0 to 6.x, in the shapes dmesg gives it + static const char klog[] = + "<5>Creating 5 MTD partitions on \"hi_sfc\":\n" + "<5>0x000000000000-0x000000020000 : \"BOOT\"\n" + "<5>[ 1.234567] 0x000000040000-0x0000001d0000 : \"KERNEL\"\n" + "0x000000020000-0x000000040000 : \"UBOOT\"\n" + "<5>0x000000100000-0x000000110000 : \"twice\"\n" + "<5>0x000000200000-0x000000210000 : \"twice\"\n" + "<5>0x000000300000-0x000000310000 : \"same\"\n" + "<5>0x000000300000-0x000000310000 : \"same\"\n" + "<6>spi: at 0x12340000 irq 5\n" + "<5>0x0000007b0000-0x000000800000 : \"cut"; + size_t klen = sizeof(klog) - 1; + CHECK(mtd_offset_from_log(klog, klen, "BOOT", 0x20000, &at) && at == 0, + "BOOT"); + CHECK(mtd_offset_from_log(klog, klen, "KERNEL", 0x190000, &at) && + at == 0x40000, + "timestamped"); + CHECK(mtd_offset_from_log(klog, klen, "UBOOT", 0x20000, &at) && + at == 0x20000, + "bare"); + CHECK(!mtd_offset_from_log(klog, klen, "BOOT", 0x10000, &at), "wrong size"); + CHECK(!mtd_offset_from_log(klog, klen, "nope", 0x20000, &at), "wrong name"); + CHECK(!mtd_offset_from_log(klog, klen, "twice", 0x10000, &at), + "two places"); + CHECK(mtd_offset_from_log(klog, klen, "same", 0x10000, &at) && + at == 0x300000, + "logged twice"); + CHECK(!mtd_offset_from_log(klog, klen, "cut", 0x50000, &at), "torn line"); + // Bounded by len: nothing after it is read + CHECK(!mtd_offset_from_log(klog, 10, "BOOT", 0x20000, &at), "len"); + CHECK(!mtd_offset_from_log("", 0, "BOOT", 0x20000, &at), "empty"); + return failed; } diff --git a/src/raw_image.c b/src/raw_image.c index 49215643..510b40b3 100644 --- a/src/raw_image.c +++ b/src/raw_image.c @@ -1,50 +1,75 @@ #include #include +#include #include "raw_image.h" -bool raw_image_fits(const raw_part_t *parts, size_t nparts, uint64_t image_size, - char *why, size_t whylen) { +size_t raw_image_plan(const raw_part_t *parts, size_t nparts, + uint64_t image_size, raw_part_t *plan, char *why, + size_t whylen) { if (!nparts) { snprintf(why, whylen, "no flash partitions found"); - return false; + return 0; } - uint64_t covered = 0; for (size_t k = 0; k < nparts; k++) { - const raw_part_t *p = &parts[k]; - if (!p->nor) { + if (!parts[k].nor) { snprintf(why, whylen, "mtd%d is not NOR: a raw NAND image has no bad block or " "UBI handling here", - p->num); - return false; + parts[k].num); + return 0; } - // Partitions are written by device number and placed by adding up - // sizes, so the two have to agree. A gap is also what a partition - // that cannot be opened for writing leaves behind. - if (p->num != (int)k) { + if (!parts[k].has_offset) { snprintf(why, whylen, - "found mtd%d where mtd%zu belongs: a partition is " - "missing, read-only or out of flash order", - p->num, k); - return false; + "cannot tell where mtd%d lies on the flash: reboot and " + "run this again while the boot log still says", + parts[k].num); + return 0; } - if (!p->erasesize || p->erasesize != parts[0].erasesize || + } + + // By offset; few enough that this is all the sort it needs + static raw_part_t sorted[64]; + if (nparts > sizeof(sorted) / sizeof(sorted[0])) { + snprintf(why, whylen, "too many partitions"); + return 0; + } + for (size_t k = 0; k < nparts; k++) { + size_t at = k; + for (; at && sorted[at - 1].offset > parts[k].offset; at--) + sorted[at] = sorted[at - 1]; + sorted[at] = parts[k]; + } + + size_t nplan = 0; + uint64_t covered = 0; + for (size_t k = 0; k < nparts; k++) { + const raw_part_t *p = &sorted[k]; + if (p->offset + p->size <= covered) + continue; + if (p->offset < covered) { + snprintf(why, whylen, + "mtd%d overlaps the partition before it at 0x%" PRIx64, + p->num, p->offset); + return 0; + } + if (p->offset > covered) { + snprintf(why, whylen, + "no partition covers 0x%" PRIx64 "-0x%" PRIx64 ": one is " + "missing or read-only", + covered, p->offset); + return 0; + } + if (!p->erasesize || p->erasesize != sorted[0].erasesize || p->size % p->erasesize) { snprintf(why, whylen, "mtd%d is not a whole number of 0x%" PRIx32 " erase " "blocks", - p->num, parts[0].erasesize); - return false; - } - if (p->has_offset && p->offset != covered) { - snprintf(why, whylen, - "mtd%d starts at 0x%" PRIx64 ", not 0x%" PRIx64 ": the " - "partitions do not follow one another", - p->num, p->offset, covered); - return false; + p->num, sorted[0].erasesize); + return 0; } + plan[nplan++] = *p; covered += p->size; } @@ -53,7 +78,7 @@ bool raw_image_fits(const raw_part_t *parts, size_t nparts, uint64_t image_size, "the image is 0x%" PRIx64 " bytes, the partitions cover " "0x%" PRIx64, image_size, covered); - return false; + return 0; } - return true; + return nplan; } diff --git a/src/raw_image.h b/src/raw_image.h index b1dae5c5..d13947d0 100644 --- a/src/raw_image.h +++ b/src/raw_image.h @@ -2,9 +2,9 @@ #define RAW_IMAGE_H /* - * The pure half of `ipctool restore --raw`: whether an image with no - * description of its own can be cut along this camera's partitions. Kept - * apart so it can be tested on a host (raw_image_test.c). + * The pure half of `ipctool restore --raw`: how an image with no description + * of its own is cut along this camera's partitions. Kept apart so it can be + * tested on a host (raw_image_test.c). */ #include @@ -16,18 +16,21 @@ typedef struct { uint64_t size; uint32_t erasesize; bool nor; - // Where the kernel says the partition starts, on kernels that say + // Where it starts on the flash, if anything could say bool has_offset; uint64_t offset; } raw_part_t; /* - * `parts` in the order they lie on the flash. True when an image of - * `image_size` bytes maps onto them one to one; otherwise `why` says what is - * in the way. There is no override: an image that does not fit is the wrong - * image, or a layout the offsets cannot be trusted on. + * Picks the partitions that cover an image of `image_size` bytes exactly once + * and puts them in `plan`, which holds `nparts`, in flash order. Returns how + * many, or 0 with `why` saying what is in the way. A partition that lies + * wholly inside the ones picked is another name for them and is left out. + * There is no override: an image that does not fit is the wrong image, and a + * layout that cannot be established cannot be written safely. */ -bool raw_image_fits(const raw_part_t *parts, size_t nparts, uint64_t image_size, - char *why, size_t whylen); +size_t raw_image_plan(const raw_part_t *parts, size_t nparts, + uint64_t image_size, raw_part_t *plan, char *why, + size_t whylen); #endif /* RAW_IMAGE_H */ diff --git a/src/raw_image_test.c b/src/raw_image_test.c index 68ed9c75..ed4860d7 100644 --- a/src/raw_image_test.c +++ b/src/raw_image_test.c @@ -1,7 +1,7 @@ /* - * raw_image_test: when `ipctool restore --raw` agrees to write. The checks are - * all that stands between a wrong file and a camera, and restore itself cannot - * run on a host. + * raw_image_test: when `ipctool restore --raw` agrees to write, and to which + * partitions. The checks are all that stands between a wrong file and a + * camera, and restore itself cannot run on a host. */ #include #include @@ -21,6 +21,7 @@ static int failed; #define ES 0x10000u #define FLASH 0x800000u +#define MAX 8 // 8 MB: boot, env, kernel, rootfs, as OpenIPC lays out a NOR static void layout(raw_part_t *p) { @@ -40,22 +41,27 @@ static void layout(raw_part_t *p) { static void refused(const raw_part_t *p, size_t n, uint64_t size, const char *expect) { char why[128] = ""; - CHECK(!raw_image_fits(p, n, size, why, sizeof why), "accepted, wanted '%s'", - expect); + raw_part_t plan[MAX]; + CHECK(!raw_image_plan(p, n, size, plan, why, sizeof why), + "accepted, wanted '%s'", expect); CHECK(strstr(why, expect), "'%s' does not say '%s'", why, expect); } +// The plan is these devices, in this order +static void planned(const raw_part_t *p, size_t n, const int *nums, size_t m) { + char why[128] = ""; + raw_part_t plan[MAX]; + size_t got = raw_image_plan(p, n, FLASH, plan, why, sizeof why); + CHECK(got == m, "%zu partitions planned, wanted %zu: %s", got, m, why); + for (size_t k = 0; k < got && k < m; k++) + CHECK(plan[k].num == nums[k], "plan[%zu] is mtd%d", k, plan[k].num); +} + int main(void) { - raw_part_t p[4]; - char why[128]; + raw_part_t p[MAX]; layout(p); - CHECK(raw_image_fits(p, 4, FLASH, why, sizeof why), "whole image: %s", why); - - // Old kernels have no sysfs offset; the layout still adds up - for (int i = 0; i < 4; i++) - p[i].has_offset = false; - CHECK(raw_image_fits(p, 4, FLASH, why, sizeof why), "no offsets: %s", why); + planned(p, 4, (int[]){0, 1, 2, 3}, 4); // The file is wrong. A zero-length file on a machine with no MTD at all // has to stop here too: the next step stops every process. @@ -70,29 +76,59 @@ int main(void) { p[2].nor = false; refused(p, 4, FLASH, "not NOR"); - // "all" at mtd0, or a partition enumeration could not open + // Nothing could say where this one is; its size proves nothing + layout(p); + p[2].has_offset = false; + refused(p, 4, FLASH, "cannot tell where mtd2"); + + // A partition that could not be opened + layout(p); + p[2] = p[3]; + refused(p, 3, FLASH, "no partition covers 0x50000-0x250000"); + layout(p); + refused(p, 3, FLASH, "the image is"); + + // Numbers need not follow the flash: "all" went first and was left out + // by enumeration, so the real ones start at mtd1 layout(p); for (int i = 0; i < 4; i++) p[i].num = i + 1; - refused(p, 4, FLASH, "found mtd1 where mtd0"); - layout(p); - p[2].num = 3; - refused(p, 4, FLASH, "found mtd3 where mtd2"); + planned(p, 4, (int[]){1, 2, 3, 4}, 4); - // Anjoy: UBOOT is mtd3 and lies second, whatever order it is listed in + // Anjoy: UBOOT is mtd3 and lies second, whichever order it is listed in layout(p); p[1].num = 3; p[3].num = 1; - refused(p, 4, FLASH, "found mtd3 where mtd1"); + planned(p, 4, (int[]){0, 3, 2, 1}, 4); + raw_part_t swapped = p[1]; + p[1] = p[3]; + p[3] = swapped; + planned(p, 4, (int[]){0, 3, 2, 1}, 4); - // A hole in the middle of the flash that the sizes alone do not show + // A second name for one partition, and one inside what is already picked layout(p); - p[2].offset += ES; - refused(p, 4, FLASH, "mtd2 starts at"); + p[4] = p[2]; + p[4].num = 4; + planned(p, 5, (int[]){0, 1, 2, 3}, 4); + p[4].offset = p[2].offset + ES; + p[4].size = ES; + planned(p, 5, (int[]){0, 1, 2, 3}, 4); + + // The sizes add up, and only the offsets show these two share flash + layout(p); + p[2].offset -= ES; + p[3].offset += ES; + refused(p, 4, FLASH, "mtd2 overlaps"); + layout(p); + p[3].offset = p[2].offset; + p[3].offset += p[2].size - ES; + refused(p, 4, FLASH, "mtd3 overlaps"); layout(p); p[1].size += 1; - refused(p, 4, FLASH, "erase blocks"); + p[2].offset += 1; + p[3].offset += 1; + refused(p, 4, FLASH + 1, "erase blocks"); layout(p); p[3].erasesize = 0x1000; refused(p, 4, FLASH, "erase blocks"); diff --git a/src/tools.h b/src/tools.h index ca67e387..05d9b172 100644 --- a/src/tools.h +++ b/src/tools.h @@ -61,6 +61,8 @@ bool dts_items_by_regex(const char *filename, const char *re, char *outbuf, bool line_from_file(const char *filename, const char *re, char *outbuf, size_t outlen); int dmesg(); +// The kernel log, malloc()ed and not NUL-terminated; its length, or <= 0 +ssize_t get_dmesg_buf(char **buf); uint32_t read_le32(const char *ptr); char *file_to_buf(const char *filename, size_t *len); char *fread_to_buf(const char *filename, size_t *len, uint32_t round_up,