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 swap_bytes
= false;
71 static uint8_t readbuf
[TFFS_SECTOR_SIZE
];
72 static uint8_t oobbuf
[TFFS_SECTOR_OOB_SIZE
];
73 static uint32_t blocksize
;
75 struct tffs_sectors
*sectors
;
82 static inline void sector_mark_bad(int num
)
84 sectors
->sectors
[num
/ 8] &= ~(0x80 >> (num
% 8));
87 static inline uint8_t sector_get_good(int num
)
89 return sectors
->sectors
[num
/ 8] & 0x80 >> (num
% 8);
92 struct tffs_entry_segment
{
102 struct tffs_name_table_entry
{
107 struct tffs_key_name_table
{
109 struct tffs_name_table_entry
*entries
;
112 static inline uint8_t read_uint8(void *buf
, ptrdiff_t off
)
114 return *(uint8_t *)(buf
+ off
);
117 static inline uint32_t read_uint32(void *buf
, ptrdiff_t off
)
119 uint32_t tmp
= *(uint32_t *)(buf
+ off
);
126 static inline uint64_t read_uint64(void *buf
, ptrdiff_t off
)
128 uint64_t tmp
= *(uint64_t *)(buf
+ off
);
135 static int read_sector(off_t pos
)
137 if (pread(mtdfd
, readbuf
, TFFS_SECTOR_SIZE
, pos
) != TFFS_SECTOR_SIZE
) {
144 static int read_sectoroob(off_t pos
)
146 struct mtd_oob_buf oob
= {
148 .length
= TFFS_SECTOR_OOB_SIZE
,
152 if (ioctl(mtdfd
, MEMREADOOB
, &oob
) < 0) {
159 static inline uint32_t get_walk_size(uint32_t entry_len
)
161 return (entry_len
+ 3) & ~0x03;
164 static void print_entry_value(const struct tffs_entry
*entry
)
166 /* These are NOT NULL terminated. */
167 fwrite(entry
->val
, 1, entry
->len
, stdout
);
170 static int find_entry(uint32_t id
, struct tffs_entry
*entry
)
173 uint32_t num_segments
= 0;
174 struct tffs_entry_segment
*segments
= NULL
;
177 uint8_t block_end
= 0;
178 for (uint32_t sector
= 0; sector
< sectors
->num_sectors
; sector
++, pos
+= TFFS_SECTOR_SIZE
) {
180 if (pos
% blocksize
== 0) {
183 } else if (sector_get_good(sector
)) {
184 if (read_sectoroob(pos
) || read_sector(pos
)) {
185 fprintf(stderr
, "ERROR: sector isn't readable, but has been previously!\n");
188 uint32_t oob_id
= read_uint32(oobbuf
, 0x02);
189 uint32_t oob_len
= read_uint32(oobbuf
, 0x06);
190 uint32_t oob_rev
= read_uint32(oobbuf
, 0x0a);
191 uint32_t read_id
= read_uint32(readbuf
, 0x00);
192 uint32_t read_len
= read_uint32(readbuf
, 0x04);
193 uint32_t read_rev
= read_uint32(readbuf
, 0x0c);
194 if (oob_id
!= read_id
|| oob_len
!= read_len
|| oob_rev
!= read_rev
) {
195 fprintf(stderr
, "Warning: sector has inconsistent metadata\n");
198 if (read_id
== TFFS_ID_END
) {
199 /* no more entries in this block */
203 if (read_len
> TFFS_MAXIMUM_SEGMENT_SIZE
) {
204 fprintf(stderr
, "Warning: segment is longer than possible\n");
208 if (read_rev
< rev
) {
209 /* obsolete revision => ignore this */
212 if (read_rev
> rev
) {
213 /* newer revision => clear old data */
214 for (uint32_t i
= 0; i
< num_segments
; i
++) {
215 free(segments
[i
].val
);
223 uint32_t seg
= read_uint32(readbuf
, 0x10);
225 if (seg
== TFFS_SEGMENT_CLEARED
) {
229 uint32_t next_seg
= read_uint32(readbuf
, 0x14);
231 uint32_t new_num_segs
= next_seg
== 0 ? seg
+ 1 : next_seg
+ 1;
232 if (new_num_segs
> num_segments
) {
233 segments
= realloc(segments
, new_num_segs
* sizeof(struct tffs_entry_segment
));
234 memset(segments
+ (num_segments
* sizeof(struct tffs_entry_segment
)), 0x0,
235 (new_num_segs
- num_segments
) * sizeof(struct tffs_entry_segment
));
236 num_segments
= new_num_segs
;
238 segments
[seg
].len
= read_len
;
239 segments
[seg
].val
= malloc(read_len
);
240 memcpy(segments
[seg
].val
, readbuf
+ TFFS_ENTRY_HEADER_SIZE
, read_len
);
245 if (num_segments
== 0) {
249 assert (segments
!= NULL
);
252 for (uint32_t i
= 0; i
< num_segments
; i
++) {
253 if (segments
[i
].val
== NULL
) {
254 /* missing segment */
258 len
+= segments
[i
].len
;
261 void *p
= malloc(len
);
264 for (uint32_t i
= 0; i
< num_segments
; i
++) {
265 memcpy(p
, segments
[i
].val
, segments
[i
].len
);
266 p
+= segments
[i
].len
;
272 static void parse_key_names(struct tffs_entry
*names_entry
,
273 struct tffs_key_name_table
*key_names
)
275 uint32_t pos
= 0, i
= 0;
276 struct tffs_name_table_entry
*name_item
;
278 key_names
->entries
= NULL
;
281 key_names
->entries
= realloc(key_names
->entries
,
282 sizeof(struct tffs_name_table_entry
) * (i
+ 1));
283 if (key_names
->entries
== NULL
) {
284 fprintf(stderr
, "ERROR: memory allocation failed!\n");
287 name_item
= &key_names
->entries
[i
];
289 name_item
->id
= read_uint32(names_entry
->val
, pos
);
290 pos
+= sizeof(uint32_t);
291 name_item
->val
= strdup((const char *)(names_entry
->val
+ pos
));
294 * There is no "length" field because the string values are
295 * simply NULL-terminated -> strlen() gives us the size.
297 pos
+= get_walk_size(strlen(name_item
->val
) + 1);
300 } while (pos
< names_entry
->len
);
305 static void show_all_key_names(struct tffs_key_name_table
*key_names
)
307 for (uint32_t i
= 0; i
< key_names
->size
; i
++)
308 printf("%s\n", key_names
->entries
[i
].val
);
311 static int show_all_key_value_pairs(struct tffs_key_name_table
*key_names
)
313 uint8_t has_value
= 0;
314 struct tffs_entry tmp
;
316 for (uint32_t i
= 0; i
< key_names
->size
; i
++) {
317 if (find_entry(key_names
->entries
[i
].id
, &tmp
)) {
318 printf("%s=", (const char *)key_names
->entries
[i
].val
);
319 print_entry_value(&tmp
);
327 fprintf(stderr
, "ERROR: no values found!\n");
334 static int show_matching_key_value(struct tffs_key_name_table
*key_names
)
336 struct tffs_entry tmp
;
339 for (uint32_t i
= 0; i
< key_names
->size
; i
++) {
340 name
= key_names
->entries
[i
].val
;
342 if (strncmp(name
, name_filter
, strlen(name
)) == 0) {
343 if (find_entry(key_names
->entries
[i
].id
, &tmp
)) {
344 print_entry_value(&tmp
);
350 "ERROR: no value found for name %s!\n",
357 fprintf(stderr
, "ERROR: Unknown key name %s!\n", name_filter
);
361 static int check_sector(off_t pos
)
363 if (read_sectoroob(pos
)) {
366 if (read_uint8(oobbuf
, 0x00) != 0xff) {
370 if (read_uint8(oobbuf
, 0x01) != 0xff) {
377 static int check_block(off_t pos
, uint32_t sector
)
379 if (!check_sector(pos
)) {
382 if (read_sector(pos
)) {
385 if (read_uint64(readbuf
, 0x00) != TFFS_BLOCK_HEADER_MAGIC
) {
386 fprintf(stderr
, "Warning: block without magic header. Skipping block\n");
389 if (read_uint32(readbuf
, 0x0c) != TFFS_SECTORS_PER_PAGE
) {
390 fprintf(stderr
, "Warning: block with wrong number of sectors per page. Skipping block\n");
394 uint32_t num_hdr_bad
= read_uint32(readbuf
, 0x0c);
395 for (uint32_t i
= 0; i
< num_hdr_bad
; i
++) {
396 uint32_t bad
= sector
+ read_uint64(readbuf
, 0x1c + sizeof(uint64_t)*i
);
397 sector_mark_bad(bad
);
403 static int scan_mtd(void)
405 struct mtd_info_user info
;
407 if (ioctl(mtdfd
, MEMGETINFO
, &info
)) {
411 blocksize
= info
.erasesize
;
413 sectors
= malloc(sizeof(*sectors
) + (info
.size
/ TFFS_SECTOR_SIZE
+ 7) / 8);
414 if (sectors
== NULL
) {
415 fprintf(stderr
, "ERROR: memory allocation failed!\n");
418 sectors
->num_sectors
= info
.size
/ TFFS_SECTOR_SIZE
;
419 memset(sectors
->sectors
, 0xff, (info
.size
/ TFFS_SECTOR_SIZE
+ 7) / 8);
421 uint32_t sector
= 0, valid_blocks
= 0;
422 uint8_t block_ok
= 0;
423 for (off_t pos
= 0; pos
< info
.size
; sector
++, pos
+= TFFS_SECTOR_SIZE
) {
424 if (pos
% info
.erasesize
== 0) {
425 block_ok
= check_block(pos
, sector
);
426 /* first sector of the block contains metadata
427 => handle it like a bad sector */
428 sector_mark_bad(sector
);
432 } else if (!block_ok
|| !sector_get_good(sector
) || !check_sector(pos
)) {
433 sector_mark_bad(sector
);
440 static void usage(int status
)
442 FILE *stream
= (status
!= EXIT_SUCCESS
) ? stderr
: stdout
;
444 fprintf(stream
, "Usage: %s [OPTIONS...]\n", progname
);
448 " -a list all key value pairs found in the TFFS file/device\n"
449 " -d <mtd> inspect the TFFS on mtd device <mtd>\n"
450 " -h show this screen\n"
451 " -l list all supported keys\n"
452 " -n <key name> display the value of the given key\n"
458 static void parse_options(int argc
, char *argv
[])
463 c
= getopt(argc
, argv
, "abd:hln:");
471 print_all_key_names
= false;
483 print_all_key_names
= true;
488 name_filter
= optarg
;
490 print_all_key_names
= false;
499 fprintf(stderr
, "ERROR: No input file (-d <file>) given!\n");
503 if (!show_all
&& !name_filter
&& !print_all_key_names
) {
505 "ERROR: either -l, -a or -n <key name> is required!\n");
510 int main(int argc
, char *argv
[])
512 int ret
= EXIT_FAILURE
;
513 struct tffs_entry name_table
;
514 struct tffs_key_name_table key_names
;
516 progname
= basename(argv
[0]);
518 parse_options(argc
, argv
);
520 mtdfd
= open(mtddev
, O_RDONLY
);
522 fprintf(stderr
, "ERROR: Failed to open tffs device %s\n",
528 fprintf(stderr
, "ERROR: Parsing blocks from tffs device %s failed\n", mtddev
);
529 fprintf(stderr
, " Is byte-swapping (-b) required?\n");
533 if (!find_entry(TFFS_ID_TABLE_NAME
, &name_table
)) {
534 fprintf(stderr
, "ERROR: No name table found on tffs device %s\n",
536 goto out_free_sectors
;
539 parse_key_names(&name_table
, &key_names
);
540 if (key_names
.size
< 1) {
541 fprintf(stderr
, "ERROR: No name table found on tffs device %s\n",
546 if (print_all_key_names
) {
547 show_all_key_names(&key_names
);
549 } else if (show_all
) {
550 ret
= show_all_key_value_pairs(&key_names
);
552 ret
= show_matching_key_value(&key_names
);
555 free(key_names
.entries
);
557 free(name_table
.val
);