bcm4908img: detect Linksys images
[openwrt/staging/mkresin.git] / package / utils / bcm4908img / src / bcm4908img.c
1 // SPDX-License-Identifier: GPL-2.0-or-later
2 /*
3 * Copyright (C) 2021 Rafał Miłecki <rafal@milecki.pl>
4 */
5
6 #include <byteswap.h>
7 #include <endian.h>
8 #include <errno.h>
9 #include <stdbool.h>
10 #include <stddef.h>
11 #include <stdint.h>
12 #include <stdio.h>
13 #include <stdlib.h>
14 #include <string.h>
15 #include <sys/stat.h>
16 #include <unistd.h>
17
18 #if !defined(__BYTE_ORDER)
19 #error "Unknown byte order"
20 #endif
21
22 #if __BYTE_ORDER == __BIG_ENDIAN
23 #define cpu_to_le32(x) bswap_32(x)
24 #define le32_to_cpu(x) bswap_32(x)
25 #define cpu_to_be32(x) (x)
26 #define be32_to_cpu(x) (x)
27 #define cpu_to_le16(x) bswap_16(x)
28 #define le16_to_cpu(x) bswap_16(x)
29 #define cpu_to_be16(x) (x)
30 #define be16_to_cpu(x) (x)
31 #elif __BYTE_ORDER == __LITTLE_ENDIAN
32 #define cpu_to_le32(x) (x)
33 #define le32_to_cpu(x) (x)
34 #define cpu_to_be32(x) bswap_32(x)
35 #define be32_to_cpu(x) bswap_32(x)
36 #define cpu_to_le16(x) (x)
37 #define le16_to_cpu(x) (x)
38 #define cpu_to_be16(x) bswap_16(x)
39 #define be16_to_cpu(x) bswap_16(x)
40 #else
41 #error "Unsupported endianness"
42 #endif
43
44 #define WFI_VERSION 0x00005732
45 #define WFI_VERSION_NAND_1MB_DATA 0x00005731
46
47 #define WFI_NOR_FLASH 1
48 #define WFI_NAND16_FLASH 2
49 #define WFI_NAND128_FLASH 3
50 #define WFI_NAND256_FLASH 4
51 #define WFI_NAND512_FLASH 5
52 #define WFI_NAND1024_FLASH 6
53 #define WFI_NAND2048_FLASH 7
54
55 #define WFI_FLAG_HAS_PMC 0x1
56 #define WFI_FLAG_SUPPORTS_BTRM 0x2
57
58 #define UBI_EC_HDR_MAGIC 0x55424923
59
60 static int debug;
61
62 struct bcm4908img_tail {
63 uint32_t crc32;
64 uint32_t version;
65 uint32_t chip_id;
66 uint32_t flash_type;
67 uint32_t flags;
68 };
69
70 /**
71 * struct bcm4908img_info - info about BCM4908 image
72 *
73 * Standard BCM4908 image consists of:
74 * 1. (Optional) vedor header
75 * 2. (Optional) cferom
76 * 3. bootfs ─┐
77 * 4. padding ├─ firmware
78 * 5. rootfs ─┘
79 * 6. BCM4908 tail
80 * 7. (Optional) vendor tail
81 */
82 struct bcm4908img_info {
83 size_t cferom_offset;
84 size_t bootfs_offset;
85 size_t padding_offset;
86 size_t rootfs_offset;
87 size_t tail_offset;
88 uint32_t crc32; /* Calculated checksum */
89 struct bcm4908img_tail tail;
90 };
91
92 char *pathname;
93
94 static inline size_t bcm4908img_min(size_t x, size_t y) {
95 return x < y ? x : y;
96 }
97
98 /**************************************************
99 * CRC32
100 **************************************************/
101
102 static const uint32_t crc32_tbl[] = {
103 0x00000000, 0x77073096, 0xee0e612c, 0x990951ba,
104 0x076dc419, 0x706af48f, 0xe963a535, 0x9e6495a3,
105 0x0edb8832, 0x79dcb8a4, 0xe0d5e91e, 0x97d2d988,
106 0x09b64c2b, 0x7eb17cbd, 0xe7b82d07, 0x90bf1d91,
107 0x1db71064, 0x6ab020f2, 0xf3b97148, 0x84be41de,
108 0x1adad47d, 0x6ddde4eb, 0xf4d4b551, 0x83d385c7,
109 0x136c9856, 0x646ba8c0, 0xfd62f97a, 0x8a65c9ec,
110 0x14015c4f, 0x63066cd9, 0xfa0f3d63, 0x8d080df5,
111 0x3b6e20c8, 0x4c69105e, 0xd56041e4, 0xa2677172,
112 0x3c03e4d1, 0x4b04d447, 0xd20d85fd, 0xa50ab56b,
113 0x35b5a8fa, 0x42b2986c, 0xdbbbc9d6, 0xacbcf940,
114 0x32d86ce3, 0x45df5c75, 0xdcd60dcf, 0xabd13d59,
115 0x26d930ac, 0x51de003a, 0xc8d75180, 0xbfd06116,
116 0x21b4f4b5, 0x56b3c423, 0xcfba9599, 0xb8bda50f,
117 0x2802b89e, 0x5f058808, 0xc60cd9b2, 0xb10be924,
118 0x2f6f7c87, 0x58684c11, 0xc1611dab, 0xb6662d3d,
119 0x76dc4190, 0x01db7106, 0x98d220bc, 0xefd5102a,
120 0x71b18589, 0x06b6b51f, 0x9fbfe4a5, 0xe8b8d433,
121 0x7807c9a2, 0x0f00f934, 0x9609a88e, 0xe10e9818,
122 0x7f6a0dbb, 0x086d3d2d, 0x91646c97, 0xe6635c01,
123 0x6b6b51f4, 0x1c6c6162, 0x856530d8, 0xf262004e,
124 0x6c0695ed, 0x1b01a57b, 0x8208f4c1, 0xf50fc457,
125 0x65b0d9c6, 0x12b7e950, 0x8bbeb8ea, 0xfcb9887c,
126 0x62dd1ddf, 0x15da2d49, 0x8cd37cf3, 0xfbd44c65,
127 0x4db26158, 0x3ab551ce, 0xa3bc0074, 0xd4bb30e2,
128 0x4adfa541, 0x3dd895d7, 0xa4d1c46d, 0xd3d6f4fb,
129 0x4369e96a, 0x346ed9fc, 0xad678846, 0xda60b8d0,
130 0x44042d73, 0x33031de5, 0xaa0a4c5f, 0xdd0d7cc9,
131 0x5005713c, 0x270241aa, 0xbe0b1010, 0xc90c2086,
132 0x5768b525, 0x206f85b3, 0xb966d409, 0xce61e49f,
133 0x5edef90e, 0x29d9c998, 0xb0d09822, 0xc7d7a8b4,
134 0x59b33d17, 0x2eb40d81, 0xb7bd5c3b, 0xc0ba6cad,
135 0xedb88320, 0x9abfb3b6, 0x03b6e20c, 0x74b1d29a,
136 0xead54739, 0x9dd277af, 0x04db2615, 0x73dc1683,
137 0xe3630b12, 0x94643b84, 0x0d6d6a3e, 0x7a6a5aa8,
138 0xe40ecf0b, 0x9309ff9d, 0x0a00ae27, 0x7d079eb1,
139 0xf00f9344, 0x8708a3d2, 0x1e01f268, 0x6906c2fe,
140 0xf762575d, 0x806567cb, 0x196c3671, 0x6e6b06e7,
141 0xfed41b76, 0x89d32be0, 0x10da7a5a, 0x67dd4acc,
142 0xf9b9df6f, 0x8ebeeff9, 0x17b7be43, 0x60b08ed5,
143 0xd6d6a3e8, 0xa1d1937e, 0x38d8c2c4, 0x4fdff252,
144 0xd1bb67f1, 0xa6bc5767, 0x3fb506dd, 0x48b2364b,
145 0xd80d2bda, 0xaf0a1b4c, 0x36034af6, 0x41047a60,
146 0xdf60efc3, 0xa867df55, 0x316e8eef, 0x4669be79,
147 0xcb61b38c, 0xbc66831a, 0x256fd2a0, 0x5268e236,
148 0xcc0c7795, 0xbb0b4703, 0x220216b9, 0x5505262f,
149 0xc5ba3bbe, 0xb2bd0b28, 0x2bb45a92, 0x5cb36a04,
150 0xc2d7ffa7, 0xb5d0cf31, 0x2cd99e8b, 0x5bdeae1d,
151 0x9b64c2b0, 0xec63f226, 0x756aa39c, 0x026d930a,
152 0x9c0906a9, 0xeb0e363f, 0x72076785, 0x05005713,
153 0x95bf4a82, 0xe2b87a14, 0x7bb12bae, 0x0cb61b38,
154 0x92d28e9b, 0xe5d5be0d, 0x7cdcefb7, 0x0bdbdf21,
155 0x86d3d2d4, 0xf1d4e242, 0x68ddb3f8, 0x1fda836e,
156 0x81be16cd, 0xf6b9265b, 0x6fb077e1, 0x18b74777,
157 0x88085ae6, 0xff0f6a70, 0x66063bca, 0x11010b5c,
158 0x8f659eff, 0xf862ae69, 0x616bffd3, 0x166ccf45,
159 0xa00ae278, 0xd70dd2ee, 0x4e048354, 0x3903b3c2,
160 0xa7672661, 0xd06016f7, 0x4969474d, 0x3e6e77db,
161 0xaed16a4a, 0xd9d65adc, 0x40df0b66, 0x37d83bf0,
162 0xa9bcae53, 0xdebb9ec5, 0x47b2cf7f, 0x30b5ffe9,
163 0xbdbdf21c, 0xcabac28a, 0x53b39330, 0x24b4a3a6,
164 0xbad03605, 0xcdd70693, 0x54de5729, 0x23d967bf,
165 0xb3667a2e, 0xc4614ab8, 0x5d681b02, 0x2a6f2b94,
166 0xb40bbe37, 0xc30c8ea1, 0x5a05df1b, 0x2d02ef8d,
167 };
168
169 uint32_t bcm4908img_crc32(uint32_t crc, const void *buf, size_t len) {
170 const uint8_t *in = buf;
171
172 while (len) {
173 crc = crc32_tbl[(crc ^ *in) & 0xff] ^ (crc >> 8);
174 in++;
175 len--;
176 }
177
178 return crc;
179 }
180
181 /**************************************************
182 * Helpers
183 **************************************************/
184
185 static FILE *bcm4908img_open(const char *pathname, const char *mode) {
186 struct stat st;
187
188 if (pathname)
189 return fopen(pathname, mode);
190
191 if (isatty(fileno(stdin))) {
192 fprintf(stderr, "Reading from TTY stdin is unsupported\n");
193 return NULL;
194 }
195
196 if (fstat(fileno(stdin), &st)) {
197 fprintf(stderr, "Failed to fstat stdin: %d\n", -errno);
198 return NULL;
199 }
200
201 if (S_ISFIFO(st.st_mode)) {
202 fprintf(stderr, "Reading from pipe stdin is unsupported\n");
203 return NULL;
204 }
205
206 return stdin;
207 }
208
209 static void bcm4908img_close(FILE *fp) {
210 if (fp != stdin)
211 fclose(fp);
212 }
213
214 static int bcm4908img_calc_crc32(FILE *fp, struct bcm4908img_info *info) {
215 uint8_t buf[1024];
216 size_t length;
217 size_t bytes;
218
219 /* Start with cferom (or bootfs) - skip vendor header */
220 fseek(fp, info->cferom_offset, SEEK_SET);
221
222 info->crc32 = 0xffffffff;
223 length = info->tail_offset - info->cferom_offset;
224 while (length && (bytes = fread(buf, 1, bcm4908img_min(sizeof(buf), length), fp)) > 0) {
225 info->crc32 = bcm4908img_crc32(info->crc32, buf, bytes);
226 length -= bytes;
227 }
228 if (length) {
229 fprintf(stderr, "Failed to read last %zd B of data\n", length);
230 return -EIO;
231 }
232
233 return 0;
234 }
235
236 /**************************************************
237 * Existing firmware parser
238 **************************************************/
239
240 struct chk_header {
241 uint32_t magic;
242 uint32_t header_len;
243 uint8_t reserved[8];
244 uint32_t kernel_chksum;
245 uint32_t rootfs_chksum;
246 uint32_t kernel_len;
247 uint32_t rootfs_len;
248 uint32_t image_chksum;
249 uint32_t header_chksum;
250 char board_id[0];
251 };
252
253 struct linksys_tail {
254 char magic[9];
255 uint8_t version[8];
256 char model[15];
257 uint32_t crc32;
258 uint8_t padding[9];
259 uint8_t signature[16];
260 uint8_t reserved[192];
261 };
262
263 static bool bcm4908img_is_all_ff(const void *buf, size_t length)
264 {
265 const uint8_t *in = buf;
266 int i;
267
268 for (i = 0; i < length; i++) {
269 if (in[i] != 0xff)
270 return false;
271 }
272
273 return true;
274 }
275
276 static int bcm4908img_parse(FILE *fp, struct bcm4908img_info *info) {
277 struct bcm4908img_tail *tail = &info->tail;
278 struct linksys_tail *linksys;
279 struct chk_header *chk;
280 struct stat st;
281 uint8_t buf[1024];
282 size_t file_size;
283 uint16_t tmp16;
284 size_t length;
285 size_t bytes;
286 int err = 0;
287
288 memset(info, 0, sizeof(*info));
289
290 /* File size */
291
292 if (fstat(fileno(fp), &st)) {
293 err = -errno;
294 fprintf(stderr, "Failed to fstat: %d\n", err);
295 return err;
296 }
297 file_size = st.st_size;
298
299 info->tail_offset = file_size - sizeof(*tail);
300
301 /* Vendor formats */
302
303 rewind(fp);
304 if (fread(buf, 1, sizeof(buf), fp) != sizeof(buf)) {
305 fprintf(stderr, "Failed to read file header\n");
306 return -EIO;
307 }
308 chk = (void *)buf;
309 if (be32_to_cpu(chk->magic) == 0x2a23245e)
310 info->cferom_offset = be32_to_cpu(chk->header_len);
311
312 fseek(fp, -sizeof(buf), SEEK_END);
313 if (fread(buf, 1, sizeof(buf), fp) != sizeof(buf)) {
314 fprintf(stderr, "Failed to read file header\n");
315 return -EIO;
316 }
317 linksys = (void *)(buf + sizeof(buf) - sizeof(*linksys));
318 if (!memcmp(linksys->magic, ".LINKSYS.", sizeof(linksys->magic))) {
319 info->tail_offset -= sizeof(*linksys);
320 }
321
322 /* Offsets */
323
324 for (info->bootfs_offset = info->cferom_offset;
325 info->bootfs_offset < info->tail_offset;
326 info->bootfs_offset += 0x20000) {
327 if (fseek(fp, info->bootfs_offset, SEEK_SET)) {
328 err = -errno;
329 fprintf(stderr, "Failed to fseek to the 0x%zx\n", info->bootfs_offset);
330 return err;
331 }
332 if (fread(&tmp16, 1, sizeof(tmp16), fp) != sizeof(tmp16)) {
333 fprintf(stderr, "Failed to read while looking for JFFS2\n");
334 return -EIO;
335 }
336 if (be16_to_cpu(tmp16) == 0x8519)
337 break;
338 }
339 if (info->bootfs_offset >= info->tail_offset) {
340 fprintf(stderr, "Failed to find bootfs offset\n");
341 return -EPROTO;
342 }
343
344 for (info->rootfs_offset = info->bootfs_offset;
345 info->rootfs_offset < info->tail_offset;
346 info->rootfs_offset += 0x20000) {
347 uint32_t *magic = (uint32_t *)&buf[0];
348
349 if (fseek(fp, info->rootfs_offset, SEEK_SET)) {
350 err = -errno;
351 fprintf(stderr, "Failed to fseek: %d\n", err);
352 return err;
353 }
354
355 length = info->padding_offset ? sizeof(*magic) : 256;
356 bytes = fread(buf, 1, length, fp);
357 if (bytes != length) {
358 fprintf(stderr, "Failed to read %zu bytes\n", length);
359 return -EIO;
360 }
361
362 if (!info->padding_offset && bcm4908img_is_all_ff(buf, length))
363 info->padding_offset = info->rootfs_offset;
364
365 if (be32_to_cpu(*magic) == UBI_EC_HDR_MAGIC)
366 break;
367 }
368 if (info->rootfs_offset >= info->tail_offset) {
369 fprintf(stderr, "Failed to find rootfs offset\n");
370 return -EPROTO;
371 }
372
373 /* CRC32 */
374
375 /* Start with cferom (or bootfs) - skip vendor header */
376 fseek(fp, info->cferom_offset, SEEK_SET);
377
378 info->crc32 = 0xffffffff;
379 length = info->tail_offset - info->cferom_offset;
380 while (length && (bytes = fread(buf, 1, bcm4908img_min(sizeof(buf), length), fp)) > 0) {
381 info->crc32 = bcm4908img_crc32(info->crc32, buf, bytes);
382 length -= bytes;
383 }
384 if (length) {
385 fprintf(stderr, "Failed to read last %zd B of data\n", length);
386 return -EIO;
387 }
388
389 /* Tail */
390
391 if (fread(tail, 1, sizeof(*tail), fp) != sizeof(*tail)) {
392 fprintf(stderr, "Failed to read BCM4908 image tail\n");
393 return -EIO;
394 }
395
396 /* Standard validation */
397
398 if (info->crc32 != le32_to_cpu(tail->crc32)) {
399 fprintf(stderr, "Invalid data crc32: 0x%08x instead of 0x%08x\n", info->crc32, le32_to_cpu(tail->crc32));
400 return -EPROTO;
401 }
402
403 return 0;
404 }
405
406 /**************************************************
407 * Info
408 **************************************************/
409
410 static int bcm4908img_info(int argc, char **argv) {
411 struct bcm4908img_info info;
412 const char *pathname = NULL;
413 FILE *fp;
414 int c;
415 int err = 0;
416
417 while ((c = getopt(argc, argv, "i:")) != -1) {
418 switch (c) {
419 case 'i':
420 pathname = optarg;
421 break;
422 }
423 }
424
425 fp = bcm4908img_open(pathname, "r");
426 if (!fp) {
427 fprintf(stderr, "Failed to open BCM4908 image\n");
428 err = -EACCES;
429 goto out;
430 }
431
432 err = bcm4908img_parse(fp, &info);
433 if (err) {
434 fprintf(stderr, "Failed to parse BCM4908 image\n");
435 goto err_close;
436 }
437
438 if (info.bootfs_offset != info.cferom_offset)
439 printf("cferom offset:\t%zu\n", info.cferom_offset);
440 printf("bootfs offset:\t0x%zx\n", info.bootfs_offset);
441 if (info.padding_offset)
442 printf("padding offset:\t0x%zx\n", info.padding_offset);
443 printf("rootfs offset:\t0x%zx\n", info.rootfs_offset);
444 printf("Checksum:\t0x%08x\n", info.crc32);
445
446 err_close:
447 bcm4908img_close(fp);
448 out:
449 return err;
450 }
451
452 /**************************************************
453 * Create
454 **************************************************/
455
456 static ssize_t bcm4908img_create_append_file(FILE *trx, const char *in_path, uint32_t *crc32) {
457 FILE *in;
458 size_t bytes;
459 ssize_t length = 0;
460 uint8_t buf[1024];
461
462 in = fopen(in_path, "r");
463 if (!in) {
464 fprintf(stderr, "Failed to open %s\n", in_path);
465 return -EACCES;
466 }
467
468 while ((bytes = fread(buf, 1, sizeof(buf), in)) > 0) {
469 if (fwrite(buf, 1, bytes, trx) != bytes) {
470 fprintf(stderr, "Failed to write %zu B to %s\n", bytes, pathname);
471 length = -EIO;
472 break;
473 }
474 *crc32 = bcm4908img_crc32(*crc32, buf, bytes);
475 length += bytes;
476 }
477
478 fclose(in);
479
480 return length;
481 }
482
483 static ssize_t bcm4908img_create_append_zeros(FILE *trx, size_t length) {
484 uint8_t *buf;
485
486 buf = malloc(length);
487 if (!buf)
488 return -ENOMEM;
489 memset(buf, 0, length);
490
491 if (fwrite(buf, 1, length, trx) != length) {
492 fprintf(stderr, "Failed to write %zu B to %s\n", length, pathname);
493 free(buf);
494 return -EIO;
495 }
496
497 free(buf);
498
499 return length;
500 }
501
502 static ssize_t bcm4908img_create_align(FILE *trx, size_t cur_offset, size_t alignment) {
503 if (cur_offset & (alignment - 1)) {
504 size_t length = alignment - (cur_offset % alignment);
505 return bcm4908img_create_append_zeros(trx, length);
506 }
507
508 return 0;
509 }
510
511 static int bcm4908img_create(int argc, char **argv) {
512 struct bcm4908img_tail tail = {
513 .version = cpu_to_le32(WFI_VERSION),
514 .chip_id = cpu_to_le32(0x4908),
515 .flash_type = cpu_to_le32(WFI_NAND128_FLASH),
516 .flags = cpu_to_le32(WFI_FLAG_SUPPORTS_BTRM),
517 };
518 uint32_t crc32 = 0xffffffff;
519 size_t cur_offset = 0;
520 ssize_t bytes;
521 FILE *fp;
522 int c;
523 int err = 0;
524
525 if (argc < 3) {
526 fprintf(stderr, "No BCM4908 image pathname passed\n");
527 err = -EINVAL;
528 goto out;
529 }
530 pathname = argv[2];
531
532 fp = fopen(pathname, "w+");
533 if (!fp) {
534 fprintf(stderr, "Failed to open %s\n", pathname);
535 err = -EACCES;
536 goto out;
537 }
538
539 optind = 3;
540 while ((c = getopt(argc, argv, "f:a:A:")) != -1) {
541 switch (c) {
542 case 'f':
543 bytes = bcm4908img_create_append_file(fp, optarg, &crc32);
544 if (bytes < 0) {
545 fprintf(stderr, "Failed to append file %s\n", optarg);
546 } else {
547 cur_offset += bytes;
548 }
549 break;
550 case 'a':
551 bytes = bcm4908img_create_align(fp, cur_offset, strtol(optarg, NULL, 0));
552 if (bytes < 0)
553 fprintf(stderr, "Failed to append zeros\n");
554 else
555 cur_offset += bytes;
556 break;
557 case 'A':
558 bytes = strtol(optarg, NULL, 0) - cur_offset;
559 if (bytes < 0) {
560 fprintf(stderr, "Current BCM4908 image length is 0x%zx, can't pad it with zeros to 0x%lx\n", cur_offset, strtol(optarg, NULL, 0));
561 } else {
562 bytes = bcm4908img_create_append_zeros(fp, bytes);
563 if (bytes < 0)
564 fprintf(stderr, "Failed to append zeros\n");
565 else
566 cur_offset += bytes;
567 }
568 break;
569 }
570 if (err)
571 goto err_close;
572 }
573
574 tail.crc32 = cpu_to_le32(crc32);
575
576 bytes = fwrite(&tail, 1, sizeof(tail), fp);
577 if (bytes != sizeof(tail)) {
578 fprintf(stderr, "Failed to write BCM4908 image tail to %s\n", pathname);
579 return -EIO;
580 }
581
582 err_close:
583 fclose(fp);
584 out:
585 return err;
586 }
587
588 /**************************************************
589 * Extract
590 **************************************************/
591
592 static int bcm4908img_extract(int argc, char **argv) {
593 struct bcm4908img_info info;
594 const char *pathname = NULL;
595 const char *type = NULL;
596 uint8_t buf[1024];
597 size_t offset;
598 size_t length;
599 size_t bytes;
600 FILE *fp;
601 int c;
602 int err = 0;
603
604 while ((c = getopt(argc, argv, "i:t:")) != -1) {
605 switch (c) {
606 case 'i':
607 pathname = optarg;
608 break;
609 case 't':
610 type = optarg;
611 break;
612 }
613 }
614
615 fp = bcm4908img_open(pathname, "r");
616 if (!fp) {
617 fprintf(stderr, "Failed to open BCM4908 image\n");
618 err = -EACCES;
619 goto err_out;
620 }
621
622 err = bcm4908img_parse(fp, &info);
623 if (err) {
624 fprintf(stderr, "Failed to parse BCM4908 image\n");
625 goto err_close;
626 }
627
628 if (!type) {
629 err = -EINVAL;
630 fprintf(stderr, "No data to extract specified\n");
631 goto err_close;
632 } else if (!strcmp(type, "cferom")) {
633 offset = info.cferom_offset;
634 length = info.bootfs_offset - offset;
635 if (!length) {
636 err = -ENOENT;
637 fprintf(stderr, "This BCM4908 image doesn't contain cferom\n");
638 goto err_close;
639 }
640 } else if (!strcmp(type, "bootfs")) {
641 offset = info.bootfs_offset;
642 length = (info.padding_offset ? info.padding_offset : info.rootfs_offset) - offset;
643 } else if (!strcmp(type, "rootfs")) {
644 offset = info.rootfs_offset;
645 length = info.tail_offset - offset;
646 } else if (!strcmp(type, "firmware")) {
647 offset = info.bootfs_offset;
648 length = info.tail_offset - offset;
649 } else {
650 err = -EINVAL;
651 fprintf(stderr, "Unsupported extract type: %s\n", type);
652 goto err_close;
653 }
654
655 if (!length) {
656 err = -EINVAL;
657 fprintf(stderr, "Failed to find requested data in input image\n");
658 goto err_close;
659 }
660
661 fseek(fp, offset, SEEK_SET);
662 while (length && (bytes = fread(buf, 1, bcm4908img_min(sizeof(buf), length), fp)) > 0) {
663 fwrite(buf, bytes, 1, stdout);
664 length -= bytes;
665 }
666 if (length) {
667 err = -EIO;
668 fprintf(stderr, "Failed to read last %zd B of data\n", length);
669 goto err_close;
670 }
671
672 err_close:
673 bcm4908img_close(fp);
674 err_out:
675 return err;
676 }
677
678 /**************************************************
679 * bootfs
680 **************************************************/
681
682 #define JFFS2_MAGIC_BITMASK 0x1985
683
684 #define JFFS2_COMPR_NONE 0x00
685 #define JFFS2_COMPR_ZERO 0x01
686 #define JFFS2_COMPR_RTIME 0x02
687 #define JFFS2_COMPR_RUBINMIPS 0x03
688 #define JFFS2_COMPR_COPY 0x04
689 #define JFFS2_COMPR_DYNRUBIN 0x05
690 #define JFFS2_COMPR_ZLIB 0x06
691 #define JFFS2_COMPR_LZO 0x07
692 /* Compatibility flags. */
693 #define JFFS2_COMPAT_MASK 0xc000 /* What do to if an unknown nodetype is found */
694 #define JFFS2_NODE_ACCURATE 0x2000
695 /* INCOMPAT: Fail to mount the filesystem */
696 #define JFFS2_FEATURE_INCOMPAT 0xc000
697 /* ROCOMPAT: Mount read-only */
698 #define JFFS2_FEATURE_ROCOMPAT 0x8000
699 /* RWCOMPAT_COPY: Mount read/write, and copy the node when it's GC'd */
700 #define JFFS2_FEATURE_RWCOMPAT_COPY 0x4000
701 /* RWCOMPAT_DELETE: Mount read/write, and delete the node when it's GC'd */
702 #define JFFS2_FEATURE_RWCOMPAT_DELETE 0x0000
703
704 #define JFFS2_NODETYPE_DIRENT (JFFS2_FEATURE_INCOMPAT | JFFS2_NODE_ACCURATE | 1)
705
706 typedef struct {
707 uint32_t v32;
708 } __attribute__((packed)) jint32_t;
709
710 typedef struct {
711 uint16_t v16;
712 } __attribute__((packed)) jint16_t;
713
714 struct jffs2_unknown_node
715 {
716 /* All start like this */
717 jint16_t magic;
718 jint16_t nodetype;
719 jint32_t totlen; /* So we can skip over nodes we don't grok */
720 jint32_t hdr_crc;
721 };
722
723 struct jffs2_raw_dirent
724 {
725 jint16_t magic;
726 jint16_t nodetype; /* == JFFS2_NODETYPE_DIRENT */
727 jint32_t totlen;
728 jint32_t hdr_crc;
729 jint32_t pino;
730 jint32_t version;
731 jint32_t ino; /* == zero for unlink */
732 jint32_t mctime;
733 uint8_t nsize;
734 uint8_t type;
735 uint8_t unused[2];
736 jint32_t node_crc;
737 jint32_t name_crc;
738 uint8_t name[0];
739 };
740
741 #define je16_to_cpu(x) ((x).v16)
742 #define je32_to_cpu(x) ((x).v32)
743
744 static int bcm4908img_bootfs_ls(FILE *fp, struct bcm4908img_info *info) {
745 struct jffs2_unknown_node node;
746 struct jffs2_raw_dirent dirent;
747 size_t offset;
748 size_t bytes;
749 int err = 0;
750
751 for (offset = info->bootfs_offset; ; offset += (je32_to_cpu(node.totlen) + 0x03) & ~0x03) {
752 char name[FILENAME_MAX + 1];
753
754 if (fseek(fp, offset, SEEK_SET)) {
755 err = -errno;
756 fprintf(stderr, "Failed to fseek: %d\n", err);
757 return err;
758 }
759
760 bytes = fread(&node, 1, sizeof(node), fp);
761 if (bytes != sizeof(node)) {
762 fprintf(stderr, "Failed to read %zu bytes\n", sizeof(node));
763 return -EIO;
764 }
765
766 if (je16_to_cpu(node.magic) != JFFS2_MAGIC_BITMASK) {
767 break;
768 }
769
770 if (je16_to_cpu(node.nodetype) != JFFS2_NODETYPE_DIRENT) {
771 continue;
772 }
773
774 memcpy(&dirent, &node, sizeof(node));
775 bytes += fread((uint8_t *)&dirent + sizeof(node), 1, sizeof(dirent) - sizeof(node), fp);
776 if (bytes != sizeof(dirent)) {
777 fprintf(stderr, "Failed to read %zu bytes\n", sizeof(node));
778 return -EIO;
779 }
780
781 if (dirent.nsize + 1 > sizeof(name)) {
782 /* Keep reading & printing BUT exit with error code */
783 fprintf(stderr, "Too long filename\n");
784 err = -ENOMEM;
785 continue;
786 }
787
788 bytes = fread(name, 1, dirent.nsize, fp);
789 if (bytes != dirent.nsize) {
790 fprintf(stderr, "Failed to read filename\n");
791 return -EIO;
792 }
793 name[bytes] = '\0';
794
795 printf("%s\n", name);
796 }
797
798 return err;
799 }
800
801 static int bcm4908img_bootfs_mv(FILE *fp, struct bcm4908img_info *info, int argc, char **argv) {
802 struct jffs2_unknown_node node;
803 struct jffs2_raw_dirent dirent;
804 const char *oldname;
805 const char *newname;
806 size_t offset;
807 size_t bytes;
808 int err = -ENOENT;
809
810 if (argc - optind < 2) {
811 fprintf(stderr, "No enough arguments passed\n");
812 return -EINVAL;
813 }
814 oldname = argv[optind++];
815 newname = argv[optind++];
816
817 if (strlen(newname) != strlen(oldname)) {
818 fprintf(stderr, "New filename must have the same length as the old one\n");
819 return -EINVAL;
820 }
821
822 for (offset = info->bootfs_offset; ; offset += (je32_to_cpu(node.totlen) + 0x03) & ~0x03) {
823 char name[FILENAME_MAX];
824 uint32_t crc32;
825
826 if (fseek(fp, offset, SEEK_SET)) {
827 err = -errno;
828 fprintf(stderr, "Failed to fseek: %d\n", err);
829 return err;
830 }
831
832 bytes = fread(&node, 1, sizeof(node), fp);
833 if (bytes != sizeof(node)) {
834 fprintf(stderr, "Failed to read %zu bytes\n", sizeof(node));
835 return -EIO;
836 }
837
838 if (je16_to_cpu(node.magic) != JFFS2_MAGIC_BITMASK) {
839 break;
840 }
841
842 if (je16_to_cpu(node.nodetype) != JFFS2_NODETYPE_DIRENT) {
843 continue;
844 }
845
846 bytes += fread((uint8_t *)&dirent + sizeof(node), 1, sizeof(dirent) - sizeof(node), fp);
847 if (bytes != sizeof(dirent)) {
848 fprintf(stderr, "Failed to read %zu bytes\n", sizeof(node));
849 return -EIO;
850 }
851
852 if (dirent.nsize + 1 > sizeof(name)) {
853 fprintf(stderr, "Too long filename\n");
854 err = -ENOMEM;
855 continue;
856 }
857
858 bytes = fread(name, 1, dirent.nsize, fp);
859 if (bytes != dirent.nsize) {
860 fprintf(stderr, "Failed to read filename\n");
861 return -EIO;
862 }
863 name[bytes] = '\0';
864
865 if (debug)
866 printf("offset:%08zx name_crc:%04x filename:%s\n", offset, je32_to_cpu(dirent.name_crc), name);
867
868 if (strcmp(name, oldname)) {
869 continue;
870 }
871
872 if (fseek(fp, offset + offsetof(struct jffs2_raw_dirent, name_crc), SEEK_SET)) {
873 err = -errno;
874 fprintf(stderr, "Failed to fseek: %d\n", err);
875 return err;
876 }
877 crc32 = bcm4908img_crc32(0, newname, dirent.nsize);
878 bytes = fwrite(&crc32, 1, sizeof(crc32), fp);
879 if (bytes != sizeof(crc32)) {
880 fprintf(stderr, "Failed to write new CRC32\n");
881 return -EIO;
882 }
883
884 if (fseek(fp, offset + offsetof(struct jffs2_raw_dirent, name), SEEK_SET)) {
885 err = -errno;
886 fprintf(stderr, "Failed to fseek: %d\n", err);
887 return err;
888 }
889 bytes = fwrite(newname, 1, dirent.nsize, fp);
890 if (bytes != dirent.nsize) {
891 fprintf(stderr, "Failed to write new filename\n");
892 return -EIO;
893 }
894
895 /* Calculate new BCM4908 image checksum */
896
897 err = bcm4908img_calc_crc32(fp, info);
898 if (err) {
899 fprintf(stderr, "Failed to write new filename\n");
900 return err;
901 }
902
903 info->tail.crc32 = cpu_to_le32(info->crc32);
904 if (fseek(fp, -sizeof(struct bcm4908img_tail), SEEK_END)) {
905 err = -errno;
906 fprintf(stderr, "Failed to write new filename\n");
907 return err;
908 }
909
910 if (fwrite(&info->tail, 1, sizeof(struct bcm4908img_tail), fp) != sizeof(struct bcm4908img_tail)) {
911 fprintf(stderr, "Failed to write updated tail\n");
912 return -EIO;
913 }
914
915 printf("Successfully renamed %s to the %s\n", oldname, newname);
916
917 return 0;
918 }
919
920 fprintf(stderr, "Failed to find %s\n", oldname);
921
922 return -ENOENT;
923 }
924
925 static int bcm4908img_bootfs(int argc, char **argv) {
926 struct bcm4908img_info info;
927 const char *pathname = NULL;
928 const char *mode;
929 const char *cmd;
930 FILE *fp;
931 int c;
932 int err = 0;
933
934 while ((c = getopt(argc, argv, "i:")) != -1) {
935 switch (c) {
936 case 'i':
937 pathname = optarg;
938 break;
939 }
940 }
941
942 if (argc - optind < 1) {
943 fprintf(stderr, "No bootfs command specified\n");
944 err = -EINVAL;
945 goto out;
946 }
947 cmd = argv[optind++];
948
949 mode = strcmp(cmd, "mv") ? "r" : "r+";
950 fp = bcm4908img_open(pathname, mode);
951 if (!fp) {
952 fprintf(stderr, "Failed to open BCM4908 image\n");
953 err = -EACCES;
954 goto out;
955 }
956
957 err = bcm4908img_parse(fp, &info);
958 if (err) {
959 fprintf(stderr, "Failed to parse BCM4908 image\n");
960 goto err_close;
961 }
962
963 if (!strcmp(cmd, "ls")) {
964 err = bcm4908img_bootfs_ls(fp, &info);
965 } else if (!strcmp(cmd, "mv")) {
966 err = bcm4908img_bootfs_mv(fp, &info, argc, argv);
967 } else {
968 err = -EINVAL;
969 fprintf(stderr, "Unsupported bootfs command: %s\n", cmd);
970 }
971
972 err_close:
973 bcm4908img_close(fp);
974 out:
975 return err;
976 }
977
978 /**************************************************
979 * Start
980 **************************************************/
981
982 static void usage() {
983 printf("Usage:\n");
984 printf("\n");
985 printf("Info about a BCM4908 image:\n");
986 printf("\tbcm4908img info <options>\n");
987 printf("\t-i <file>\t\t\t\tinput BCM490 image\n");
988 printf("\n");
989 printf("Creating a new BCM4908 image:\n");
990 printf("\tbcm4908img create <file> [options]\n");
991 printf("\t-f file\t\t\t\tadd data from specified file\n");
992 printf("\t-a alignment\t\t\tpad image with zeros to specified alignment\n");
993 printf("\t-A offset\t\t\t\tappend zeros until reaching specified offset\n");
994 printf("\n");
995 printf("Extracting from a BCM4908 image:\n");
996 printf("\tbcm4908img extract <options>\n");
997 printf("\t-i <file>\t\t\t\tinput BCM490 image\n");
998 printf("\t-t <type>\t\t\t\tone of: cferom, bootfs, rootfs, firmware\n");
999 printf("\n");
1000 printf("Access bootfs in a BCM4908 image:\n");
1001 printf("\tbcm4908img bootfs <options> <command> <arguments>\n");
1002 printf("\t-i <file>\t\t\t\tinput BCM490 image\n");
1003 printf("\tls\t\t\t\t\tlist bootfs files\n");
1004 printf("\tmv <source> <dest>\t\t\trename bootfs file\n");
1005 }
1006
1007 int main(int argc, char **argv) {
1008 if (argc > 1) {
1009 optind++;
1010 if (!strcmp(argv[1], "info"))
1011 return bcm4908img_info(argc, argv);
1012 else if (!strcmp(argv[1], "create"))
1013 return bcm4908img_create(argc, argv);
1014 else if (!strcmp(argv[1], "extract"))
1015 return bcm4908img_extract(argc, argv);
1016 else if (!strcmp(argv[1], "bootfs"))
1017 return bcm4908img_bootfs(argc, argv);
1018 }
1019
1020 usage();
1021 return 0;
1022 }