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 read_id
= read_uint32(readbuf
, 0x00);
190 uint32_t read_len
= read_uint32(readbuf
, 0x04);
191 uint32_t read_rev
= read_uint32(readbuf
, 0x0c);
192 if (read_oob_sector_health
) {
193 uint32_t oob_id
= read_uint32(oobbuf
, 0x02);
194 uint32_t oob_len
= read_uint32(oobbuf
, 0x06);
195 uint32_t oob_rev
= read_uint32(oobbuf
, 0x0a);
197 if (oob_id
!= read_id
|| oob_len
!= read_len
|| oob_rev
!= read_rev
) {
198 fprintf(stderr
, "Warning: sector has inconsistent metadata\n");
202 if (read_id
== TFFS_ID_END
) {
203 /* no more entries in this block */
207 if (read_len
> TFFS_MAXIMUM_SEGMENT_SIZE
) {
208 fprintf(stderr
, "Warning: segment is longer than possible\n");
212 if (read_rev
< rev
) {
213 /* obsolete revision => ignore this */
216 if (read_rev
> rev
) {
217 /* newer revision => clear old data */
218 for (uint32_t i
= 0; i
< num_segments
; i
++) {
219 free(segments
[i
].val
);
227 uint32_t seg
= read_uint32(readbuf
, 0x10);
229 if (seg
== TFFS_SEGMENT_CLEARED
) {
233 uint32_t next_seg
= read_uint32(readbuf
, 0x14);
235 uint32_t new_num_segs
= next_seg
== 0 ? seg
+ 1 : next_seg
+ 1;
236 if (new_num_segs
> num_segments
) {
237 segments
= realloc(segments
, new_num_segs
* sizeof(struct tffs_entry_segment
));
238 memset(segments
+ (num_segments
* sizeof(struct tffs_entry_segment
)), 0x0,
239 (new_num_segs
- num_segments
) * sizeof(struct tffs_entry_segment
));
240 num_segments
= new_num_segs
;
242 segments
[seg
].len
= read_len
;
243 segments
[seg
].val
= malloc(read_len
);
244 memcpy(segments
[seg
].val
, readbuf
+ TFFS_ENTRY_HEADER_SIZE
, read_len
);
249 if (num_segments
== 0) {
253 assert (segments
!= NULL
);
256 for (uint32_t i
= 0; i
< num_segments
; i
++) {
257 if (segments
[i
].val
== NULL
) {
258 /* missing segment */
262 len
+= segments
[i
].len
;
265 void *p
= malloc(len
);
268 for (uint32_t i
= 0; i
< num_segments
; i
++) {
269 memcpy(p
, segments
[i
].val
, segments
[i
].len
);
270 p
+= segments
[i
].len
;
276 static void parse_key_names(struct tffs_entry
*names_entry
,
277 struct tffs_key_name_table
*key_names
)
279 uint32_t pos
= 0, i
= 0;
280 struct tffs_name_table_entry
*name_item
;
282 key_names
->entries
= NULL
;
285 key_names
->entries
= realloc(key_names
->entries
,
286 sizeof(struct tffs_name_table_entry
) * (i
+ 1));
287 if (key_names
->entries
== NULL
) {
288 fprintf(stderr
, "ERROR: memory allocation failed!\n");
291 name_item
= &key_names
->entries
[i
];
293 name_item
->id
= read_uint32(names_entry
->val
, pos
);
294 pos
+= sizeof(uint32_t);
295 name_item
->val
= strdup((const char *)(names_entry
->val
+ pos
));
298 * There is no "length" field because the string values are
299 * simply NULL-terminated -> strlen() gives us the size.
301 pos
+= get_walk_size(strlen(name_item
->val
) + 1);
304 } while (pos
< names_entry
->len
);
309 static void show_all_key_names(struct tffs_key_name_table
*key_names
)
311 for (uint32_t i
= 0; i
< key_names
->size
; i
++)
312 printf("%s\n", key_names
->entries
[i
].val
);
315 static int show_all_key_value_pairs(struct tffs_key_name_table
*key_names
)
317 uint8_t has_value
= 0;
318 struct tffs_entry tmp
;
320 for (uint32_t i
= 0; i
< key_names
->size
; i
++) {
321 if (find_entry(key_names
->entries
[i
].id
, &tmp
)) {
322 printf("%s=", (const char *)key_names
->entries
[i
].val
);
323 print_entry_value(&tmp
);
331 fprintf(stderr
, "ERROR: no values found!\n");
338 static int show_matching_key_value(struct tffs_key_name_table
*key_names
)
340 struct tffs_entry tmp
;
343 for (uint32_t i
= 0; i
< key_names
->size
; i
++) {
344 name
= key_names
->entries
[i
].val
;
346 if (strcmp(name
, name_filter
) == 0) {
347 if (find_entry(key_names
->entries
[i
].id
, &tmp
)) {
348 print_entry_value(&tmp
);
354 "ERROR: no value found for name %s!\n",
361 fprintf(stderr
, "ERROR: Unknown key name %s!\n", name_filter
);
365 static int check_sector(off_t pos
)
367 if (!read_oob_sector_health
) {
370 if (read_sectoroob(pos
)) {
373 if (read_uint8(oobbuf
, 0x00) != 0xff) {
377 if (read_uint8(oobbuf
, 0x01) != 0xff) {
384 static int check_block(off_t pos
, uint32_t sector
)
386 if (!check_sector(pos
)) {
389 if (read_sector(pos
)) {
392 if (read_uint64(readbuf
, 0x00) != TFFS_BLOCK_HEADER_MAGIC
) {
393 fprintf(stderr
, "Warning: block without magic header. Skipping block\n");
396 if (read_uint32(readbuf
, 0x0c) != TFFS_SECTORS_PER_PAGE
) {
397 fprintf(stderr
, "Warning: block with wrong number of sectors per page. Skipping block\n");
401 uint32_t num_hdr_bad
= read_uint32(readbuf
, 0x0c);
402 for (uint32_t i
= 0; i
< num_hdr_bad
; i
++) {
403 uint32_t bad
= sector
+ read_uint64(readbuf
, 0x1c + sizeof(uint64_t)*i
);
404 sector_mark_bad(bad
);
410 static int scan_mtd(void)
412 struct mtd_info_user info
;
414 if (ioctl(mtdfd
, MEMGETINFO
, &info
)) {
418 blocksize
= info
.erasesize
;
420 sectors
= malloc(sizeof(*sectors
) + (info
.size
/ TFFS_SECTOR_SIZE
+ 7) / 8);
421 if (sectors
== NULL
) {
422 fprintf(stderr
, "ERROR: memory allocation failed!\n");
425 sectors
->num_sectors
= info
.size
/ TFFS_SECTOR_SIZE
;
426 memset(sectors
->sectors
, 0xff, (info
.size
/ TFFS_SECTOR_SIZE
+ 7) / 8);
428 uint32_t sector
= 0, valid_blocks
= 0;
429 uint8_t block_ok
= 0;
430 for (off_t pos
= 0; pos
< info
.size
; sector
++, pos
+= TFFS_SECTOR_SIZE
) {
431 if (pos
% info
.erasesize
== 0) {
432 block_ok
= check_block(pos
, sector
);
433 /* first sector of the block contains metadata
434 => handle it like a bad sector */
435 sector_mark_bad(sector
);
439 } else if (!block_ok
|| !sector_get_good(sector
) || !check_sector(pos
)) {
440 sector_mark_bad(sector
);
447 static void usage(int status
)
449 FILE *stream
= (status
!= EXIT_SUCCESS
) ? stderr
: stdout
;
451 fprintf(stream
, "Usage: %s [OPTIONS...]\n", progname
);
455 " -a list all key value pairs found in the TFFS file/device\n"
456 " -d <mtd> inspect the TFFS on mtd device <mtd>\n"
457 " -h show this screen\n"
458 " -l list all supported keys\n"
459 " -n <key name> display the value of the given key\n"
460 " -o read OOB information about sector health\n"
466 static void parse_options(int argc
, char *argv
[])
471 c
= getopt(argc
, argv
, "abd:hln:o");
479 print_all_key_names
= false;
491 print_all_key_names
= true;
496 name_filter
= optarg
;
498 print_all_key_names
= false;
501 read_oob_sector_health
= true;
510 fprintf(stderr
, "ERROR: No input file (-d <file>) given!\n");
514 if (!show_all
&& !name_filter
&& !print_all_key_names
) {
516 "ERROR: either -l, -a or -n <key name> is required!\n");
521 int main(int argc
, char *argv
[])
523 int ret
= EXIT_FAILURE
;
524 struct tffs_entry name_table
;
525 struct tffs_key_name_table key_names
;
527 progname
= basename(argv
[0]);
529 parse_options(argc
, argv
);
531 mtdfd
= open(mtddev
, O_RDONLY
);
533 fprintf(stderr
, "ERROR: Failed to open tffs device %s\n",
539 fprintf(stderr
, "ERROR: Parsing blocks from tffs device %s failed\n", mtddev
);
540 fprintf(stderr
, " Is byte-swapping (-b) required?\n");
544 if (!find_entry(TFFS_ID_TABLE_NAME
, &name_table
)) {
545 fprintf(stderr
, "ERROR: No name table found on tffs device %s\n",
547 goto out_free_sectors
;
550 parse_key_names(&name_table
, &key_names
);
551 if (key_names
.size
< 1) {
552 fprintf(stderr
, "ERROR: No name table found on tffs device %s\n",
557 if (print_all_key_names
) {
558 show_all_key_names(&key_names
);
560 } else if (show_all
) {
561 ret
= show_all_key_value_pairs(&key_names
);
563 ret
= show_matching_key_value(&key_names
);
566 free(key_names
.entries
);
568 free(name_table
.val
);