2 * A tool for reading the TFFS partitions (a name-value storage usually
3 * found in AVM Fritz!Box based devices) on nand flash.
5 * Copyright (c) 2018 Valentin Spreckels <Valentin.Spreckels@Informatik.Uni-Oldenburg.DE>
7 * Based on the fritz_tffs_read tool:
8 * Copyright (c) 2015-2016 Martin Blumenstingl <martin.blumenstingl@googlemail.com>
9 * and on the TFFS 2.0 kernel driver from AVM:
10 * Copyright (c) 2004-2007 AVM GmbH <fritzbox_info@avm.de>
11 * and the TFFS 3.0 kernel driver from AVM:
12 * Copyright (C) 2004-2014 AVM GmbH <fritzbox_info@avm.de>
13 * and the OpenWrt TFFS kernel driver:
14 * Copyright (c) 2013 John Crispin <john@phrozen.org>
16 * This program is free software; you can redistribute it and/or modify
17 * it under the terms of the GNU General Public License as published by
18 * the Free Software Foundation; either version 2 of the License, or
19 * (at your option) any later version.
21 * This program is distributed in the hope that it will be useful,
22 * but WITHOUT ANY WARRANTY; without even the implied warranty of
23 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
24 * GNU General Public License for more details.
26 * You should have received a copy of the GNU General Public License along
27 * with this program; if not, write to the Free Software Foundation, Inc.,
28 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
42 #include <sys/ioctl.h>
43 #include <sys/types.h>
45 #include <arpa/inet.h>
46 #include <mtd/mtd-user.h>
49 #define DEFAULT_TFFS_SIZE (256 * 1024)
51 #define TFFS_ID_END 0xffffffff
52 #define TFFS_ID_TABLE_NAME 0x000001ff
54 #define TFFS_BLOCK_HEADER_MAGIC 0x41564d5f54464653ULL
55 #define TFFS_VERSION 0x0003
56 #define TFFS_ENTRY_HEADER_SIZE 0x18
57 #define TFFS_MAXIMUM_SEGMENT_SIZE (0x800 - TFFS_ENTRY_HEADER_SIZE)
59 #define TFFS_SECTOR_SIZE 0x0800
60 #define TFFS_SECTOR_OOB_SIZE 0x0040
61 #define TFFS_SECTORS_PER_PAGE 2
63 #define TFFS_SEGMENT_CLEARED 0xffffffff
65 static char *progname
;
67 static char *name_filter
= NULL
;
68 static bool show_all
= false;
69 static bool print_all_key_names
= false;
70 static bool read_oob_sector_health
= false;
71 static bool swap_bytes
= false;
72 static uint8_t readbuf
[TFFS_SECTOR_SIZE
];
73 static uint8_t oobbuf
[TFFS_SECTOR_OOB_SIZE
];
74 static uint32_t blocksize
;
76 struct tffs_sectors
*sectors
;
83 static inline void sector_mark_bad(int num
)
85 sectors
->sectors
[num
/ 8] &= ~(0x80 >> (num
% 8));
88 static inline uint8_t sector_get_good(int num
)
90 return sectors
->sectors
[num
/ 8] & 0x80 >> (num
% 8);
93 struct tffs_entry_segment
{
103 struct tffs_name_table_entry
{
108 struct tffs_key_name_table
{
110 struct tffs_name_table_entry
*entries
;
113 static inline uint8_t read_uint8(void *buf
, ptrdiff_t off
)
115 return *(uint8_t *)(buf
+ off
);
118 static inline uint32_t read_uint32(void *buf
, ptrdiff_t off
)
120 uint32_t tmp
= *(uint32_t *)(buf
+ off
);
127 static inline uint64_t read_uint64(void *buf
, ptrdiff_t off
)
129 uint64_t tmp
= *(uint64_t *)(buf
+ off
);
136 static int read_sector(off_t pos
)
138 if (pread(mtdfd
, readbuf
, TFFS_SECTOR_SIZE
, pos
) != TFFS_SECTOR_SIZE
) {
145 static int read_sectoroob(off_t pos
)
147 struct mtd_oob_buf oob
= {
149 .length
= TFFS_SECTOR_OOB_SIZE
,
153 if (ioctl(mtdfd
, MEMREADOOB
, &oob
) < 0) {
160 static inline uint32_t get_walk_size(uint32_t entry_len
)
162 return (entry_len
+ 3) & ~0x03;
165 static void print_entry_value(const struct tffs_entry
*entry
)
167 /* These are NOT NULL terminated. */
168 fwrite(entry
->val
, 1, entry
->len
, stdout
);
171 static int find_entry(uint32_t id
, struct tffs_entry
*entry
)
174 uint32_t num_segments
= 0;
175 struct tffs_entry_segment
*segments
= NULL
;
178 uint8_t block_end
= 0;
179 for (uint32_t sector
= 0; sector
< sectors
->num_sectors
; sector
++, pos
+= TFFS_SECTOR_SIZE
) {
181 if (pos
% blocksize
== 0) {
184 } else if (sector_get_good(sector
)) {
185 if (read_sectoroob(pos
) || read_sector(pos
)) {
186 fprintf(stderr
, "ERROR: sector isn't readable, but has been previously!\n");
189 uint32_t oob_id
= read_uint32(oobbuf
, 0x02);
190 uint32_t oob_len
= read_uint32(oobbuf
, 0x06);
191 uint32_t oob_rev
= read_uint32(oobbuf
, 0x0a);
192 uint32_t read_id
= read_uint32(readbuf
, 0x00);
193 uint32_t read_len
= read_uint32(readbuf
, 0x04);
194 uint32_t read_rev
= read_uint32(readbuf
, 0x0c);
195 if (read_oob_sector_health
&& (oob_id
!= read_id
|| oob_len
!= read_len
|| oob_rev
!= read_rev
)) {
196 fprintf(stderr
, "Warning: sector has inconsistent metadata\n");
199 if (read_id
== TFFS_ID_END
) {
200 /* no more entries in this block */
204 if (read_len
> TFFS_MAXIMUM_SEGMENT_SIZE
) {
205 fprintf(stderr
, "Warning: segment is longer than possible\n");
209 if (read_rev
< rev
) {
210 /* obsolete revision => ignore this */
213 if (read_rev
> rev
) {
214 /* newer revision => clear old data */
215 for (uint32_t i
= 0; i
< num_segments
; i
++) {
216 free(segments
[i
].val
);
224 uint32_t seg
= read_uint32(readbuf
, 0x10);
226 if (seg
== TFFS_SEGMENT_CLEARED
) {
230 uint32_t next_seg
= read_uint32(readbuf
, 0x14);
232 uint32_t new_num_segs
= next_seg
== 0 ? seg
+ 1 : next_seg
+ 1;
233 if (new_num_segs
> num_segments
) {
234 segments
= realloc(segments
, new_num_segs
* sizeof(struct tffs_entry_segment
));
235 memset(segments
+ (num_segments
* sizeof(struct tffs_entry_segment
)), 0x0,
236 (new_num_segs
- num_segments
) * sizeof(struct tffs_entry_segment
));
237 num_segments
= new_num_segs
;
239 segments
[seg
].len
= read_len
;
240 segments
[seg
].val
= malloc(read_len
);
241 memcpy(segments
[seg
].val
, readbuf
+ TFFS_ENTRY_HEADER_SIZE
, read_len
);
246 if (num_segments
== 0) {
250 assert (segments
!= NULL
);
253 for (uint32_t i
= 0; i
< num_segments
; i
++) {
254 if (segments
[i
].val
== NULL
) {
255 /* missing segment */
259 len
+= segments
[i
].len
;
262 void *p
= malloc(len
);
265 for (uint32_t i
= 0; i
< num_segments
; i
++) {
266 memcpy(p
, segments
[i
].val
, segments
[i
].len
);
267 p
+= segments
[i
].len
;
273 static void parse_key_names(struct tffs_entry
*names_entry
,
274 struct tffs_key_name_table
*key_names
)
276 uint32_t pos
= 0, i
= 0;
277 struct tffs_name_table_entry
*name_item
;
279 key_names
->entries
= NULL
;
282 key_names
->entries
= realloc(key_names
->entries
,
283 sizeof(struct tffs_name_table_entry
) * (i
+ 1));
284 if (key_names
->entries
== NULL
) {
285 fprintf(stderr
, "ERROR: memory allocation failed!\n");
288 name_item
= &key_names
->entries
[i
];
290 name_item
->id
= read_uint32(names_entry
->val
, pos
);
291 pos
+= sizeof(uint32_t);
292 name_item
->val
= strdup((const char *)(names_entry
->val
+ pos
));
295 * There is no "length" field because the string values are
296 * simply NULL-terminated -> strlen() gives us the size.
298 pos
+= get_walk_size(strlen(name_item
->val
) + 1);
301 } while (pos
< names_entry
->len
);
306 static void show_all_key_names(struct tffs_key_name_table
*key_names
)
308 for (uint32_t i
= 0; i
< key_names
->size
; i
++)
309 printf("%s\n", key_names
->entries
[i
].val
);
312 static int show_all_key_value_pairs(struct tffs_key_name_table
*key_names
)
314 uint8_t has_value
= 0;
315 struct tffs_entry tmp
;
317 for (uint32_t i
= 0; i
< key_names
->size
; i
++) {
318 if (find_entry(key_names
->entries
[i
].id
, &tmp
)) {
319 printf("%s=", (const char *)key_names
->entries
[i
].val
);
320 print_entry_value(&tmp
);
328 fprintf(stderr
, "ERROR: no values found!\n");
335 static int show_matching_key_value(struct tffs_key_name_table
*key_names
)
337 struct tffs_entry tmp
;
340 for (uint32_t i
= 0; i
< key_names
->size
; i
++) {
341 name
= key_names
->entries
[i
].val
;
343 if (strcmp(name
, name_filter
) == 0) {
344 if (find_entry(key_names
->entries
[i
].id
, &tmp
)) {
345 print_entry_value(&tmp
);
351 "ERROR: no value found for name %s!\n",
358 fprintf(stderr
, "ERROR: Unknown key name %s!\n", name_filter
);
362 static int check_sector(off_t pos
)
364 if (!read_oob_sector_health
) {
367 if (read_sectoroob(pos
)) {
370 if (read_uint8(oobbuf
, 0x00) != 0xff) {
374 if (read_uint8(oobbuf
, 0x01) != 0xff) {
381 static int check_block(off_t pos
, uint32_t sector
)
383 if (!check_sector(pos
)) {
386 if (read_sector(pos
)) {
389 if (read_uint64(readbuf
, 0x00) != TFFS_BLOCK_HEADER_MAGIC
) {
390 fprintf(stderr
, "Warning: block without magic header. Skipping block\n");
393 if (read_uint32(readbuf
, 0x0c) != TFFS_SECTORS_PER_PAGE
) {
394 fprintf(stderr
, "Warning: block with wrong number of sectors per page. Skipping block\n");
398 uint32_t num_hdr_bad
= read_uint32(readbuf
, 0x0c);
399 for (uint32_t i
= 0; i
< num_hdr_bad
; i
++) {
400 uint32_t bad
= sector
+ read_uint64(readbuf
, 0x1c + sizeof(uint64_t)*i
);
401 sector_mark_bad(bad
);
407 static int scan_mtd(void)
409 struct mtd_info_user info
;
411 if (ioctl(mtdfd
, MEMGETINFO
, &info
)) {
415 blocksize
= info
.erasesize
;
417 sectors
= malloc(sizeof(*sectors
) + (info
.size
/ TFFS_SECTOR_SIZE
+ 7) / 8);
418 if (sectors
== NULL
) {
419 fprintf(stderr
, "ERROR: memory allocation failed!\n");
422 sectors
->num_sectors
= info
.size
/ TFFS_SECTOR_SIZE
;
423 memset(sectors
->sectors
, 0xff, (info
.size
/ TFFS_SECTOR_SIZE
+ 7) / 8);
425 uint32_t sector
= 0, valid_blocks
= 0;
426 uint8_t block_ok
= 0;
427 for (off_t pos
= 0; pos
< info
.size
; sector
++, pos
+= TFFS_SECTOR_SIZE
) {
428 if (pos
% info
.erasesize
== 0) {
429 block_ok
= check_block(pos
, sector
);
430 /* first sector of the block contains metadata
431 => handle it like a bad sector */
432 sector_mark_bad(sector
);
436 } else if (!block_ok
|| !sector_get_good(sector
) || !check_sector(pos
)) {
437 sector_mark_bad(sector
);
444 static void usage(int status
)
446 FILE *stream
= (status
!= EXIT_SUCCESS
) ? stderr
: stdout
;
448 fprintf(stream
, "Usage: %s [OPTIONS...]\n", progname
);
452 " -a list all key value pairs found in the TFFS file/device\n"
453 " -d <mtd> inspect the TFFS on mtd device <mtd>\n"
454 " -h show this screen\n"
455 " -l list all supported keys\n"
456 " -n <key name> display the value of the given key\n"
457 " -o read OOB information about sector health\n"
463 static void parse_options(int argc
, char *argv
[])
468 c
= getopt(argc
, argv
, "abd:hln:o");
476 print_all_key_names
= false;
488 print_all_key_names
= true;
493 name_filter
= optarg
;
495 print_all_key_names
= false;
498 read_oob_sector_health
= true;
507 fprintf(stderr
, "ERROR: No input file (-d <file>) given!\n");
511 if (!show_all
&& !name_filter
&& !print_all_key_names
) {
513 "ERROR: either -l, -a or -n <key name> is required!\n");
518 int main(int argc
, char *argv
[])
520 int ret
= EXIT_FAILURE
;
521 struct tffs_entry name_table
;
522 struct tffs_key_name_table key_names
;
524 progname
= basename(argv
[0]);
526 parse_options(argc
, argv
);
528 mtdfd
= open(mtddev
, O_RDONLY
);
530 fprintf(stderr
, "ERROR: Failed to open tffs device %s\n",
536 fprintf(stderr
, "ERROR: Parsing blocks from tffs device %s failed\n", mtddev
);
537 fprintf(stderr
, " Is byte-swapping (-b) required?\n");
541 if (!find_entry(TFFS_ID_TABLE_NAME
, &name_table
)) {
542 fprintf(stderr
, "ERROR: No name table found on tffs device %s\n",
544 goto out_free_sectors
;
547 parse_key_names(&name_table
, &key_names
);
548 if (key_names
.size
< 1) {
549 fprintf(stderr
, "ERROR: No name table found on tffs device %s\n",
554 if (print_all_key_names
) {
555 show_all_key_names(&key_names
);
557 } else if (show_all
) {
558 ret
= show_all_key_value_pairs(&key_names
);
560 ret
= show_matching_key_value(&key_names
);
563 free(key_names
.entries
);
565 free(name_table
.val
);