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