From 2d380c80cc68cf0fcb2e62d90c2736d95dc82f08 Mon Sep 17 00:00:00 2001 From: AI Dev Date: Fri, 9 Oct 2026 13:59:29 +0000 Subject: [PATCH 1/4] backup: stream UBI volumes instead of holding them in RAM `ipctool upload` with a backup on a Novatek NT98566 with 128 MB of NAND refused with "Not sent: 1 partition(s) of the flash could not be read". Its rootfs is a 62.5 MB UBI volume, and the backup malloc()ed every UBI volume whole and held them all at once -- more than the camera has free. The sha1s in the same report were fine, because those stream. - A UBI volume is now a span with a path: http_post() and save_file() read it in 64 KB pieces while sending or writing. Nothing else changes in the body or the file. A file that ends short of its length stops the upload with ERR_SOURCE, since the Content-Length is already sent. - A partition that cannot be read is named, with the reason ("rootfs (UBI volume rootfs, /dev/ubi0_0): ..."), instead of only being counted. "No partition found" is no longer reported as "1 partition could not be read". - read_ubi_volume() is gone, with nothing left calling it. - http_test: a file-backed span arrives whole between in-memory ones, and a short file is ERR_SOURCE. --- src/backup.c | 54 ++++++++++++++++++++++++++++++++++++++++++------ src/http.c | 40 ++++++++++++++++++++++++++++++++--- src/http.h | 8 ++++++- src/http_test.c | 28 +++++++++++++++++++++++++ src/mtd.c | 28 ------------------------- src/mtd.h | 2 -- src/report.c | 3 +++ src/report_cmd.c | 15 ++++++++++++-- 8 files changed, 136 insertions(+), 42 deletions(-) diff --git a/src/backup.c b/src/backup.c index e86d51ff..8e175e03 100644 --- a/src/backup.c +++ b/src/backup.c @@ -58,36 +58,56 @@ static bool cb_mtd_backup(int i, const char *name, struct mtd_info_user *mtd, int nvols = enum_ubi_volumes(ubi_num, vols, MAX_UBI_VOLS); for (int v = 0; v < nvols; v++) { if (c->count == c->cap) { + fprintf(stderr, " %s: more partitions than ipctool backs up\n", + name); c->missed++; continue; } - size_t out_len = 0; - char *buf = read_ubi_volume(ubi_num, vols[v].vol_id, - vols[v].data_bytes, &out_len); - if (!buf) { + /* Streamed from the volume as it is sent, not read into RAM: a + * NAND camera's rootfs volume is tens of MB, more than such a + * camera has free, and malloc() failing on it was the whole + * backup refused as "1 partition could not be read". */ + char devpath[64]; + snprintf(devpath, sizeof(devpath), "/dev/ubi%d_%d", ubi_num, + vols[v].vol_id); + int fd = open(devpath, O_RDONLY); + if (fd < 0) { + fprintf(stderr, " %s (UBI volume %s, %s): %s\n", name, + vols[v].name, devpath, strerror(errno)); c->missed++; continue; } - c->blocks[c->count].data = buf; - c->blocks[c->count].len = out_len; + close(fd); + char *path = strdup(devpath); + if (!path) { + c->missed++; + continue; + } + c->blocks[c->count].data = NULL; + c->blocks[c->count].len = vols[v].data_bytes; + c->blocks[c->count].path = path; c->count++; } return true; } if (c->count == c->cap) { + fprintf(stderr, " %s: more partitions than ipctool backs up\n", name); c->missed++; return true; } int fd; char *addr = open_mtdblock(i, &fd, mtd->size, 0); if (!addr) { + fprintf(stderr, " %s (/dev/mtdblock%d): %s\n", name, i, + strerror(errno)); c->missed++; return true; } c->blocks[c->count].data = addr; c->blocks[c->count].len = mtd->size; + c->blocks[c->count].path = NULL; c->count++; return true; } @@ -127,6 +147,27 @@ int save_file(const char *filename, span_t blocks[MAX_MTDBLOCKS + 1], fwrite(&len_header, 1, sizeof(len_header), fp); } + if (blocks[i].path) { + // A UBI volume, streamed as the upload streams it. + static char piece[65536]; + FILE *src = fopen(blocks[i].path, "rb"); + size_t left = blocks[i].len; + while (src && left) { + size_t n = fread( + piece, 1, left < sizeof(piece) ? left : sizeof(piece), src); + if (!n || fwrite(piece, 1, n, fp) != n) + break; + left -= n; + } + if (src) + fclose(src); + if (left) { + fprintf(stderr, "Error reading %s, aborting\n", blocks[i].path); + fclose(fp); + return 1; + } + continue; + } int nbytes = fwrite(blocks[i].data, 1, blocks[i].len, fp); if (nbytes == -1) break; @@ -144,6 +185,7 @@ size_t backup_blocks(const char *yaml, size_t yaml_len, span_t *blocks, size_t *missed) { blocks[0].data = yaml; blocks[0].len = yaml_len + 1; // end string data with \0 + blocks[0].path = NULL; return map_mtdblocks(blocks + 1, MAX_MTDBLOCKS, missed) + 1; } diff --git a/src/http.c b/src/http.c index 1cd02c4a..e0c37d40 100644 --- a/src/http.c +++ b/src/http.c @@ -251,13 +251,45 @@ int http_post(const char *hostname, int port, const char *path, nservers_t *ns, size_t sent = 0; int shown = -1; + static char piece[65536]; for (size_t i = 0; i < nspans; i++) { + int src = -1; + if (spans[i].path && (src = open(spans[i].path, O_RDONLY)) < 0) { + fprintf(stderr, "\nCould not open %s: %s\n", spans[i].path, + strerror(errno)); + close(s); + return ERR_SOURCE; + } /* In 64 KB pieces, so a 16 MB partition shows its progress. */ for (size_t off = 0; off < spans[i].len;) { size_t chunk = spans[i].len - off; - if (chunk > 65536) - chunk = 65536; - if (write_all(s, spans[i].data + off, chunk)) { + if (chunk > sizeof(piece)) + chunk = sizeof(piece); + const char *from = spans[i].data + off; + if (src >= 0) { + size_t got = 0; + while (got < chunk) { + ssize_t n = read(src, piece + got, chunk - got); + if (n < 0 && errno == EINTR) + continue; + if (n <= 0) { + // The length was promised in Content-Length: a + // short body is a broken one, so stop here. + fprintf(stderr, "\n%s ended at %zu of %zu bytes%s%s\n", + spans[i].path, (size_t)(off + got), + spans[i].len, n < 0 ? ": " : "", + n < 0 ? strerror(errno) : ""); + close(src); + close(s); + return ERR_SOURCE; + } + got += (size_t)n; + } + from = piece; + } + if (write_all(s, from, chunk)) { + if (src >= 0) + close(src); // The server may have answered and closed: read what it // said, so a refusal reaches the user as a reason. Or the // body stopped moving, and nothing is coming back either. @@ -278,6 +310,8 @@ int http_post(const char *hostname, int port, const char *path, nservers_t *ns, } } } + if (src >= 0) + close(src); } if (shown >= 0) fprintf(stderr, "\n"); diff --git a/src/http.h b/src/http.h index 9f2db2c8..aa25f3eb 100644 --- a/src/http.h +++ b/src/http.h @@ -9,9 +9,13 @@ #define MAX_MTDBLOCKS 20 +/* len bytes: at data, or -- when path is set -- read from that file while + * they are sent, for what is too big to hold in RAM (a UBI volume of tens of + * MB on a camera with less than that free). */ typedef struct { const char *data; size_t len; + const char *path; } span_t; #define HTTP_ERR(err) ((int)err < 0 && (int)err > -100 ? (-(int)err) : 0) @@ -24,6 +28,7 @@ typedef struct { #define ERR_HTTP 6 #define ERR_MALLOC 7 #define ERR_STALLED 8 +#define ERR_SOURCE 9 #define ERR_BUTT 10 /* The upload's timing, variables so http_test can shorten them: how long a @@ -36,7 +41,8 @@ extern int http_late_answer_s; /* POST spans as one body of total bytes; resp gets the answer's body, status * its HTTP status, location its Location header ("" without one). 0, or one - * of the ERR_ codes above: ERR_STALLED when the body stopped moving and no + * of the ERR_ codes above: ERR_SOURCE when a span's file could not be read + * whole (http_post says which), ERR_STALLED when the body stopped moving and no * answer came. An answer that arrives before the body is sent -- a redirect * or a refusal on the headers alone -- is taken without sending the body. */ int http_post(const char *hostname, int port, const char *path, nservers_t *ns, diff --git a/src/http_test.c b/src/http_test.c index 20f8df3e..634606a7 100644 --- a/src/http_test.c +++ b/src/http_test.c @@ -173,6 +173,34 @@ int main(void) { status); CHECK(reaped(cont) == 16, "after a 100 the body did not arrive whole"); + // A span read from a file as it is sent -- a UBI volume too big for RAM + // -- arrives whole, between spans held in memory. + char tmpl[] = "/tmp/http_test.XXXXXX"; + int tf = mkstemp(tmpl); + CHECK(tf >= 0 && write(tf, big, total) == (ssize_t)total, "temp file"); + close(tf); + span_t mixed[3] = { + {big, 1u << 20, NULL}, {NULL, total, tmpl}, {big, 1u << 20, NULL}}; + int file_port; + pid_t fil = spawn(ACCEPT, &file_port, 0); + err = http_post("127.0.0.1", file_port, "/", &ns, + "application/octet-stream", mixed, 3, total + (2u << 20), + resp, sizeof(resp), &status, loc, sizeof(loc)); + CHECK(!err && status == 201, "file span: err %d, status %d", err, status); + CHECK(reaped(fil) == 18, "the file span did not arrive whole"); + + // A file shorter than its span promised stops the upload: the + // Content-Length is already sent, and a short body is a broken one. + span_t longer = {NULL, total + 1, tmpl}; + int short_port; + pid_t sho = spawn(ACCEPT, &short_port, 0); + err = http_post("127.0.0.1", short_port, "/", &ns, + "application/octet-stream", &longer, 1, total + 1, resp, + sizeof(resp), &status, loc, sizeof(loc)); + CHECK(err == ERR_SOURCE, "a short file: err %d", err); + reaped(sho); + unlink(tmpl); + // A server that never reads: a stall, in the stall timeout plus the late // answer's, not the minutes it used to take. int freeze_port; diff --git a/src/mtd.c b/src/mtd.c index 64398a51..7db99b77 100644 --- a/src/mtd.c +++ b/src/mtd.c @@ -203,34 +203,6 @@ int enum_ubi_volumes(int ubi_num, ubi_vol_info_t *vols, int max_vols) { return count; } -char *read_ubi_volume(int ubi_num, int vol_id, size_t data_bytes, - size_t *out_len) { - char devpath[64]; - snprintf(devpath, sizeof(devpath), "/dev/ubi%d_%d", ubi_num, vol_id); - - int fd = open(devpath, O_RDONLY); - if (fd == -1) - return NULL; - - char *buf = malloc(data_bytes); - if (!buf) { - close(fd); - return NULL; - } - - size_t total = 0; - while (total < data_bytes) { - ssize_t n = read(fd, buf + total, data_bytes - total); - if (n <= 0) - break; - total += n; - } - close(fd); - - *out_len = total; - return buf; -} - bool sha1_ubi_volume(int ubi_num, int vol_id, size_t data_bytes, unsigned char digest[20], size_t *out_len) { char devpath[64]; diff --git a/src/mtd.h b/src/mtd.h index 4e8e96f6..f3459764 100644 --- a/src/mtd.h +++ b/src/mtd.h @@ -65,8 +65,6 @@ typedef struct { int find_ubi_for_mtd(int mtd_num); int enum_ubi_volumes(int ubi_num, ubi_vol_info_t *vols, int max_vols); -char *read_ubi_volume(int ubi_num, int vol_id, size_t data_bytes, - size_t *out_len); bool sha1_ubi_volume(int ubi_num, int vol_id, size_t data_bytes, unsigned char digest[20], size_t *out_len); diff --git a/src/report.c b/src/report.c index b04c0930..e699aa19 100644 --- a/src/report.c +++ b/src/report.c @@ -13,6 +13,7 @@ static void add_span(report_body_t *b, const char *data, size_t len) { } b->spans[b->nspans].data = data; b->spans[b->nspans].len = len; + b->spans[b->nspans].path = NULL; b->nspans++; b->total += len; } @@ -74,6 +75,8 @@ void report_add_backup(report_body_t *b, const span_t *blocks, size_t n) { add_span(b, (const char *)le, 4); } add_span(b, blocks[i].data, blocks[i].len); + if (!b->overflow) + b->spans[b->nspans - 1].path = blocks[i].path; } add_span(b, crlf, 2); } diff --git a/src/report_cmd.c b/src/report_cmd.c index a05f88d4..2227010e 100644 --- a/src/report_cmd.c +++ b/src/report_cmd.c @@ -195,15 +195,22 @@ int report_cmd(int argc, char **argv) { } if (with_backup) { size_t missed = 0; + // Each partition that cannot be read is named, with why, as it is met. nblocks = backup_blocks(yaml, yaml_len, blocks, &missed); for (size_t i = 1; i < nblocks; i++) flash += blocks[i].len; - if (missed || nblocks < 2) { + if (nblocks < 2 && !missed) { + fprintf(stderr, "Not sent: no partition of the flash was found to " + "back up.\n"); + free(yaml); + return EXIT_FAILURE; + } + if (missed) { fprintf(stderr, "Not sent: %zu partition(s) of the flash could not be " "read, and a backup missing any of them is not the whole " "flash.\n", - missed ? missed : (size_t)1); + missed); free(yaml); return EXIT_FAILURE; } @@ -306,6 +313,10 @@ int report_cmd(int argc, char **argv) { } free(body); free(yaml); + if (err == ERR_SOURCE) { + fprintf(stderr, "Not sent whole, so nothing was stored.\n"); + return EXIT_FAILURE; + } if (err == ERR_STALLED) { fprintf(stderr, "The upload to %s stopped moving and no answer came. Nothing " From 7362c3839022bf99de1f537ed96e289597b5f04c Mon Sep 17 00:00:00 2001 From: AI Dev Date: Fri, 9 Oct 2026 14:04:23 +0000 Subject: [PATCH 2/4] backup: Qodo on #235 -- no arithmetic on a file span's NULL data; a failed write is named as one --- src/backup.c | 14 +++++++++++--- src/http.c | 7 ++++--- 2 files changed, 15 insertions(+), 6 deletions(-) diff --git a/src/backup.c b/src/backup.c index 8e175e03..f34e07b9 100644 --- a/src/backup.c +++ b/src/backup.c @@ -152,17 +152,25 @@ int save_file(const char *filename, span_t blocks[MAX_MTDBLOCKS + 1], static char piece[65536]; FILE *src = fopen(blocks[i].path, "rb"); size_t left = blocks[i].len; + const char *failed = src ? NULL : blocks[i].path; while (src && left) { size_t n = fread( piece, 1, left < sizeof(piece) ? left : sizeof(piece), src); - if (!n || fwrite(piece, 1, n, fp) != n) + if (!n) { + failed = blocks[i].path; break; + } + if (fwrite(piece, 1, n, fp) != n) { + failed = filename; + break; + } left -= n; } if (src) fclose(src); - if (left) { - fprintf(stderr, "Error reading %s, aborting\n", blocks[i].path); + if (failed) { + fprintf(stderr, "Error %s '%s', aborting\n", + failed == filename ? "writing" : "reading", failed); fclose(fp); return 1; } diff --git a/src/http.c b/src/http.c index e0c37d40..883f329d 100644 --- a/src/http.c +++ b/src/http.c @@ -265,8 +265,10 @@ int http_post(const char *hostname, int port, const char *path, nservers_t *ns, size_t chunk = spans[i].len - off; if (chunk > sizeof(piece)) chunk = sizeof(piece); - const char *from = spans[i].data + off; - if (src >= 0) { + const char *from = piece; + if (src < 0) { + from = spans[i].data + off; + } else { size_t got = 0; while (got < chunk) { ssize_t n = read(src, piece + got, chunk - got); @@ -285,7 +287,6 @@ int http_post(const char *hostname, int port, const char *path, nservers_t *ns, } got += (size_t)n; } - from = piece; } if (write_all(s, from, chunk)) { if (src >= 0) From 35f75d5c1e092b542525293e847bf6c376ce272e Mon Sep 17 00:00:00 2001 From: AI Dev Date: Fri, 9 Oct 2026 14:22:42 +0000 Subject: [PATCH 3/4] mtd: leave gluebi's UBI-volume devices out of the partitions On a gk7205v510 with NAND, each UBI volume is also an MTD device (gluebi: mtd3 kernel, mtd4 rootfs, mtd5 rootfs_data). The ones not mounted opened here and were listed as partitions after the UBI partition that already holds them: the report showed `kernel` twice and the backup carried it twice. The mounted ones refuse O_RDWR and were skipped. So what appeared depended on what was mounted. They are MTD_UBIVOLUME, not flash, and are now skipped for every consumer. --- src/mtd.c | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/mtd.c b/src/mtd.c index 7db99b77..36f868f0 100644 --- a/src/mtd.c +++ b/src/mtd.c @@ -29,6 +29,9 @@ #define MTD_NORFLASH 3 #define MTD_NANDFLASH 4 +// A UBI volume shown as an MTD device by gluebi: not flash, a second view of +// what its UBI partition holds. +#define MTD_UBIVOLUME 7 #define MAX_MPOINTS 10 #define MPOINT_LEN 90 @@ -411,7 +414,12 @@ void enum_mtd_info(void *ctx, cb_mtd cb) { if (devfd < 0) goto skip; - if (ioctl(devfd, MEMGETINFO, &mtds[n].mtd) >= 0) + /* gluebi's devices are skipped: listing them showed a + * volume twice in the report and put it twice in a backup + * -- only the volumes not mounted, since a mounted one + * refuses O_RDWR here (gk7205v510 NAND, 2026-10). */ + if (ioctl(devfd, MEMGETINFO, &mtds[n].mtd) >= 0 && + mtds[n].mtd.type != MTD_UBIVOLUME) mtds[n].valid = true; close(devfd); From d4ffa7d68d18ef93c0abe4eb9287e2ab6e12397e Mon Sep 17 00:00:00 2001 From: AI Dev Date: Fri, 9 Oct 2026 14:26:39 +0000 Subject: [PATCH 4/4] mtd: all twelve of Novatek's partitions, and not its "all" The NT98566 in #234 has twelve MTD partitions; /proc/mtd was read up to ten, so "encrypt" (4 MB at 0x7c00000) and "all" were dropped without a word. The report said 124M for a 128 MB chip, and a backup would have lacked "encrypt" while calling itself whole. MAX_MTD is now MAX_MTDBLOCKS (20; restore indexes partitions by number up to that). "all" -- 0 to the chip's end, after the partitions that cover it -- is another name for the flash, and backing it up would put the chip in twice. mtd_mark_aliases() leaves out a partition that contains another whole, by the offsets in /sys/class/mtd/mtdN/offset, or, without them, one whose size is the sum of all the others'. mtd_alias_test holds it to #234's layout both ways, and to a NOR layout where nothing is an alias. On the lab gk7205v510 NAND and hi3516ev300 NOR the report and backup are unchanged. --- .github/workflows/pr-build-check.yml | 3 +- CMakeLists.txt | 6 +++ src/mtd.c | 29 +++++++++++++- src/mtd_alias.c | 24 +++++++++++ src/mtd_alias.h | 15 +++++++ src/mtd_alias_test.c | 60 ++++++++++++++++++++++++++++ 6 files changed, 135 insertions(+), 2 deletions(-) create mode 100644 src/mtd_alias.c create mode 100644 src/mtd_alias.h create mode 100644 src/mtd_alias_test.c diff --git a/.github/workflows/pr-build-check.yml b/.github/workflows/pr-build-check.yml index 24a0053d..ab1cd937 100644 --- a/.github/workflows/pr-build-check.yml +++ b/.github/workflows/pr-build-check.yml @@ -140,10 +140,11 @@ jobs: cmake -H. -Bbuild -DCMAKE_BUILD_TYPE=Release cmake --build build --target cYAML_test reginfo_test longse_test \ sensors_test anyka_test hal_hisi_test \ - report_test http_test + report_test http_test mtd_alias_test ./build/cYAML_test ./build/report_test ./build/http_test + ./build/mtd_alias_test ./build/reginfo_test ./build/longse_test ./build/anyka_test diff --git a/CMakeLists.txt b/CMakeLists.txt index 901145c7..dcc69e07 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -351,6 +351,8 @@ set(IPCTOOL_SRC src/membw.h src/mtd.c src/mtd.h + src/mtd_alias.c + src/mtd_alias.h src/network.c src/network.h src/ptrace.c @@ -506,4 +508,8 @@ if(NOT ONLY_LIBRARY) add_executable(http_test src/http_test.c src/http.c src/dns.c src/report.c src/cjson/cJSON.c) target_include_directories(http_test PRIVATE include src) + + # Which partitions are another name for the chip ("all" on Novatek). + add_executable(mtd_alias_test src/mtd_alias_test.c src/mtd_alias.c) + target_include_directories(mtd_alias_test PRIVATE include src) endif() diff --git a/src/mtd.c b/src/mtd.c index 36f868f0..fcba9f1e 100644 --- a/src/mtd.c +++ b/src/mtd.c @@ -18,7 +18,9 @@ #include "chipid.h" #include "hal/common.h" #include "hal/hisi/hal_hisi.h" +#include "http.h" // MAX_MTDBLOCKS #include "mtd.h" +#include "mtd_alias.h" #include "sha1.h" #include "tools.h" #include "uboot.h" @@ -390,7 +392,9 @@ static bool cb_mtd_info(int i, const char *name, struct mtd_info_user *mtd, return true; } -#define MAX_MTD 10 +/* Novatek has twelve partitions (ipctool#234); 10 dropped the last two + * without a word. No more than MAX_MTDBLOCKS: restore indexes by number. */ +#define MAX_MTD MAX_MTDBLOCKS struct mtd_entry { int i; @@ -455,6 +459,29 @@ void enum_mtd_info(void *ctx, cb_mtd cb) { mtds[1] = tmp; } + // A partition that is another name for others -- "all" -- is left out. + uint64_t offs[MAX_MTD], sizes[MAX_MTD]; + bool has_off[MAX_MTD], alias[MAX_MTD]; + int idx[MAX_MTD], m = 0; + 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; + sizes[m] = mtds[i].mtd.size; + idx[m++] = i; + } + mtd_mark_aliases(m, offs, has_off, sizes, alias); + for (int k = 0; k < m; k++) + if (alias[k]) + mtds[idx[k]].valid = false; + for (int i = 0; i < n; i++) { if (mtds[i].valid && !cb(mtds[i].i, mtds[i].name, &mtds[i].mtd, ctx)) break; diff --git a/src/mtd_alias.c b/src/mtd_alias.c new file mode 100644 index 00000000..43b2393c --- /dev/null +++ b/src/mtd_alias.c @@ -0,0 +1,24 @@ +#include "mtd_alias.h" + +void mtd_mark_aliases(int n, const uint64_t *off, const bool *has_off, + const uint64_t *size, bool *alias) { + for (int i = 0; i < n; i++) { + alias[i] = false; + if (has_off[i]) { + for (int j = 0; j < n; j++) { + if (j == i || !has_off[j] || size[j] >= size[i]) + continue; + if (off[j] >= off[i] && off[j] + size[j] <= off[i] + size[i]) { + alias[i] = true; + break; + } + } + continue; + } + uint64_t others = 0; + for (int j = 0; j < n; j++) + if (j != i) + others += size[j]; + alias[i] = n > 1 && others == size[i]; + } +} diff --git a/src/mtd_alias.h b/src/mtd_alias.h new file mode 100644 index 00000000..ca1834f6 --- /dev/null +++ b/src/mtd_alias.h @@ -0,0 +1,15 @@ +#ifndef MTD_ALIAS_H +#define MTD_ALIAS_H + +#include +#include + +/* Marks alias[i] for each partition that is another name for flash the + * others already cover -- Novatek's "all", 0..chip size, after "loader" to + * "encrypt". Where the kernel gives offsets (has_off), a partition that + * contains another one whole is the alias. Without them, one whose size is + * the sum of all the others' is. Backing it up would put the chip in twice. */ +void mtd_mark_aliases(int n, const uint64_t *off, const bool *has_off, + const uint64_t *size, bool *alias); + +#endif /* MTD_ALIAS_H */ diff --git a/src/mtd_alias_test.c b/src/mtd_alias_test.c new file mode 100644 index 00000000..b35a2c23 --- /dev/null +++ b/src/mtd_alias_test.c @@ -0,0 +1,60 @@ +/* mtd_alias_test: which partitions are another name for the whole chip. */ +#include + +#include "mtd_alias.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) + +int main(void) { + // Novatek NT98566 on 128 MB NAND (ipctool#234): twelve partitions, the + // last one "all". + uint64_t off[12] = {0, 0x40000, 0x80000, 0xc0000, + 0x2c0000, 0x2e0000, 0x300000, 0x800000, + 0x6000000, 0x7000000, 0x7c00000, 0}; + uint64_t size[12] = {0x40000, 0x40000, 0x40000, 0x200000, + 0x20000, 0x20000, 0x500000, 0x5800000, + 0x1000000, 0xc00000, 0x400000, 0x8000000}; + bool has[12], alias[12]; + for (int i = 0; i < 12; i++) + has[i] = true; + mtd_mark_aliases(12, off, has, size, alias); + for (int i = 0; i < 11; i++) + CHECK(!alias[i], "partition %d called an alias", i); + CHECK(alias[11], "\"all\" not called an alias"); + + // The same without offsets (an old kernel): by size. + for (int i = 0; i < 12; i++) + has[i] = false; + mtd_mark_aliases(12, off, has, size, alias); + for (int i = 0; i < 11; i++) + CHECK(!alias[i], "by size: partition %d called an alias", i); + CHECK(alias[11], "by size: \"all\" not called an alias"); + + // An ordinary NOR layout: nothing is an alias, even where one partition + // is the size of the others combined by chance with offsets known. + uint64_t noff[4] = {0, 0x40000, 0x50000, 0x250000}; + uint64_t nsize[4] = {0x40000, 0x10000, 0x200000, 0x250000}; + bool nhas[4] = {true, true, true, true}, nalias[4]; + mtd_mark_aliases(4, noff, nhas, nsize, nalias); + for (int i = 0; i < 4; i++) + CHECK(!nalias[i], "NOR partition %d called an alias", i); + + // One partition alone is never an alias. + uint64_t one = 0x1000000, zero = 0; + bool f = false, a; + mtd_mark_aliases(1, &zero, &f, &one, &a); + CHECK(!a, "a lone partition called an alias"); + + if (!failed) + printf("mtd_alias_test: ok\n"); + return failed; +}