/src/sleuthkit/tsk/fs/yaffs.cpp
Line | Count | Source (jump to first uncovered line) |
1 | | /* |
2 | | ** The Sleuth Kit |
3 | | ** |
4 | | ** Brian Carrier [carrier <at> sleuthkit [dot] org] |
5 | | ** Copyright (c) 2006-2011 Brian Carrier, Basis Technology. All Rights reserved |
6 | | ** Copyright (c) 2003-2005 Brian Carrier. All rights reserved |
7 | | ** |
8 | | ** TASK |
9 | | v** Copyright (c) 2002-2003 Brian Carrier, @stake Inc. All rights reserved |
10 | | ** |
11 | | ** Copyright (c) 1997,1998,1999, International Business Machines |
12 | | ** Corporation and others. All Rights Reserved. |
13 | | */ |
14 | | |
15 | | /** |
16 | | *\file yaffs.cpp |
17 | | * Contains the internal TSK YAFFS2 file system functions. |
18 | | */ |
19 | | |
20 | | /* TCT |
21 | | * LICENSE |
22 | | * This software is distributed under the IBM Public License. |
23 | | * AUTHOR(S) |
24 | | * Wietse Venema |
25 | | * IBM T.J. Watson Research |
26 | | * P.O. Box 704 |
27 | | * Yorktown Heights, NY 10598, USA |
28 | | --*/ |
29 | | |
30 | | #include <algorithm> |
31 | | #include <map> |
32 | | #include <memory> |
33 | | #include <string> |
34 | | #include <set> |
35 | | #include <vector> |
36 | | |
37 | | #include <string.h> |
38 | | |
39 | | #include "tsk_fs_i.h" |
40 | | #include "tsk_yaffs.h" |
41 | | #include "tsk_fs.h" |
42 | | |
43 | | /* |
44 | | * Implementation Notes: |
45 | | * - As inode, we use object id and a version number derived from the |
46 | | * number of unique sequence ids for the object still left in the |
47 | | * file system. |
48 | | * |
49 | | * - The version numbers start at 1 and increase as they get closer to |
50 | | * the the latest version. Version number 0 is a special version |
51 | | * that is equivalent to the latest version (without having to know |
52 | | * the latest version number.) |
53 | | * |
54 | | * - Since inodes are composed using the object id in the least |
55 | | * significant bits and the version up higher, requesting the |
56 | | * inode that matches the object id you are looking for will |
57 | | * retrieve the latest version of this object. |
58 | | * |
59 | | * - Files always exist in the latest version of their parent directory |
60 | | * only. |
61 | | * |
62 | | * - Filenames are not unique even with attached version numbers, since |
63 | | * version numbers are namespaced by inode. |
64 | | * |
65 | | * - The cache stores a lot of info via the structure. As this is |
66 | | * used for investigations, we assume these decisions will be updated |
67 | | * to expose the most useful view of this log based file system. TSK |
68 | | * doesn't seem have a real way to expose a versioned view of a log |
69 | | * based file system like this. Shoehorning it into the framework |
70 | | * ends up dropping some information. I looked at using resource |
71 | | * streams as versions, but the abstraction breaks quickly. |
72 | | * |
73 | | */ |
74 | | |
75 | | static const int TWELVE_BITS_MASK = 0xFFF; // Only keep 12 bits |
76 | | |
77 | | static uint8_t |
78 | | yaffsfs_read_header(YAFFSFS_INFO *yfs, YaffsHeader ** header, TSK_OFF_T offset); |
79 | | static uint8_t |
80 | | yaffsfs_load_attrs(TSK_FS_FILE *file); |
81 | | |
82 | | /** |
83 | | * Generate an inode number based on the file's object and version numbers |
84 | | */ |
85 | | static TSK_RETVAL_ENUM |
86 | 0 | yaffscache_obj_id_and_version_to_inode(uint32_t obj_id, uint32_t version_num, TSK_INUM_T *inode) { |
87 | 0 | if ((obj_id & ~YAFFS_OBJECT_ID_MASK) != 0) { |
88 | 0 | tsk_error_reset(); |
89 | 0 | tsk_error_set_errno(TSK_ERR_FS); |
90 | 0 | tsk_error_set_errstr( |
91 | 0 | "yaffsfs_parse_image_load_cache: Max object ID %" PRIu32 " is invalid", obj_id); |
92 | 0 | return TSK_ERR; |
93 | 0 | } |
94 | | |
95 | 0 | if ((version_num & ~YAFFS_VERSION_NUM_MASK) != 0) { |
96 | 0 | tsk_error_reset(); |
97 | 0 | tsk_error_set_errno(TSK_ERR_FS); |
98 | 0 | tsk_error_set_errstr( |
99 | 0 | "yaffsfs_parse_image_load_cache: Max version number %" PRIu32 " is invalid", version_num); |
100 | 0 | return TSK_ERR; |
101 | 0 | } |
102 | | |
103 | 0 | *inode = obj_id | (version_num << YAFFS_VERSION_NUM_SHIFT); |
104 | 0 | return TSK_OK; |
105 | 0 | } |
106 | | |
107 | | /** |
108 | | * Given the TSK-generated inode address, extract the object id and version number from it |
109 | | */ |
110 | | static TSK_RETVAL_ENUM |
111 | 0 | yaffscache_inode_to_obj_id_and_version(TSK_INUM_T inode, uint32_t *obj_id, uint32_t *version_num) { |
112 | 0 | *obj_id = inode & YAFFS_OBJECT_ID_MASK; |
113 | 0 | *version_num = (inode >> YAFFS_VERSION_NUM_SHIFT) & YAFFS_VERSION_NUM_MASK; |
114 | |
|
115 | 0 | return TSK_OK; |
116 | 0 | } |
117 | | |
118 | | /* |
119 | | * Order it like yaffs2.git does -- sort by (seq_num, offset/block) |
120 | | */ |
121 | | static int |
122 | | yaffscache_chunk_compare(YaffsCacheChunk *curr, uint32_t addee_obj_id, TSK_OFF_T addee_offset, uint32_t addee_seq_number) |
123 | 0 | { |
124 | 0 | if (curr->ycc_obj_id == addee_obj_id) { |
125 | 0 | if (curr->ycc_seq_number == addee_seq_number) { |
126 | 0 | if (curr->ycc_offset == addee_offset) { |
127 | 0 | return 0; |
128 | 0 | } |
129 | 0 | else if (curr->ycc_offset < addee_offset) { |
130 | 0 | return -1; |
131 | 0 | } |
132 | 0 | else { |
133 | 0 | return 1; |
134 | 0 | } |
135 | 0 | } |
136 | 0 | else if (curr->ycc_seq_number < addee_seq_number) { |
137 | 0 | return -1; |
138 | 0 | } |
139 | 0 | else { |
140 | 0 | return 1; |
141 | 0 | } |
142 | 0 | } |
143 | 0 | else if (curr->ycc_obj_id < addee_obj_id) { |
144 | 0 | return -1; |
145 | 0 | } |
146 | 0 | else { |
147 | 0 | return 1; |
148 | 0 | } |
149 | 0 | } |
150 | | |
151 | | static TSK_RETVAL_ENUM |
152 | | yaffscache_chunk_find_insertion_point(YAFFSFS_INFO *yfs, uint32_t obj_id, TSK_OFF_T offset, uint32_t seq_number, YaffsCacheChunk **chunk) |
153 | 0 | { |
154 | 0 | YaffsCacheChunk *curr, *prev; |
155 | | |
156 | | // Have we seen this obj_id? If not, add an entry for it |
157 | 0 | if (yfs->chunkMap->find(obj_id) == yfs->chunkMap->end()) { |
158 | 0 | fflush(stderr); |
159 | 0 | YaffsCacheChunkGroup chunkGroup; |
160 | 0 | chunkGroup.cache_chunks_head = NULL; |
161 | 0 | chunkGroup.cache_chunks_tail = NULL; |
162 | 0 | yfs->chunkMap->insert(std::make_pair(obj_id, chunkGroup)); |
163 | 0 | } |
164 | |
|
165 | 0 | curr = yfs->chunkMap->operator[](obj_id).cache_chunks_head; |
166 | 0 | prev = NULL; |
167 | |
|
168 | 0 | if (chunk == NULL) { |
169 | 0 | return TSK_ERR; |
170 | 0 | } |
171 | | |
172 | 0 | while(curr != NULL) { |
173 | | // Compares obj id, then seq num, then offset. -1 => current < new |
174 | 0 | int cmp = yaffscache_chunk_compare(curr, obj_id, offset, seq_number); |
175 | |
|
176 | 0 | if (cmp == 0) { |
177 | 0 | *chunk = curr; |
178 | 0 | return TSK_OK; |
179 | 0 | } |
180 | 0 | else if (cmp == 1) { |
181 | 0 | *chunk = prev; |
182 | 0 | return TSK_STOP; |
183 | 0 | } |
184 | | |
185 | 0 | prev = curr; |
186 | 0 | curr = curr->ycc_next; |
187 | 0 | } |
188 | | |
189 | 0 | *chunk = prev; |
190 | 0 | return TSK_STOP; |
191 | 0 | } |
192 | | |
193 | | /** |
194 | | * Add a chunk to the cache. |
195 | | * @param yfs |
196 | | * @param offset Byte offset this chunk was found in (in the disk image) |
197 | | * @param seq_number Sequence number of this chunk |
198 | | * @param obj_id Object Id this chunk is associated with |
199 | | * @param parent_id Parent object ID that this chunk/object is associated with |
200 | | */ |
201 | | static TSK_RETVAL_ENUM |
202 | | yaffscache_chunk_add(YAFFSFS_INFO *yfs, TSK_OFF_T offset, uint32_t seq_number, |
203 | | uint32_t obj_id, uint32_t chunk_id, uint32_t parent_id) |
204 | 0 | { |
205 | 0 | TSK_RETVAL_ENUM result; |
206 | 0 | YaffsCacheChunk *prev; |
207 | 0 | YaffsCacheChunk *chunk; |
208 | 0 | if ((chunk = (YaffsCacheChunk*)tsk_malloc(sizeof(YaffsCacheChunk))) == NULL) { |
209 | 0 | return TSK_ERR; |
210 | 0 | } |
211 | | |
212 | 0 | chunk->ycc_offset = offset; |
213 | 0 | chunk->ycc_seq_number = seq_number; |
214 | 0 | chunk->ycc_obj_id = obj_id; |
215 | 0 | chunk->ycc_chunk_id = chunk_id; |
216 | 0 | chunk->ycc_parent_id = parent_id; |
217 | | |
218 | | // Bit of a hack here. In some images, the root directory (obj_id = 1) lists iself as its parent |
219 | | // directory, which can cause issues later when we get directory contents. To prevent this, |
220 | | // if a chunk comes in with obj_id = 1 and parent_id = 1, manually set the parent ID to zero. |
221 | 0 | if ((obj_id == 1) && (parent_id == 1)) { |
222 | 0 | chunk->ycc_parent_id = 0; |
223 | 0 | } |
224 | | |
225 | | // Find the chunk that should go right before the new chunk |
226 | 0 | result = yaffscache_chunk_find_insertion_point(yfs, obj_id, offset, seq_number, &prev); |
227 | |
|
228 | 0 | if (result == TSK_ERR) { |
229 | 0 | return TSK_ERR; |
230 | 0 | } |
231 | | |
232 | 0 | if (prev == NULL) { |
233 | | // No previous chunk - new chunk is the lowest we've seen and the new start of the list |
234 | 0 | chunk->ycc_prev = NULL; |
235 | 0 | chunk->ycc_next = yfs->chunkMap->operator[](obj_id).cache_chunks_head; |
236 | 0 | } |
237 | 0 | else { |
238 | 0 | chunk->ycc_prev = prev; |
239 | 0 | chunk->ycc_next = prev->ycc_next; |
240 | 0 | } |
241 | |
|
242 | 0 | if (chunk->ycc_next != NULL) { |
243 | | // If we're not at the end, set the prev pointer on the next chunk to point to our new one |
244 | 0 | chunk->ycc_next->ycc_prev = chunk; |
245 | 0 | } |
246 | 0 | else { |
247 | 0 | yfs->chunkMap->operator[](obj_id).cache_chunks_tail = chunk; |
248 | 0 | } |
249 | |
|
250 | 0 | if (chunk->ycc_prev != NULL) { |
251 | | // If we're not at the beginning, set the next pointer on the previous chunk to point at our new one |
252 | 0 | chunk->ycc_prev->ycc_next = chunk; |
253 | 0 | } |
254 | 0 | else { |
255 | 0 | yfs->chunkMap->operator[](obj_id).cache_chunks_head = chunk; |
256 | 0 | } |
257 | |
|
258 | 0 | return TSK_OK; |
259 | 0 | } |
260 | | |
261 | | |
262 | | /** |
263 | | * Get the file object from the cache. |
264 | | * @returns TSK_OK if it was found and TSK_STOP if we did not find it |
265 | | */ |
266 | | static TSK_RETVAL_ENUM |
267 | | yaffscache_object_find(YAFFSFS_INFO *yfs, uint32_t obj_id, YaffsCacheObject **obj) |
268 | 0 | { |
269 | 0 | YaffsCacheObject *curr, *prev; |
270 | 0 | curr = yfs->cache_objects; |
271 | 0 | prev = NULL; |
272 | |
|
273 | 0 | if (obj == NULL) { |
274 | 0 | return TSK_ERR; |
275 | 0 | } |
276 | | |
277 | 0 | while(curr != NULL) { |
278 | 0 | if (curr->yco_obj_id == obj_id) { |
279 | 0 | *obj = curr; |
280 | 0 | return TSK_OK; |
281 | 0 | } |
282 | 0 | else if (curr->yco_obj_id > obj_id) { |
283 | 0 | *obj = prev; |
284 | 0 | return TSK_STOP; |
285 | 0 | } |
286 | | |
287 | 0 | prev = curr; |
288 | 0 | curr = curr->yco_next; |
289 | 0 | } |
290 | | |
291 | 0 | *obj = prev; |
292 | 0 | return TSK_STOP; |
293 | 0 | } |
294 | | |
295 | | /** |
296 | | * Add an object to the cache if it does not already exist in there. |
297 | | * @returns TSK_ERR on error, TSK_OK otherwise. |
298 | | */ |
299 | | static TSK_RETVAL_ENUM |
300 | | yaffscache_object_find_or_add(YAFFSFS_INFO *yfs, uint32_t obj_id, YaffsCacheObject **obj) |
301 | 0 | { |
302 | 0 | YaffsCacheObject *prev; |
303 | 0 | TSK_RETVAL_ENUM result; |
304 | |
|
305 | 0 | if (obj == NULL) { |
306 | 0 | return TSK_ERR; |
307 | 0 | } |
308 | | |
309 | | // Look for this obj_id in yfs->cache_objects |
310 | | // If not found, add it in the correct spot |
311 | | // yaffscache_object_find returns the last object with obj_id less than the one |
312 | | // we were searching for, so use that to insert the new one in the list |
313 | 0 | result = yaffscache_object_find(yfs, obj_id, &prev); |
314 | 0 | if (result == TSK_OK) { |
315 | 0 | *obj = prev; |
316 | 0 | return TSK_OK; |
317 | 0 | } |
318 | 0 | else if (result == TSK_STOP) { |
319 | 0 | *obj = (YaffsCacheObject *) tsk_malloc(sizeof(YaffsCacheObject)); |
320 | 0 | (*obj)->yco_obj_id = obj_id; |
321 | 0 | if (prev == NULL) { |
322 | 0 | (*obj)->yco_next = yfs->cache_objects; |
323 | 0 | yfs->cache_objects = *obj; |
324 | 0 | } |
325 | 0 | else { |
326 | 0 | (*obj)->yco_next = prev->yco_next; |
327 | 0 | prev->yco_next = (*obj); |
328 | 0 | } |
329 | 0 | return TSK_OK; |
330 | 0 | } |
331 | 0 | else { |
332 | 0 | *obj = NULL; |
333 | 0 | return TSK_ERR; |
334 | 0 | } |
335 | 0 | } |
336 | | |
337 | | static TSK_RETVAL_ENUM |
338 | | yaffscache_object_add_version(YaffsCacheObject *obj, YaffsCacheChunk *chunk) |
339 | 0 | { |
340 | 0 | uint32_t ver_number; |
341 | 0 | YaffsCacheChunk *header_chunk = NULL; |
342 | 0 | YaffsCacheVersion *version; |
343 | | |
344 | | // Going to try ignoring unlinked/deleted headers (objID 3 and 4) |
345 | 0 | if ((chunk->ycc_chunk_id == 0) && (chunk->ycc_parent_id != YAFFS_OBJECT_UNLINKED) |
346 | 0 | &&(chunk->ycc_parent_id != YAFFS_OBJECT_DELETED)) { |
347 | 0 | header_chunk = chunk; |
348 | 0 | } |
349 | | |
350 | | /* If this is the second version (since last header_chunk is not NULL) and no |
351 | | * header was added, get rid of this incomplete old version -- can't be |
352 | | * reasonably recovered. |
353 | | * |
354 | | * TODO: These chunks are still in the structure and can be walked, |
355 | | * but I'm not sure how to represent this set of data chunks |
356 | | * with no metadata under TSK. This is rare and we don't have |
357 | | * a testcase for it now. Punting right now. |
358 | | * |
359 | | * Edit: Shouldn't get to this point anymore. Changes to |
360 | | * yaffscache_versions_insert_chunk make a version continue until it |
361 | | * has a header block. |
362 | | */ |
363 | 0 | if (obj->yco_latest != NULL) { |
364 | 0 | if (obj->yco_latest->ycv_header_chunk == NULL) { |
365 | 0 | YaffsCacheVersion *incomplete = obj->yco_latest; |
366 | |
|
367 | 0 | if (tsk_verbose) |
368 | 0 | tsk_fprintf(stderr, "yaffscache_object_add_version: " |
369 | 0 | "removed an incomplete first version (no header)\n"); |
370 | |
|
371 | 0 | obj->yco_latest = obj->yco_latest->ycv_prior; |
372 | 0 | free(incomplete); |
373 | 0 | } |
374 | 0 | } |
375 | |
|
376 | 0 | if (obj->yco_latest != NULL) { |
377 | 0 | ver_number = obj->yco_latest->ycv_version + 1; |
378 | | |
379 | | /* Until a new header is given, use the last seen header. */ |
380 | 0 | if (header_chunk == NULL) { |
381 | 0 | header_chunk = obj->yco_latest->ycv_header_chunk; |
382 | | |
383 | | // If we haven't seen a good header yet and we have a deleted/unlinked one, use it |
384 | 0 | if ((header_chunk == NULL) && (chunk->ycc_chunk_id == 0)) { |
385 | 0 | header_chunk = chunk; |
386 | 0 | } |
387 | 0 | } |
388 | 0 | } |
389 | 0 | else { |
390 | 0 | ver_number = 1; |
391 | 0 | } |
392 | |
|
393 | 0 | if ((version = (YaffsCacheVersion *) tsk_malloc(sizeof(YaffsCacheVersion))) == NULL) { |
394 | 0 | return TSK_ERR; |
395 | 0 | } |
396 | | |
397 | 0 | version->ycv_prior = obj->yco_latest; |
398 | 0 | version->ycv_version = ver_number; |
399 | 0 | version->ycv_seq_number = chunk->ycc_seq_number; |
400 | 0 | version->ycv_header_chunk = header_chunk; |
401 | 0 | version->ycv_first_chunk = chunk; |
402 | 0 | version->ycv_last_chunk = chunk; |
403 | |
|
404 | 0 | obj->yco_latest = version; |
405 | |
|
406 | 0 | return TSK_OK; |
407 | 0 | } |
408 | | |
409 | | /** |
410 | | * Add a chunk to its corresponding object in the cache. |
411 | | */ |
412 | | static TSK_RETVAL_ENUM |
413 | | yaffscache_versions_insert_chunk(YAFFSFS_INFO *yfs, YaffsCacheChunk *chunk) |
414 | 0 | { |
415 | 0 | YaffsCacheObject *obj; |
416 | 0 | TSK_RETVAL_ENUM result; |
417 | 0 | YaffsCacheVersion *version; |
418 | | |
419 | | // Building a list in yfs->cache_objects, sorted by obj_id |
420 | 0 | result = yaffscache_object_find_or_add(yfs, chunk->ycc_obj_id, &obj); |
421 | 0 | if (result != TSK_OK) { |
422 | 0 | return TSK_ERR; |
423 | 0 | } |
424 | 0 | version = obj->yco_latest; |
425 | | |
426 | | /* First chunk in this object? */ |
427 | 0 | if (version == NULL) { |
428 | 0 | yaffscache_object_add_version(obj, chunk); |
429 | 0 | } |
430 | 0 | else { |
431 | | /* Chunk in the same update? */ |
432 | 0 | if (chunk->ycc_seq_number == version->ycv_seq_number) { |
433 | 0 | version->ycv_last_chunk = chunk; |
434 | 0 | if ((chunk->ycc_chunk_id == 0) && (chunk->ycc_parent_id != YAFFS_OBJECT_UNLINKED) |
435 | 0 | &&(chunk->ycc_parent_id != YAFFS_OBJECT_DELETED)) { |
436 | 0 | version->ycv_header_chunk = chunk; |
437 | 0 | } |
438 | 0 | else if ((chunk->ycc_chunk_id == 0) && (version->ycv_header_chunk == NULL)) { |
439 | 0 | version->ycv_header_chunk = chunk; |
440 | 0 | } |
441 | 0 | } |
442 | | // If there was no header for the last version, continue adding to it instead |
443 | | // of starting a new version. |
444 | 0 | else if (version->ycv_header_chunk == NULL) { |
445 | 0 | version->ycv_seq_number = chunk->ycc_seq_number; |
446 | 0 | version->ycv_last_chunk = chunk; |
447 | 0 | if ((chunk->ycc_chunk_id == 0) && (chunk->ycc_parent_id != YAFFS_OBJECT_UNLINKED) |
448 | 0 | &&(chunk->ycc_parent_id != YAFFS_OBJECT_DELETED)) { |
449 | 0 | version->ycv_header_chunk = chunk; |
450 | 0 | } |
451 | 0 | else if ((chunk->ycc_chunk_id == 0) && (version->ycv_header_chunk == NULL)) { |
452 | 0 | version->ycv_header_chunk = chunk; |
453 | 0 | } |
454 | 0 | } |
455 | 0 | else if (chunk->ycc_chunk_id == 0) { // Directories only have a header block |
456 | | // If we're looking at a new version of a directory where the previous version had the same name, |
457 | | // leave everything in the same version. Multiple versions of the same directory aren't really giving us |
458 | | // any information. |
459 | 0 | YaffsHeader * newHeader; |
460 | 0 | yaffsfs_read_header(yfs, &newHeader, chunk->ycc_offset); |
461 | 0 | if ((newHeader != NULL) && (newHeader->obj_type == YAFFS_TYPE_DIRECTORY)) { |
462 | | // Read in the old header |
463 | 0 | YaffsHeader * oldHeader; |
464 | 0 | yaffsfs_read_header(yfs, &oldHeader, version->ycv_header_chunk->ycc_offset); |
465 | 0 | if ((oldHeader != NULL) && (oldHeader->obj_type == YAFFS_TYPE_DIRECTORY) && |
466 | 0 | (0 == strncmp(oldHeader->name, newHeader->name, YAFFS_HEADER_NAME_LENGTH))) { |
467 | 0 | version->ycv_seq_number = chunk->ycc_seq_number; |
468 | 0 | version->ycv_last_chunk = chunk; |
469 | 0 | version->ycv_header_chunk = chunk; |
470 | 0 | } |
471 | 0 | else { |
472 | | // The older header either isn't a directory or it doesn't have the same name, so leave it |
473 | | // as its own version |
474 | 0 | yaffscache_object_add_version(obj, chunk); |
475 | 0 | } |
476 | 0 | } |
477 | 0 | else { |
478 | | // Not a directory |
479 | 0 | yaffscache_object_add_version(obj, chunk); |
480 | 0 | } |
481 | 0 | } |
482 | 0 | else { |
483 | | // Otherwise, add this chunk as the start of a new version |
484 | 0 | yaffscache_object_add_version(obj, chunk); |
485 | 0 | } |
486 | 0 | } |
487 | |
|
488 | 0 | return TSK_OK; |
489 | 0 | } |
490 | | |
491 | | static TSK_RETVAL_ENUM |
492 | | yaffscache_versions_compute(YAFFSFS_INFO *yfs) |
493 | 0 | { |
494 | 0 | std::map<unsigned int,YaffsCacheChunkGroup>::iterator iter; |
495 | 0 | for( iter = yfs->chunkMap->begin(); iter != yfs->chunkMap->end(); ++iter ) { |
496 | 0 | YaffsCacheChunk *chunk_curr = yfs->chunkMap->operator[](iter->first).cache_chunks_head; |
497 | |
|
498 | 0 | while(chunk_curr != NULL) { |
499 | 0 | if (yaffscache_versions_insert_chunk(yfs, chunk_curr) != TSK_OK) { |
500 | 0 | return TSK_ERR; |
501 | 0 | } |
502 | | |
503 | 0 | chunk_curr = chunk_curr->ycc_next; |
504 | 0 | } |
505 | 0 | } |
506 | | |
507 | 0 | return TSK_OK; |
508 | 0 | } |
509 | | |
510 | | /** |
511 | | * Callback for yaffscache_find_children() |
512 | | * @param obj Object that is a child |
513 | | * @param version Version of the object |
514 | | * @param args Pointer to what was passed into yaffscache_find_children |
515 | | */ |
516 | | typedef TSK_RETVAL_ENUM yc_find_children_cb(YaffsCacheObject *obj, YaffsCacheVersion *version, void *args); |
517 | | |
518 | | /** |
519 | | * Search the cache for objects that are children of the given address. |
520 | | * @param yfs |
521 | | * @param parent_inode Inode of folder/directory |
522 | | * @param cb Call back to call for each found child |
523 | | * @param args Pointer to structure that will be passed to cb |
524 | | * @returns TSK_ERR on error |
525 | | */ |
526 | | static TSK_RETVAL_ENUM |
527 | | yaffscache_find_children(YAFFSFS_INFO *yfs, TSK_INUM_T parent_inode, yc_find_children_cb cb, void *args) |
528 | 0 | { |
529 | 0 | YaffsCacheObject *obj; |
530 | |
|
531 | 0 | uint32_t parent_id, version_num; |
532 | 0 | if (yaffscache_inode_to_obj_id_and_version(parent_inode, &parent_id, &version_num) != TSK_OK) { |
533 | 0 | return TSK_ERR; |
534 | 0 | } |
535 | | |
536 | | /* Iterate over all objects and all versions of the objects to see if one is the child |
537 | | * of the given parent. */ |
538 | 0 | for (obj = yfs->cache_objects; obj != NULL; obj = obj->yco_next) { |
539 | 0 | YaffsCacheVersion *version; |
540 | 0 | for (version = obj->yco_latest; version != NULL; version = version->ycv_prior) { |
541 | | /* Is this an incomplete version? */ |
542 | 0 | if (version->ycv_header_chunk == NULL) { |
543 | 0 | continue; |
544 | 0 | } |
545 | | |
546 | 0 | if (version->ycv_header_chunk->ycc_parent_id == parent_id) { |
547 | 0 | TSK_RETVAL_ENUM result = cb(obj, version, args); |
548 | 0 | if (result != TSK_OK) |
549 | 0 | return result; |
550 | 0 | } |
551 | 0 | } |
552 | 0 | } |
553 | | |
554 | 0 | return TSK_OK; |
555 | 0 | } |
556 | | |
557 | | /** |
558 | | * Lookup an object based on its inode. |
559 | | * @param yfs |
560 | | * @param inode |
561 | | * @param version [out] Pointer to store version of the object that was found (if inode had a version of 0) |
562 | | * @param obj_ret [out] Pointer to store found object into |
563 | | * @returns TSK_ERR on error. |
564 | | */ |
565 | | static TSK_RETVAL_ENUM |
566 | 0 | yaffscache_version_find_by_inode(YAFFSFS_INFO *yfs, TSK_INUM_T inode, YaffsCacheVersion **version, YaffsCacheObject **obj_ret) { |
567 | 0 | uint32_t obj_id, version_num; |
568 | 0 | YaffsCacheObject *obj; |
569 | 0 | YaffsCacheVersion *curr; |
570 | |
|
571 | 0 | if (version == NULL) { |
572 | 0 | return TSK_ERR; |
573 | 0 | } |
574 | | |
575 | | // convert inode to obj and version and find it in cache |
576 | 0 | if (yaffscache_inode_to_obj_id_and_version(inode, &obj_id, &version_num) != TSK_OK) { |
577 | 0 | *version = NULL; |
578 | 0 | return TSK_ERR; |
579 | 0 | } |
580 | | |
581 | 0 | if (yaffscache_object_find(yfs, obj_id, &obj) != TSK_OK) { |
582 | 0 | *version = NULL; |
583 | 0 | return TSK_ERR; |
584 | 0 | } |
585 | | |
586 | 0 | if (version_num == 0) { |
587 | 0 | if (obj_ret != NULL) { |
588 | 0 | *obj_ret = obj; |
589 | 0 | } |
590 | 0 | *version = obj->yco_latest; |
591 | 0 | return TSK_OK; |
592 | 0 | } |
593 | | |
594 | | // Find the requested version in the list. |
595 | 0 | for(curr = obj->yco_latest; curr != NULL; curr = curr->ycv_prior) { |
596 | 0 | if (curr->ycv_version == version_num) { |
597 | 0 | if (obj_ret != NULL) { |
598 | 0 | *obj_ret = obj; |
599 | 0 | } |
600 | 0 | *version = curr; |
601 | 0 | return TSK_OK; |
602 | 0 | } |
603 | 0 | } |
604 | | |
605 | 0 | if (obj_ret != NULL) { |
606 | 0 | *obj_ret = NULL; |
607 | 0 | } |
608 | 0 | *version = NULL; |
609 | 0 | return TSK_ERR; |
610 | 0 | } |
611 | | |
612 | | static void |
613 | | yaffscache_object_dump(FILE *fp, YaffsCacheObject *obj) |
614 | 0 | { |
615 | 0 | YaffsCacheVersion *next_version = obj->yco_latest; |
616 | 0 | YaffsCacheChunk *chunk = next_version->ycv_last_chunk; |
617 | |
|
618 | 0 | fprintf(fp, "Object %d\n", obj->yco_obj_id); |
619 | 0 | while(chunk != NULL && chunk->ycc_obj_id == obj->yco_obj_id) { |
620 | 0 | if (next_version != NULL && |
621 | 0 | chunk == next_version->ycv_last_chunk) { |
622 | 0 | fprintf(fp, " @%d: %p %p %p\n", |
623 | 0 | next_version->ycv_version, |
624 | 0 | (void*) next_version->ycv_header_chunk, |
625 | 0 | (void*) next_version->ycv_first_chunk, |
626 | 0 | (void*)next_version->ycv_last_chunk); |
627 | 0 | next_version = next_version->ycv_prior; |
628 | 0 | } |
629 | |
|
630 | 0 | fprintf(fp, " + %p %08x %08x %0" PRIxOFF "\n", |
631 | 0 | (void*) chunk, |
632 | 0 | chunk->ycc_chunk_id, |
633 | 0 | chunk->ycc_seq_number, |
634 | 0 | chunk->ycc_offset); |
635 | |
|
636 | 0 | chunk = chunk->ycc_prev; |
637 | 0 | } |
638 | 0 | } |
639 | | |
640 | | /* |
641 | | static void |
642 | | yaffscache_objects_dump(FILE *fp, YAFFSFS_INFO *yfs) |
643 | | { |
644 | | YaffsCacheObject *obj; |
645 | | |
646 | | for(obj = yfs->cache_objects; obj != NULL; obj = obj->yco_next) |
647 | | yaffscache_object_dump(fp, obj); |
648 | | } |
649 | | */ |
650 | | |
651 | | static void |
652 | | yaffscache_objects_stats(YAFFSFS_INFO *yfs, |
653 | | unsigned int *obj_count, |
654 | | uint32_t *obj_first, uint32_t *obj_last, |
655 | | uint32_t *version_count, |
656 | | uint32_t *version_first, uint32_t *version_last) |
657 | 0 | { |
658 | 0 | YaffsCacheObject *obj; |
659 | 0 | YaffsCacheVersion *ver; |
660 | | |
661 | | /* deleted and unlinked special objects don't have headers */ |
662 | 0 | *obj_count = 2; |
663 | 0 | *obj_first = 0xffffffff; |
664 | 0 | *obj_last = 0; |
665 | |
|
666 | 0 | *version_count = 0; |
667 | 0 | *version_first = 0xffffffff; |
668 | 0 | *version_last = 0; |
669 | |
|
670 | 0 | for(obj = yfs->cache_objects; obj != NULL; obj = obj->yco_next) { |
671 | 0 | *obj_count += 1; |
672 | 0 | if (obj->yco_obj_id < *obj_first) |
673 | 0 | *obj_first = obj->yco_obj_id; |
674 | 0 | if (obj->yco_obj_id > *obj_last) |
675 | 0 | *obj_last = obj->yco_obj_id; |
676 | |
|
677 | 0 | for(ver = obj->yco_latest; ver != NULL; ver = ver->ycv_prior) { |
678 | 0 | *version_count += 1; |
679 | 0 | if (ver->ycv_seq_number < *version_first) |
680 | 0 | *version_first = ver->ycv_seq_number; |
681 | 0 | if (ver->ycv_seq_number > *version_last) |
682 | 0 | *version_last = ver->ycv_seq_number; |
683 | 0 | } |
684 | 0 | } |
685 | 0 | } |
686 | | |
687 | | static void |
688 | | yaffscache_objects_free(YAFFSFS_INFO *yfs) |
689 | 0 | { |
690 | 0 | if ((yfs != NULL) && (yfs->cache_objects != NULL)) { |
691 | 0 | YaffsCacheObject *obj = yfs->cache_objects; |
692 | 0 | while(obj != NULL) { |
693 | 0 | YaffsCacheObject *to_free = obj; |
694 | |
|
695 | 0 | YaffsCacheVersion *ver = obj->yco_latest; |
696 | 0 | while(ver != NULL) { |
697 | 0 | YaffsCacheVersion *v_to_free = ver; |
698 | 0 | ver = ver->ycv_prior; |
699 | 0 | free(v_to_free); |
700 | 0 | } |
701 | |
|
702 | 0 | obj = obj->yco_next; |
703 | 0 | free(to_free); |
704 | 0 | } |
705 | 0 | } |
706 | 0 | } |
707 | | |
708 | | static void |
709 | | yaffscache_chunks_free(YAFFSFS_INFO *yfs) |
710 | 0 | { |
711 | 0 | if ((yfs != NULL) && (yfs->chunkMap != NULL)) { |
712 | | // Free the YaffsCacheChunks in each ChunkGroup |
713 | 0 | std::map<unsigned int,YaffsCacheChunkGroup>::iterator iter; |
714 | 0 | for( iter = yfs->chunkMap->begin(); iter != yfs->chunkMap->end(); ++iter ) { |
715 | 0 | YaffsCacheChunk *chunk = yfs->chunkMap->operator[](iter->first).cache_chunks_head; |
716 | 0 | while(chunk != NULL) { |
717 | 0 | YaffsCacheChunk *to_free = chunk; |
718 | 0 | chunk = chunk->ycc_next; |
719 | 0 | free(to_free); |
720 | 0 | } |
721 | 0 | } |
722 | | |
723 | | // Free the map |
724 | 0 | yfs->chunkMap->clear(); |
725 | 0 | delete yfs->chunkMap; |
726 | 0 | } |
727 | |
|
728 | 0 | } |
729 | | |
730 | | |
731 | | |
732 | | /* |
733 | | * Parsing and helper functions |
734 | | * |
735 | | * |
736 | | */ |
737 | | |
738 | | /* Function to parse config file |
739 | | * |
740 | | * @param img_info Image info for this image |
741 | | * @param map<string, int> Stores values from config file indexed on parameter name |
742 | | * @returns YAFFS_CONFIG_STATUS One of YAFFS_CONFIG_OK, YAFFS_CONFIG_FILE_NOT_FOUND, or YAFFS_CONFIG_ERROR |
743 | | */ |
744 | | static YAFFS_CONFIG_STATUS |
745 | 0 | yaffs_load_config_file(TSK_IMG_INFO * a_img_info, std::map<std::string, std::string> & results) { |
746 | 0 | size_t config_file_name_len; |
747 | 0 | TSK_TCHAR * config_file_name; |
748 | 0 | FILE* config_file; |
749 | 0 | char buf[1001]; |
750 | | |
751 | | // Ensure there is at least one image name |
752 | 0 | if (a_img_info->num_img < 1) { |
753 | 0 | return YAFFS_CONFIG_ERROR; |
754 | 0 | } |
755 | | |
756 | | // Construct the name of the config file from the first image name |
757 | 0 | config_file_name_len = TSTRLEN(a_img_info->images[0]); |
758 | 0 | config_file_name_len += TSTRLEN(YAFFS_CONFIG_FILE_SUFFIX); |
759 | 0 | config_file_name = (TSK_TCHAR *) tsk_malloc(sizeof(TSK_TCHAR) * (config_file_name_len + 1)); |
760 | |
|
761 | 0 | TSTRNCPY(config_file_name, a_img_info->images[0], config_file_name_len + 1); |
762 | 0 | TSTRNCAT(config_file_name, YAFFS_CONFIG_FILE_SUFFIX, TSTRLEN(YAFFS_CONFIG_FILE_SUFFIX) + 1); |
763 | |
|
764 | | #ifdef TSK_WIN32 |
765 | | HANDLE hWin; |
766 | | |
767 | | if ((hWin = CreateFile(config_file_name, GENERIC_READ, |
768 | | FILE_SHARE_READ, 0, OPEN_EXISTING, 0, |
769 | | 0)) == INVALID_HANDLE_VALUE) { |
770 | | |
771 | | // For the moment, assume that the file just doesn't exist, which isn't an error |
772 | | free(config_file_name); |
773 | | return YAFFS_CONFIG_FILE_NOT_FOUND; |
774 | | } |
775 | | config_file = _fdopen(_open_osfhandle((intptr_t) hWin, _O_RDONLY), "r"); |
776 | | if (config_file == NULL) { |
777 | | tsk_error_reset(); |
778 | | tsk_error_set_errno(TSK_ERR_FS); |
779 | | tsk_error_set_errstr( |
780 | | "yaffs_load_config: Error converting Windows handle to C handle"); |
781 | | free(config_file_name); |
782 | | CloseHandle(hWin); |
783 | | return YAFFS_CONFIG_ERROR; |
784 | | } |
785 | | #else |
786 | 0 | if (NULL == (config_file = fopen(config_file_name, "r"))) { |
787 | 0 | free(config_file_name); |
788 | 0 | return YAFFS_CONFIG_FILE_NOT_FOUND; |
789 | 0 | } |
790 | 0 | #endif |
791 | | |
792 | 0 | while(fgets(buf, 1000, config_file) != NULL) { |
793 | | |
794 | | // Is it a comment? |
795 | 0 | if ((buf[0] == '#') || (buf[0] == ';')) { |
796 | 0 | continue; |
797 | 0 | } |
798 | | |
799 | | // Is there a '=' ? |
800 | 0 | if (strchr(buf, '=') == NULL) { |
801 | 0 | continue; |
802 | 0 | } |
803 | | |
804 | | // Copy to strings while removing whitespace and converting to lower case |
805 | 0 | std::string paramName(""); |
806 | 0 | std::string paramVal(""); |
807 | |
|
808 | 0 | const char * paramNamePtr = strtok(buf, "="); |
809 | 0 | while(*paramNamePtr != '\0') { |
810 | 0 | if (! isspace((char)(*paramNamePtr))) { |
811 | 0 | paramName += tolower((char)(*paramNamePtr)); |
812 | 0 | } |
813 | 0 | paramNamePtr++; |
814 | 0 | } |
815 | |
|
816 | 0 | const char * paramValPtr = strtok(NULL, "="); |
817 | 0 | while(*paramValPtr != '\0') { |
818 | 0 | if (! isspace(*paramValPtr)) { |
819 | 0 | paramVal += tolower((char)(*paramValPtr)); |
820 | 0 | } |
821 | 0 | paramValPtr++; |
822 | 0 | } |
823 | | |
824 | | // Make sure this parameter is not already in the map |
825 | 0 | if (results.find(paramName) != results.end()) { |
826 | | // Duplicate parameter - return an error |
827 | 0 | tsk_error_reset(); |
828 | 0 | tsk_error_set_errno(TSK_ERR_FS); |
829 | 0 | tsk_error_set_errstr( |
830 | 0 | "yaffs_load_config: Duplicate parameter name in config file (\"%s\"). %s", paramName.c_str(), YAFFS_HELP_MESSAGE); |
831 | 0 | fclose(config_file); |
832 | 0 | free(config_file_name); |
833 | 0 | return YAFFS_CONFIG_ERROR; |
834 | 0 | } |
835 | | |
836 | | // Add this entry to the map |
837 | 0 | results[paramName] = paramVal; |
838 | 0 | } |
839 | | |
840 | 0 | fclose(config_file); |
841 | 0 | free(config_file_name); |
842 | 0 | return YAFFS_CONFIG_OK; |
843 | 0 | } |
844 | | |
845 | | /* |
846 | | * Helper function for yaffs_validate_config |
847 | | * Tests that a string consists only of digits and has at least one digit |
848 | | * (Can modify later if we want negative fields to be valid) |
849 | | * |
850 | | * @param numStr String to test |
851 | | * @returns 1 on error, 0 on success |
852 | | */ |
853 | | static int |
854 | 0 | yaffs_validate_integer_field(std::string numStr) { |
855 | 0 | unsigned int i; |
856 | | |
857 | | // Test if empty |
858 | 0 | if (numStr.length() == 0) { |
859 | 0 | return 1; |
860 | 0 | } |
861 | | |
862 | | // Test each character |
863 | 0 | for(i = 0;i < numStr.length();i++) { |
864 | 0 | if (isdigit(numStr[i]) == 0) { |
865 | 0 | return 1; |
866 | 0 | } |
867 | 0 | } |
868 | | |
869 | 0 | return 0; |
870 | 0 | } |
871 | | |
872 | | /* |
873 | | * Function to validate the contents of the config file |
874 | | * Currently testing: |
875 | | * All YAFFS_CONFIG fields should be integers (if they exist) |
876 | | * Either need all three of YAFFS_CONFIG_SEQ_NUM_STR, YAFFS_CONFIG_OBJ_ID_STR, YAFFS_CONFIG_CHUNK_ID_STR |
877 | | * or none of them |
878 | | * |
879 | | * @param paramMap Holds mapping of parameter name to parameter value |
880 | | * @returns 1 on error (invalid parameters), 0 on success |
881 | | */ |
882 | | static int |
883 | 0 | yaffs_validate_config_file(std::map<std::string, std::string> & paramMap) { |
884 | 0 | int offset_field_count; |
885 | | |
886 | | // Make a list of all fields to test |
887 | 0 | std::set<std::string> integerParams; |
888 | 0 | integerParams.insert(YAFFS_CONFIG_SEQ_NUM_STR); |
889 | 0 | integerParams.insert(YAFFS_CONFIG_OBJ_ID_STR); |
890 | 0 | integerParams.insert(YAFFS_CONFIG_CHUNK_ID_STR); |
891 | 0 | integerParams.insert(YAFFS_CONFIG_PAGE_SIZE_STR); |
892 | 0 | integerParams.insert(YAFFS_CONFIG_SPARE_SIZE_STR); |
893 | 0 | integerParams.insert(YAFFS_CONFIG_CHUNKS_PER_BLOCK_STR); |
894 | | |
895 | | // If the parameter is set, verify that the value is an int |
896 | 0 | for(std::set<std::string>::iterator it = integerParams.begin();it != integerParams.end();it++) { |
897 | 0 | if ((paramMap.find(*it) != paramMap.end()) && |
898 | 0 | (0 != yaffs_validate_integer_field(paramMap[*it]))) { |
899 | 0 | tsk_error_reset(); |
900 | 0 | tsk_error_set_errno(TSK_ERR_FS); |
901 | 0 | tsk_error_set_errstr( |
902 | 0 | "yaffs_validate_config_file: Empty or non-integer value for Yaffs2 parameter \"%s\". %s", (*it).c_str(), YAFFS_HELP_MESSAGE); |
903 | 0 | return 1; |
904 | 0 | } |
905 | 0 | } |
906 | | |
907 | | // Check that we have all three spare offset fields, or none of the three |
908 | 0 | offset_field_count = 0; |
909 | 0 | if (paramMap.find(YAFFS_CONFIG_SEQ_NUM_STR) != paramMap.end()) { |
910 | 0 | offset_field_count++; |
911 | 0 | } |
912 | 0 | if (paramMap.find(YAFFS_CONFIG_OBJ_ID_STR) != paramMap.end()) { |
913 | 0 | offset_field_count++; |
914 | 0 | } |
915 | 0 | if (paramMap.find(YAFFS_CONFIG_CHUNK_ID_STR) != paramMap.end()) { |
916 | 0 | offset_field_count++; |
917 | 0 | } |
918 | |
|
919 | 0 | if (! ((offset_field_count == 0) || (offset_field_count == 3))) { |
920 | 0 | tsk_error_reset(); |
921 | 0 | tsk_error_set_errno(TSK_ERR_FS); |
922 | 0 | tsk_error_set_errstr( |
923 | 0 | "yaffs_validate_config_file: Require either all three spare offset fields or none. %s", YAFFS_HELP_MESSAGE); |
924 | 0 | return 1; |
925 | 0 | } |
926 | | |
927 | | // Make sure there aren't any unexpected fields present |
928 | 0 | for(std::map<std::string, std::string>::iterator it = paramMap.begin(); it != paramMap.end();it++) { |
929 | 0 | if (integerParams.find(it->first) == integerParams.end()) { |
930 | 0 | tsk_error_reset(); |
931 | 0 | tsk_error_set_errno(TSK_ERR_FS); |
932 | 0 | tsk_error_set_errstr( |
933 | 0 | "yaffs_validate_config_file: Found unexpected field in config file (\"%s\"). %s", it->first.c_str(), YAFFS_HELP_MESSAGE); |
934 | 0 | return 1; |
935 | 0 | } |
936 | 0 | } |
937 | | |
938 | 0 | return 0; |
939 | 0 | } |
940 | | |
941 | | /* |
942 | | * Function to attempt to determine the layout of the yaffs spare area. |
943 | | * Results of the analysis (if the format could be determined) will be stored |
944 | | * in yfs variables. |
945 | | * |
946 | | * @param yfs File system being analyzed |
947 | | * @param maxBlocksToTest Number of block groups to scan to detect spare area or 0 if there is no limit. |
948 | | * @returns TSK_ERR if format could not be detected and TSK_OK if it could be. |
949 | | */ |
950 | | static TSK_RETVAL_ENUM |
951 | 0 | yaffs_initialize_spare_format(YAFFSFS_INFO * yfs, TSK_OFF_T maxBlocksToTest) { |
952 | | |
953 | | // Testing parameters - can all be changed |
954 | 0 | unsigned int blocksToTest = 10; // Number of blocks (64 chunks) to test |
955 | 0 | unsigned int chunksToTest = 10; // Number of chunks to test in each block |
956 | 0 | unsigned int minChunksRead = 10; // Minimum number of chunks we require to run the test (we might not get the full number we want to test for a very small file) |
957 | |
|
958 | 0 | unsigned int chunkSize = yfs->page_size + yfs->spare_size; |
959 | 0 | unsigned int blockSize = yfs->chunks_per_block * chunkSize; |
960 | |
|
961 | 0 | TSK_FS_INFO *fs = &(yfs->fs_info); |
962 | 0 | unsigned char *spareBuffer; |
963 | |
|
964 | 0 | unsigned int blockIndex; |
965 | 0 | unsigned int chunkIndex; |
966 | |
|
967 | 0 | unsigned int currentOffset; |
968 | |
|
969 | 0 | unsigned char * allSpares; |
970 | 0 | unsigned int allSparesLength; |
971 | |
|
972 | 0 | TSK_OFF_T maxBlocks; |
973 | |
|
974 | 0 | bool skipBlock; |
975 | 0 | int goodOffset; |
976 | 0 | unsigned int nGoodSpares; |
977 | 0 | unsigned int nBlocksTested; |
978 | |
|
979 | 0 | int okOffsetFound = 0; // Used as a flag for if we've found an offset that sort of works but doesn't seem great |
980 | 0 | int goodOffsetFound = 0; // Flag to mark that we've found an offset that also passed secondary testing |
981 | 0 | int bestOffset = 0; |
982 | |
|
983 | 0 | bool allSameByte; // Used in test that the spare area fields not be one repeated byte |
984 | |
|
985 | 0 | unsigned int i; |
986 | |
|
987 | 0 | int thisChunkBase; |
988 | 0 | int lastChunkBase; |
989 | | |
990 | | // The spare area needs to be at least 16 bytes to run the test |
991 | 0 | if (yfs->spare_size < 16) { |
992 | 0 | if (tsk_verbose && (! yfs->autoDetect)) { |
993 | 0 | tsk_fprintf(stderr, |
994 | 0 | "yaffs_initialize_spare_format failed - given spare size (%d) is not large enough to contain needed fields\n", yfs->spare_size); |
995 | 0 | } |
996 | 0 | return TSK_ERR; |
997 | 0 | } |
998 | | |
999 | 0 | if ((spareBuffer = (unsigned char*) tsk_malloc(yfs->spare_size)) == NULL) { |
1000 | 0 | return TSK_ERR; |
1001 | 0 | } |
1002 | | |
1003 | 0 | allSparesLength = yfs->spare_size * blocksToTest * chunksToTest; |
1004 | 0 | if ((allSpares = (unsigned char*) tsk_malloc(allSparesLength)) == NULL) { |
1005 | 0 | free(spareBuffer); |
1006 | 0 | return TSK_ERR; |
1007 | 0 | } |
1008 | | |
1009 | | // Initialize the pointers to one of the configurations we've seen (thought these defaults should not get used) |
1010 | 0 | yfs->spare_seq_offset = 0; |
1011 | 0 | yfs->spare_obj_id_offset = 4; |
1012 | 0 | yfs->spare_chunk_id_offset = 8; |
1013 | 0 | yfs->spare_nbytes_offset = 12; |
1014 | | |
1015 | | // Assume the data we want is 16 consecutive bytes in the order: |
1016 | | // seq num, obj id, chunk id, byte count |
1017 | | // (not sure we're guaranteed this but we wouldn't be able to deal with the alternative anyway) |
1018 | | // Seq num is the important one. This number is constant in each block (block = 64 chunks), meaning |
1019 | | // all chunks in a block will share the same sequence number. The YAFFS2 descriptions would seem to |
1020 | | // indicate it should be different for each block, but this doesn't seem to always be the case. |
1021 | | // In particular we frequently see the 0x1000 seq number used over multiple blocks, but this isn't the only |
1022 | | // observed exception. |
1023 | | |
1024 | | // Calculate the number of blocks in the image |
1025 | 0 | maxBlocks = yfs->fs_info.img_info->size / (yfs->chunks_per_block * chunkSize); |
1026 | | |
1027 | | // If maxBlocksToTest = 0 (unlimited), set it to the total number of blocks |
1028 | | // Also reduce the number of blocks to test if it is larger than the total number of blocks |
1029 | 0 | if ((maxBlocksToTest == 0) || (maxBlocksToTest > maxBlocks)) { |
1030 | 0 | maxBlocksToTest = maxBlocks; |
1031 | 0 | } |
1032 | |
|
1033 | 0 | nGoodSpares = 0; |
1034 | 0 | nBlocksTested = 0; |
1035 | 0 | for (TSK_OFF_T blockIndex = 0;blockIndex < maxBlocksToTest;blockIndex++) { |
1036 | | |
1037 | | // Read the last spare area that we want to test first |
1038 | 0 | TSK_OFF_T offset = (TSK_OFF_T)blockIndex * blockSize + (chunksToTest - 1) * chunkSize + yfs->page_size; |
1039 | 0 | ssize_t cnt = tsk_img_read(fs->img_info, offset, (char *) spareBuffer, |
1040 | 0 | yfs->spare_size); |
1041 | 0 | if ((cnt < 0) || ((unsigned int)cnt < yfs->spare_size)) { |
1042 | 0 | break; |
1043 | 0 | } |
1044 | | |
1045 | | // Is the spare all 0xff / 0x00? |
1046 | | // If not, we know we should have all allocated chunks since YAFFS2 writes sequentially in a block |
1047 | | // - can't have an unallocated chunk followed by an allocated one |
1048 | | // We occasionally see almost all null spare area with a few 0xff, which is not a valid spare. |
1049 | 0 | skipBlock = true; |
1050 | 0 | for (i = 0;i < yfs->spare_size;i++) { |
1051 | 0 | if ((spareBuffer[i] != 0xff) && (spareBuffer[i] != 0x00)) { |
1052 | 0 | skipBlock = false; |
1053 | 0 | break; |
1054 | 0 | } |
1055 | 0 | } |
1056 | |
|
1057 | 0 | if (skipBlock) { |
1058 | 0 | continue; |
1059 | 0 | } |
1060 | | |
1061 | | // If this block is potentialy valid (i.e., the spare contains something besides 0x00 and 0xff), copy all the spares into |
1062 | | // the big array of extracted spare areas |
1063 | | |
1064 | | // Copy this spare area |
1065 | 0 | nGoodSpares++; |
1066 | 0 | for (i = 0;i < yfs->spare_size;i++) { |
1067 | 0 | allSpares[nBlocksTested * yfs->spare_size * chunksToTest + (chunksToTest - 1) * yfs->spare_size + i] = spareBuffer[i]; |
1068 | 0 | } |
1069 | | |
1070 | | // Copy all earlier spare areas in the block |
1071 | 0 | for (chunkIndex = 0;chunkIndex < chunksToTest - 1;chunkIndex++) { |
1072 | 0 | offset = blockIndex * blockSize + chunkIndex * chunkSize + yfs->page_size; |
1073 | 0 | cnt = tsk_img_read(fs->img_info, offset, (char *) spareBuffer, |
1074 | 0 | yfs->spare_size); |
1075 | 0 | if ((cnt < 0) || ((unsigned int)cnt < yfs->spare_size)) { |
1076 | | // We really shouldn't run out of data here since we already read in the furthest entry |
1077 | 0 | break; // Break out of chunksToTest loop |
1078 | 0 | } |
1079 | | |
1080 | 0 | nGoodSpares++; |
1081 | 0 | for(i = 0;i < yfs->spare_size;i++) { |
1082 | 0 | allSpares[nBlocksTested * yfs->spare_size * chunksToTest + chunkIndex * yfs->spare_size + i] = spareBuffer[i]; |
1083 | 0 | } |
1084 | 0 | } |
1085 | | |
1086 | | // Record that we've found a potentially valid block |
1087 | 0 | nBlocksTested++; |
1088 | | |
1089 | | // If we've found enough potentailly valid blocks, break |
1090 | 0 | if (nBlocksTested >= blocksToTest) { |
1091 | 0 | break; |
1092 | 0 | } |
1093 | 0 | } |
1094 | | |
1095 | | // Make sure we read enough data to reasonably perform the testing |
1096 | 0 | if (nGoodSpares < minChunksRead) { |
1097 | |
|
1098 | 0 | if (tsk_verbose && (! yfs->autoDetect)) { |
1099 | 0 | tsk_fprintf(stderr, |
1100 | 0 | "yaffs_initialize_spare_format failed - not enough potentially valid data could be read\n"); |
1101 | 0 | } |
1102 | |
|
1103 | 0 | free(spareBuffer); |
1104 | 0 | free(allSpares); |
1105 | 0 | return TSK_ERR; |
1106 | 0 | } |
1107 | | |
1108 | 0 | if (tsk_verbose && (! yfs->autoDetect)) { |
1109 | 0 | tsk_fprintf(stderr, |
1110 | 0 | "yaffs_initialize_spare_format: Testing potential offsets for the sequence number in the spare area\n"); |
1111 | 0 | } |
1112 | | |
1113 | | // Print out the collected spare areas if we're in verbose mode |
1114 | 0 | if (tsk_verbose && (! yfs->autoDetect)) { |
1115 | 0 | for(blockIndex = 0;blockIndex < nBlocksTested;blockIndex++) { |
1116 | 0 | for(chunkIndex = 0;chunkIndex < chunksToTest;chunkIndex++) { |
1117 | 0 | for(i = 0;i < yfs->spare_size;i++) { |
1118 | 0 | fprintf(stderr, "%02x", allSpares[blockIndex * yfs->spare_size * chunksToTest + chunkIndex * yfs->spare_size + i]); |
1119 | 0 | } |
1120 | 0 | fprintf(stderr, "\n"); |
1121 | 0 | } |
1122 | 0 | } |
1123 | 0 | } |
1124 | | |
1125 | | // Test all indices into the spare area (that leave enough space for all 16 bytes) |
1126 | 0 | for(currentOffset = 0;currentOffset <= yfs->spare_size - 16;currentOffset++) { |
1127 | 0 | goodOffset = 1; |
1128 | 0 | for(blockIndex = 0;blockIndex < nBlocksTested;blockIndex++) { |
1129 | 0 | for(chunkIndex = 1;chunkIndex < chunksToTest;chunkIndex++) { |
1130 | |
|
1131 | 0 | lastChunkBase = blockIndex * yfs->spare_size * chunksToTest + (chunkIndex - 1) * yfs->spare_size; |
1132 | 0 | thisChunkBase = lastChunkBase + yfs->spare_size; |
1133 | | |
1134 | | // Seq num should not be all 0xff (we tested earlier that the chunk has been initialized) |
1135 | 0 | if ((0xff == allSpares[thisChunkBase + currentOffset]) && |
1136 | 0 | (0xff == allSpares[thisChunkBase + currentOffset + 1]) && |
1137 | 0 | (0xff == allSpares[thisChunkBase + currentOffset + 2]) && |
1138 | 0 | (0xff == allSpares[thisChunkBase + currentOffset + 3])) { |
1139 | 0 | if (tsk_verbose && (! yfs->autoDetect)) { |
1140 | 0 | tsk_fprintf(stderr, |
1141 | 0 | "yaffs_initialize_spare_format: Eliminating offset %d - invalid sequence number 0xffffffff\n", |
1142 | 0 | currentOffset); |
1143 | 0 | } |
1144 | 0 | goodOffset = 0; |
1145 | 0 | break; |
1146 | 0 | } |
1147 | | |
1148 | | // Seq num should not be zero |
1149 | 0 | if ((0 == allSpares[thisChunkBase + currentOffset]) && |
1150 | 0 | (0 == allSpares[thisChunkBase + currentOffset + 1]) && |
1151 | 0 | (0 == allSpares[thisChunkBase + currentOffset + 2]) && |
1152 | 0 | (0 == allSpares[thisChunkBase + currentOffset + 3])) { |
1153 | 0 | if (tsk_verbose && (! yfs->autoDetect)) { |
1154 | 0 | tsk_fprintf(stderr, |
1155 | 0 | "yaffs_initialize_spare_format: Eliminating offset %d - invalid sequence number 0\n", |
1156 | 0 | currentOffset); |
1157 | 0 | } |
1158 | 0 | goodOffset = 0; |
1159 | 0 | break; |
1160 | 0 | } |
1161 | | |
1162 | | // Seq num should match the previous one in the block |
1163 | 0 | if ((allSpares[lastChunkBase + currentOffset] != allSpares[thisChunkBase + currentOffset]) || |
1164 | 0 | (allSpares[lastChunkBase + currentOffset + 1] != allSpares[thisChunkBase + currentOffset + 1]) || |
1165 | 0 | (allSpares[lastChunkBase + currentOffset + 2] != allSpares[thisChunkBase + currentOffset + 2]) || |
1166 | 0 | (allSpares[lastChunkBase + currentOffset + 3] != allSpares[thisChunkBase + currentOffset + 3])) { |
1167 | 0 | if (tsk_verbose && (! yfs->autoDetect)) { |
1168 | 0 | tsk_fprintf(stderr, |
1169 | 0 | "yaffs_initialize_spare_format: Eliminating offset %d - did not match previous chunk sequence number\n", |
1170 | 0 | currentOffset); |
1171 | 0 | } |
1172 | 0 | goodOffset = 0; |
1173 | 0 | break; |
1174 | 0 | } |
1175 | | |
1176 | | // Obj id should not be zero |
1177 | 0 | if ((0 == allSpares[thisChunkBase + currentOffset + 4]) && |
1178 | 0 | (0 == allSpares[thisChunkBase + currentOffset + 5]) && |
1179 | 0 | (0 == allSpares[thisChunkBase + currentOffset + 6]) && |
1180 | 0 | (0 == allSpares[thisChunkBase + currentOffset + 7])) { |
1181 | 0 | if (tsk_verbose && (! yfs->autoDetect)) { |
1182 | 0 | tsk_fprintf(stderr, |
1183 | 0 | "yaffs_initialize_spare_format: Eliminating offset %d - invalid object id 0\n", |
1184 | 0 | currentOffset); |
1185 | 0 | } |
1186 | 0 | goodOffset = 0; |
1187 | 0 | break; |
1188 | 0 | } |
1189 | | |
1190 | | // All 16 bytes should not be the same |
1191 | | // (It is theoretically possible that this could be valid, but incredibly unlikely) |
1192 | 0 | allSameByte = true; |
1193 | 0 | for(i = 1;i < 16;i++) { |
1194 | 0 | if (allSpares[thisChunkBase + currentOffset] != allSpares[thisChunkBase + currentOffset + i]) { |
1195 | 0 | allSameByte = false; |
1196 | 0 | break; |
1197 | 0 | } |
1198 | 0 | } |
1199 | 0 | if (allSameByte) { |
1200 | 0 | if (tsk_verbose && (! yfs->autoDetect)) { |
1201 | 0 | tsk_fprintf(stderr, |
1202 | 0 | "yaffs_initialize_spare_format: Eliminating offset %d - all repeated bytes\n", |
1203 | 0 | currentOffset); |
1204 | 0 | } |
1205 | 0 | goodOffset = 0; |
1206 | 0 | break; |
1207 | 0 | } |
1208 | |
|
1209 | 0 | } // End of loop over chunks |
1210 | |
|
1211 | 0 | if (!goodOffset) { // Break out of loop over blocks |
1212 | 0 | break; |
1213 | 0 | } |
1214 | 0 | } |
1215 | 0 | if (goodOffset) { |
1216 | | |
1217 | | // Note that we've found an offset that is at least promising |
1218 | 0 | if ((! goodOffsetFound) && (! okOffsetFound)) { |
1219 | 0 | bestOffset = currentOffset; |
1220 | 0 | } |
1221 | 0 | okOffsetFound = 1; |
1222 | |
|
1223 | 0 | if (tsk_verbose && (! yfs->autoDetect)) { |
1224 | 0 | tsk_fprintf(stderr, |
1225 | 0 | "yaffs_initialize_spare_format: Found potential spare offsets: %d (sequence number), %d (object id), %d (chunk id), %d (n bytes)\n", |
1226 | 0 | currentOffset, currentOffset+4, currentOffset+8, currentOffset+12); |
1227 | 0 | } |
1228 | | |
1229 | | // Now do some more tests |
1230 | | // Really need some more real-world test data to do this right. |
1231 | 0 | int possibleError = 0; |
1232 | | |
1233 | | // We probably don't want the first byte to always be 0xff |
1234 | 0 | int firstByteFF = 1; |
1235 | 0 | for(blockIndex = 0;blockIndex < nBlocksTested;blockIndex++) { |
1236 | 0 | for(chunkIndex = 1;chunkIndex < chunksToTest;chunkIndex++) { |
1237 | 0 | if (allSpares[blockIndex * yfs->spare_size * chunksToTest + chunkIndex * yfs->spare_size + currentOffset] != 0xff) { |
1238 | 0 | firstByteFF = 0; |
1239 | 0 | } |
1240 | 0 | } |
1241 | 0 | } |
1242 | |
|
1243 | 0 | if (firstByteFF) { |
1244 | 0 | if (tsk_verbose && (! yfs->autoDetect)) { |
1245 | 0 | tsk_fprintf(stderr, |
1246 | 0 | "yaffs_initialize_spare_format: Previous data starts with all 0xff bytes. Looking for better offsets.\n"); |
1247 | 0 | } |
1248 | 0 | possibleError = 1; |
1249 | 0 | } |
1250 | |
|
1251 | 0 | if (! possibleError) { |
1252 | | |
1253 | | // If we already have a good offset, print this one out but don't record it |
1254 | 0 | if (! goodOffsetFound) { |
1255 | |
|
1256 | 0 | goodOffsetFound = 1; |
1257 | 0 | bestOffset = currentOffset; |
1258 | | |
1259 | | // Offset passed additional testing and we haven't seen an earlier good one, so go ahead and use it |
1260 | 0 | if (tsk_verbose && (! yfs->autoDetect)) { |
1261 | 0 | tsk_fprintf(stderr, |
1262 | 0 | "yaffs_initialize_spare_format: Previous offsets appear good - will use as final offsets\n"); |
1263 | 0 | } |
1264 | |
|
1265 | 0 | } |
1266 | 0 | else { |
1267 | | // Keep using the old one |
1268 | 0 | if (tsk_verbose && (! yfs->autoDetect)) { |
1269 | 0 | tsk_fprintf(stderr, |
1270 | 0 | "yaffs_initialize_spare_format: Previous offsets appear good but staying with earlier valid ones\n"); |
1271 | 0 | } |
1272 | 0 | } |
1273 | 0 | } |
1274 | 0 | } |
1275 | 0 | } |
1276 | |
|
1277 | 0 | free(spareBuffer); |
1278 | 0 | free(allSpares); |
1279 | |
|
1280 | 0 | if (okOffsetFound || goodOffsetFound) { |
1281 | | // Record everything |
1282 | 0 | yfs->spare_seq_offset = bestOffset; |
1283 | 0 | yfs->spare_obj_id_offset = bestOffset + 4; |
1284 | 0 | yfs->spare_chunk_id_offset = bestOffset + 8; |
1285 | 0 | yfs->spare_nbytes_offset = bestOffset + 12; |
1286 | |
|
1287 | 0 | if (tsk_verbose && (! yfs->autoDetect)) { |
1288 | 0 | tsk_fprintf(stderr, |
1289 | 0 | "yaffs_initialize_spare_format: Final offsets: %d (sequence number), %d (object id), %d (chunk id), %d (n bytes)\n", |
1290 | 0 | bestOffset, bestOffset+4, bestOffset+8, bestOffset+12); |
1291 | 0 | tsk_fprintf(stderr, |
1292 | 0 | "If these do not seem valid: %s\n", YAFFS_HELP_MESSAGE); |
1293 | 0 | } |
1294 | 0 | return TSK_OK; |
1295 | 0 | } |
1296 | 0 | else { |
1297 | 0 | return TSK_ERR; |
1298 | 0 | } |
1299 | 0 | } |
1300 | | |
1301 | | /** |
1302 | | * yaffsfs_read_header( ... ) |
1303 | | * |
1304 | | */ |
1305 | | static uint8_t |
1306 | | yaffsfs_read_header(YAFFSFS_INFO *yfs, YaffsHeader ** header, TSK_OFF_T offset) |
1307 | 0 | { |
1308 | 0 | unsigned char *hdr; |
1309 | 0 | ssize_t cnt; |
1310 | 0 | YaffsHeader *head; |
1311 | 0 | TSK_FS_INFO *fs = &(yfs->fs_info); |
1312 | |
|
1313 | 0 | if ((hdr = (unsigned char*) tsk_malloc(yfs->page_size)) == NULL) { |
1314 | 0 | return 1; |
1315 | 0 | } |
1316 | | |
1317 | 0 | cnt = tsk_img_read(fs->img_info, offset, (char *) hdr, |
1318 | 0 | yfs->page_size); |
1319 | 0 | if ((cnt < 0) || ((unsigned int)cnt < yfs->page_size)) { |
1320 | 0 | free(hdr); |
1321 | 0 | return 1; |
1322 | 0 | } |
1323 | | |
1324 | 0 | if ((head = (YaffsHeader*) tsk_malloc( sizeof(YaffsHeader))) == NULL) { |
1325 | 0 | free(hdr); |
1326 | 0 | return 1; |
1327 | 0 | } |
1328 | | |
1329 | 0 | memcpy(&head->obj_type, hdr, 4); |
1330 | 0 | memcpy(&head->parent_id, &hdr[4], 4); |
1331 | 0 | memcpy(head->name, (char*) &hdr[0xA], YAFFS_HEADER_NAME_LENGTH); |
1332 | 0 | memcpy(&head->file_mode, &hdr[0x10C], 4); |
1333 | 0 | memcpy(&head->user_id, &hdr[0x110], 4); |
1334 | 0 | memcpy(&head->group_id, &hdr[0x114], 4); |
1335 | 0 | memcpy(&head->atime, &hdr[0x118], 4); |
1336 | 0 | memcpy(&head->mtime, &hdr[0x11C], 4); |
1337 | 0 | memcpy(&head->ctime, &hdr[0x120], 4); |
1338 | 0 | memcpy(&head->file_size, &hdr[0x124], 4); |
1339 | 0 | memcpy(&head->equivalent_id, &hdr[0x128], 4); |
1340 | 0 | memcpy(head->alias, (char*) &hdr[0x12C], YAFFS_HEADER_ALIAS_LENGTH); |
1341 | | |
1342 | | //memcpy(&head->rdev_mode, &hdr[0x1CC], 4); |
1343 | | //memcpy(&head->win_ctime, &hdr[0x1D0], 8); |
1344 | | //memcpy(&head->win_atime, &hdr[0x1D8], 8); |
1345 | | //memcpy(&head->win_mtime, &hdr[0x1E0], 8); |
1346 | | //memcpy(&head->inband_obj_id, &hdr[0x1E8], 4); |
1347 | | //memcpy(&head->inband_is_shrink, &hdr[0x1EC], 4); |
1348 | | |
1349 | | // NOTE: This isn't in Android 3.3 kernel but is in YAFFS2 git |
1350 | | //memcpy(&head->file_size_high, &hdr[0x1F0], 4); |
1351 | |
|
1352 | 0 | free(hdr); |
1353 | |
|
1354 | 0 | *header = head; |
1355 | 0 | return 0; |
1356 | 0 | } |
1357 | | |
1358 | | /** |
1359 | | * Read and parse the YAFFS2 tags in the NAND spare bytes. |
1360 | | * |
1361 | | * @param info is a YAFFS fs handle |
1362 | | * @param spare YaffsSpare object to be populated |
1363 | | * @param offset, offset to read from |
1364 | | * |
1365 | | * @returns 0 on success and 1 on error |
1366 | | */ |
1367 | | static uint8_t |
1368 | | yaffsfs_read_spare(YAFFSFS_INFO *yfs, YaffsSpare ** spare, TSK_OFF_T offset) |
1369 | 0 | { |
1370 | 0 | unsigned char *spr; |
1371 | 0 | ssize_t cnt; |
1372 | 0 | YaffsSpare *sp; |
1373 | 0 | TSK_FS_INFO *fs = &(yfs->fs_info); |
1374 | |
|
1375 | 0 | uint32_t seq_number; |
1376 | 0 | uint32_t object_id; |
1377 | 0 | uint32_t chunk_id; |
1378 | | |
1379 | | // Should have checked this by now, but just in case |
1380 | 0 | if ((yfs->spare_seq_offset + 4 > yfs->spare_size) || |
1381 | 0 | (yfs->spare_obj_id_offset + 4 > yfs->spare_size) || |
1382 | 0 | (yfs->spare_chunk_id_offset + 4 > yfs->spare_size)) { |
1383 | 0 | return 1; |
1384 | 0 | } |
1385 | | |
1386 | 0 | if ((spr = (unsigned char*) tsk_malloc(yfs->spare_size)) == NULL) { |
1387 | 0 | return 1; |
1388 | 0 | } |
1389 | | |
1390 | 0 | if (yfs->spare_size < 46) { // Why is this 46? |
1391 | 0 | tsk_error_reset(); |
1392 | 0 | tsk_error_set_errno(TSK_ERR_FS_ARG); |
1393 | 0 | tsk_error_set_errstr("yaffsfs_read_spare: spare size is too small"); |
1394 | 0 | free(spr); |
1395 | 0 | return 1; |
1396 | 0 | } |
1397 | | |
1398 | 0 | cnt = tsk_img_read(fs->img_info, offset, (char*) spr, yfs->spare_size); |
1399 | 0 | if ((cnt < 0) || ((unsigned int)cnt < yfs->spare_size)) { |
1400 | | // couldn't read sufficient bytes... |
1401 | 0 | if (spare) { |
1402 | 0 | free(spr); |
1403 | 0 | *spare = NULL; |
1404 | 0 | } |
1405 | 0 | return 1; |
1406 | 0 | } |
1407 | | |
1408 | 0 | if ((sp = (YaffsSpare*) tsk_malloc(sizeof(YaffsSpare))) == NULL) { |
1409 | 0 | return 1; |
1410 | 0 | } |
1411 | | |
1412 | 0 | memset(sp, 0, sizeof(YaffsSpare)); |
1413 | | |
1414 | | /* |
1415 | | * Complete read of the YAFFS2 spare |
1416 | | */ |
1417 | | |
1418 | | |
1419 | | // The format of the spare area should have been determined earlier |
1420 | 0 | memcpy(&seq_number, &spr[yfs->spare_seq_offset], 4); |
1421 | 0 | memcpy(&object_id, &spr[yfs->spare_obj_id_offset], 4); |
1422 | 0 | memcpy(&chunk_id, &spr[yfs->spare_chunk_id_offset], 4); |
1423 | |
|
1424 | 0 | if ((YAFFS_SPARE_FLAGS_IS_HEADER & chunk_id) != 0) { |
1425 | |
|
1426 | 0 | sp->seq_number = seq_number; |
1427 | 0 | sp->object_id = object_id & ~YAFFS_SPARE_OBJECT_TYPE_MASK; |
1428 | 0 | sp->chunk_id = 0; |
1429 | |
|
1430 | 0 | sp->has_extra_fields = 1; |
1431 | 0 | sp->extra_parent_id = chunk_id & YAFFS_SPARE_PARENT_ID_MASK; |
1432 | 0 | sp->extra_object_type = |
1433 | 0 | (object_id & YAFFS_SPARE_OBJECT_TYPE_MASK) |
1434 | 0 | >> YAFFS_SPARE_OBJECT_TYPE_SHIFT; |
1435 | 0 | } |
1436 | 0 | else { |
1437 | 0 | sp->seq_number = seq_number; |
1438 | 0 | sp->object_id = object_id; |
1439 | 0 | sp->chunk_id = chunk_id; |
1440 | |
|
1441 | 0 | sp->has_extra_fields = 0; |
1442 | 0 | } |
1443 | |
|
1444 | 0 | free(spr); |
1445 | 0 | *spare = sp; |
1446 | |
|
1447 | 0 | return 0; |
1448 | 0 | } |
1449 | | |
1450 | | static uint8_t |
1451 | | yaffsfs_is_spare_valid(YAFFSFS_INFO * /*yfs*/, YaffsSpare *spare) |
1452 | 0 | { |
1453 | 0 | if (spare == NULL) { |
1454 | 0 | return 1; |
1455 | 0 | } |
1456 | | |
1457 | 0 | if ((spare->object_id > YAFFS_MAX_OBJECT_ID) || |
1458 | 0 | (spare->seq_number < YAFFS_LOWEST_SEQUENCE_NUMBER) || |
1459 | 0 | (spare->seq_number > YAFFS_HIGHEST_SEQUENCE_NUMBER)) { |
1460 | 0 | return 1; |
1461 | 0 | } |
1462 | | |
1463 | 0 | return 0; |
1464 | 0 | } |
1465 | | |
1466 | | static uint8_t |
1467 | | yaffsfs_read_chunk(YAFFSFS_INFO *yfs, |
1468 | | YaffsHeader **header, YaffsSpare **spare, TSK_OFF_T offset) |
1469 | 0 | { |
1470 | 0 | TSK_OFF_T header_offset = offset; |
1471 | 0 | TSK_OFF_T spare_offset = offset + yfs->page_size; |
1472 | |
|
1473 | 0 | if (header == NULL || spare == NULL) { |
1474 | 0 | return 1; |
1475 | 0 | } |
1476 | | |
1477 | 0 | if (yaffsfs_read_header(yfs, header, header_offset) != 0) { |
1478 | 0 | return 1; |
1479 | 0 | } |
1480 | | |
1481 | 0 | if (yaffsfs_read_spare(yfs, spare, spare_offset) != 0) { |
1482 | 0 | free(*header); |
1483 | 0 | *header = NULL; |
1484 | 0 | return 1; |
1485 | 0 | } |
1486 | | |
1487 | 0 | return 0; |
1488 | 0 | } |
1489 | | |
1490 | | /** |
1491 | | * Cycle through the entire image and populate the cache with objects as they are found. |
1492 | | */ |
1493 | | static uint8_t |
1494 | | yaffsfs_parse_image_load_cache(YAFFSFS_INFO * yfs) |
1495 | 0 | { |
1496 | 0 | uint8_t status = TSK_OK; |
1497 | 0 | uint32_t nentries = 0; |
1498 | 0 | YaffsSpare *spare = NULL; |
1499 | |
|
1500 | 0 | uint8_t tempBuf[8]; |
1501 | 0 | uint32_t parentID; |
1502 | |
|
1503 | 0 | if (yfs->cache_objects) |
1504 | 0 | return 0; |
1505 | | |
1506 | 0 | for(TSK_OFF_T offset = 0;offset < yfs->fs_info.img_info->size;offset += yfs->page_size + yfs->spare_size) { |
1507 | 0 | status = yaffsfs_read_spare( yfs, &spare, offset + yfs->page_size); |
1508 | 0 | if (status != TSK_OK) { |
1509 | 0 | break; |
1510 | 0 | } |
1511 | | |
1512 | 0 | if (yaffsfs_is_spare_valid(yfs, spare) == TSK_OK) { |
1513 | | |
1514 | |
|
1515 | 0 | if ((spare->has_extra_fields) || (spare->chunk_id != 0)) { |
1516 | 0 | yaffscache_chunk_add(yfs, |
1517 | 0 | offset, |
1518 | 0 | spare->seq_number, |
1519 | 0 | spare->object_id, |
1520 | 0 | spare->chunk_id, |
1521 | 0 | spare->extra_parent_id); |
1522 | 0 | } |
1523 | 0 | else { |
1524 | | // If we have a header block and didn't extract it already from the spare, get the parent ID from |
1525 | | // the non-spare data |
1526 | 0 | if (8 == tsk_img_read(yfs->fs_info.img_info, offset, (char*) tempBuf, 8)) { |
1527 | 0 | memcpy(&parentID, &tempBuf[4], 4); |
1528 | |
|
1529 | 0 | yaffscache_chunk_add(yfs, |
1530 | 0 | offset, |
1531 | 0 | spare->seq_number, |
1532 | 0 | spare->object_id, |
1533 | 0 | spare->chunk_id, |
1534 | 0 | parentID); |
1535 | 0 | } |
1536 | 0 | else { |
1537 | | // Really shouldn't happen |
1538 | 0 | fprintf(stderr, "Error reading header to get parent id at offset %" PRIxOFF "\n", offset); |
1539 | 0 | yaffscache_chunk_add(yfs, |
1540 | 0 | offset, |
1541 | 0 | spare->seq_number, |
1542 | 0 | spare->object_id, |
1543 | 0 | spare->chunk_id, |
1544 | 0 | 0); |
1545 | 0 | } |
1546 | 0 | } |
1547 | 0 | } |
1548 | |
|
1549 | 0 | free(spare); |
1550 | 0 | spare = NULL; |
1551 | |
|
1552 | 0 | ++nentries; |
1553 | 0 | } |
1554 | |
|
1555 | 0 | if (tsk_verbose) |
1556 | 0 | fprintf(stderr, "yaffsfs_parse_image_load_cache: read %d entries\n", nentries); |
1557 | |
|
1558 | 0 | if (tsk_verbose) |
1559 | 0 | fprintf(stderr, "yaffsfs_parse_image_load_cache: started processing chunks for version cache...\n"); |
1560 | 0 | fflush(stderr); |
1561 | | |
1562 | | // At this point, we have a list of chunks sorted by obj id, seq number, and offset |
1563 | | // This makes the list of objects in cache_objects, which link to different versions |
1564 | 0 | yaffscache_versions_compute(yfs); |
1565 | |
|
1566 | 0 | if (tsk_verbose) |
1567 | 0 | fprintf(stderr, "yaffsfs_parse_image_load_cache: done version cache!\n"); |
1568 | 0 | fflush(stderr); |
1569 | | |
1570 | | |
1571 | | // Having multiple inodes point to the same object seems to cause trouble in TSK, especially in orphan file detection, |
1572 | | // so set the version number of the final one to zero. |
1573 | | // While we're at it, find the highest obj_id and the highest version (before resetting to zero) |
1574 | 0 | YaffsCacheObject * currObj = yfs->cache_objects; |
1575 | 0 | YaffsCacheVersion * currVer; |
1576 | 0 | while(currObj != NULL) { |
1577 | 0 | if (currObj->yco_obj_id > yfs->max_obj_id) { |
1578 | 0 | yfs->max_obj_id = currObj->yco_obj_id; |
1579 | 0 | } |
1580 | |
|
1581 | 0 | currVer = currObj->yco_latest; |
1582 | 0 | if (currVer->ycv_version > yfs->max_version) { |
1583 | 0 | yfs->max_version = currVer->ycv_version; |
1584 | 0 | } |
1585 | |
|
1586 | 0 | currVer->ycv_version = 0; |
1587 | 0 | currObj = currObj->yco_next; |
1588 | 0 | } |
1589 | | |
1590 | | // Use the max object id and version number to construct an upper bound on the inode |
1591 | 0 | TSK_INUM_T max_inum = 0; |
1592 | 0 | if (TSK_OK != yaffscache_obj_id_and_version_to_inode(yfs->max_obj_id, yfs->max_version, &max_inum)) { |
1593 | 0 | return TSK_ERR; |
1594 | 0 | } |
1595 | 0 | yfs->fs_info.last_inum = max_inum + 1; // One more for the orphan dir |
1596 | | |
1597 | | // Make sure the orphan dir is greater than the root dir |
1598 | 0 | if (yfs->fs_info.last_inum <= yfs->fs_info.root_inum) { |
1599 | 0 | tsk_error_reset(); |
1600 | 0 | tsk_error_set_errno(TSK_ERR_FS); |
1601 | 0 | tsk_error_set_errstr( |
1602 | 0 | "yaffsfs_parse_image_load_cache: Maximum inum %" PRIuINUM " is not greater than the root inum", yfs->fs_info.last_inum); |
1603 | 0 | return TSK_ERR; |
1604 | 0 | } |
1605 | | |
1606 | 0 | return TSK_OK; |
1607 | 0 | } |
1608 | | |
1609 | | // A version is allocated if: |
1610 | | // 1. This version is pointed to by yco_latest |
1611 | | // 2. This version didn't have a delete/unlinked header after the most recent copy of the normal header |
1612 | 0 | static uint8_t yaffs_is_version_allocated(YAFFSFS_INFO * yfs, TSK_INUM_T inode) { |
1613 | 0 | YaffsCacheObject * obj; |
1614 | 0 | YaffsCacheVersion * version; |
1615 | 0 | YaffsCacheChunk * curr; |
1616 | |
|
1617 | 0 | TSK_RETVAL_ENUM result = yaffscache_version_find_by_inode(yfs, inode, &version, &obj); |
1618 | 0 | if (result != TSK_OK) { |
1619 | 0 | if (tsk_verbose) |
1620 | 0 | tsk_fprintf(stderr, "yaffs_is_version_allocated: yaffscache_version_find_by_inode failed! (inode: %d)\n", inode); |
1621 | 0 | return 0; |
1622 | 0 | } |
1623 | | |
1624 | 0 | if (obj->yco_latest == version) { |
1625 | 0 | curr = obj->yco_latest->ycv_header_chunk; |
1626 | 0 | while(curr != NULL) { |
1627 | | // We're looking for a newer unlinked or deleted header. If one exists, then this object should be considered unallocated |
1628 | 0 | if ((curr->ycc_parent_id == YAFFS_OBJECT_UNLINKED) || (curr->ycc_parent_id == YAFFS_OBJECT_DELETED)) { |
1629 | 0 | return 0; |
1630 | 0 | } |
1631 | 0 | curr = curr ->ycc_next; |
1632 | 0 | } |
1633 | 0 | return 1; |
1634 | 0 | } |
1635 | 0 | else { |
1636 | 0 | return 0; |
1637 | 0 | } |
1638 | |
|
1639 | 0 | } |
1640 | | |
1641 | | /* |
1642 | | * TSK integration |
1643 | | * |
1644 | | * |
1645 | | */ |
1646 | | |
1647 | | static uint8_t |
1648 | | yaffs_make_directory(YAFFSFS_INFO *yaffsfs, TSK_FS_FILE *a_fs_file, |
1649 | | TSK_INUM_T inode, const char *name) |
1650 | 0 | { |
1651 | 0 | TSK_FS_FILE *fs_file = a_fs_file; |
1652 | | |
1653 | |
|
1654 | 0 | fs_file->meta->type = TSK_FS_META_TYPE_DIR; |
1655 | 0 | fs_file->meta->mode = (TSK_FS_META_MODE_ENUM)0; |
1656 | 0 | fs_file->meta->nlink = 1; |
1657 | |
|
1658 | 0 | if ((inode == YAFFS_OBJECT_UNLINKED) || (inode == YAFFS_OBJECT_DELETED) || |
1659 | 0 | (inode == yaffsfs->fs_info.last_inum)) { |
1660 | 0 | fs_file->meta->flags = |
1661 | 0 | (TSK_FS_META_FLAG_ENUM)(TSK_FS_META_FLAG_USED | TSK_FS_META_FLAG_ALLOC); |
1662 | 0 | } |
1663 | 0 | else { |
1664 | 0 | if (yaffs_is_version_allocated(yaffsfs, inode)) { |
1665 | 0 | fs_file->meta->flags = |
1666 | 0 | (TSK_FS_META_FLAG_ENUM)(TSK_FS_META_FLAG_USED | TSK_FS_META_FLAG_ALLOC); |
1667 | 0 | } |
1668 | 0 | else { |
1669 | 0 | fs_file->meta->flags = |
1670 | 0 | (TSK_FS_META_FLAG_ENUM)(TSK_FS_META_FLAG_USED | TSK_FS_META_FLAG_UNALLOC); |
1671 | 0 | } |
1672 | 0 | } |
1673 | 0 | fs_file->meta->uid = fs_file->meta->gid = 0; |
1674 | 0 | fs_file->meta->mtime = fs_file->meta->atime = fs_file->meta->ctime = |
1675 | 0 | fs_file->meta->crtime = 0; |
1676 | 0 | fs_file->meta->mtime_nano = fs_file->meta->atime_nano = |
1677 | 0 | fs_file->meta->ctime_nano = fs_file->meta->crtime_nano = 0; |
1678 | |
|
1679 | 0 | if (fs_file->meta->name2 == NULL) { |
1680 | 0 | if ((fs_file->meta->name2 = (TSK_FS_META_NAME_LIST *) |
1681 | 0 | tsk_malloc(sizeof(TSK_FS_META_NAME_LIST))) == NULL) { |
1682 | 0 | return 1; |
1683 | 0 | } |
1684 | 0 | fs_file->meta->name2->next = NULL; |
1685 | 0 | } |
1686 | | |
1687 | 0 | if (fs_file->meta->attr != NULL) { |
1688 | 0 | tsk_fs_attrlist_markunused(fs_file->meta->attr); |
1689 | 0 | } |
1690 | 0 | else { |
1691 | 0 | fs_file->meta->attr = tsk_fs_attrlist_alloc(); |
1692 | 0 | } |
1693 | |
|
1694 | 0 | strncpy(fs_file->meta->name2->name, name, TSK_FS_META_NAME_LIST_NSIZE - 1); |
1695 | |
|
1696 | 0 | fs_file->meta->size = 0; |
1697 | 0 | fs_file->meta->attr_state = TSK_FS_META_ATTR_EMPTY; |
1698 | 0 | fs_file->meta->addr = inode; |
1699 | 0 | return 0; |
1700 | 0 | } |
1701 | | |
1702 | | static uint8_t |
1703 | | yaffs_make_regularfile( YAFFSFS_INFO * yaffsfs, TSK_FS_FILE * a_fs_file, |
1704 | | TSK_INUM_T inode, const char * name ) |
1705 | 0 | { |
1706 | 0 | TSK_FS_FILE *fs_file = a_fs_file; |
1707 | |
|
1708 | 0 | fs_file->meta->type = TSK_FS_META_TYPE_REG; |
1709 | 0 | fs_file->meta->mode = (TSK_FS_META_MODE_ENUM)0; |
1710 | 0 | fs_file->meta->nlink =1; |
1711 | |
|
1712 | 0 | if (yaffs_is_version_allocated(yaffsfs, inode)) { |
1713 | 0 | fs_file->meta->flags = |
1714 | 0 | (TSK_FS_META_FLAG_ENUM)(TSK_FS_META_FLAG_USED | TSK_FS_META_FLAG_ALLOC); |
1715 | 0 | } |
1716 | 0 | else { |
1717 | 0 | fs_file->meta->flags = |
1718 | 0 | (TSK_FS_META_FLAG_ENUM)(TSK_FS_META_FLAG_USED | TSK_FS_META_FLAG_UNALLOC); |
1719 | 0 | } |
1720 | |
|
1721 | 0 | fs_file->meta->uid = fs_file->meta->gid = 0; |
1722 | 0 | fs_file->meta->mtime = fs_file->meta->atime = fs_file->meta->ctime = |
1723 | 0 | fs_file->meta->crtime = 0; |
1724 | 0 | fs_file->meta->mtime_nano = fs_file->meta->atime_nano = |
1725 | 0 | fs_file->meta->ctime_nano = fs_file->meta->crtime_nano = 0; |
1726 | |
|
1727 | 0 | if (fs_file->meta->name2 == NULL) { |
1728 | 0 | if ((fs_file->meta->name2 = (TSK_FS_META_NAME_LIST *) |
1729 | 0 | tsk_malloc(sizeof(TSK_FS_META_NAME_LIST))) == NULL) |
1730 | 0 | return 1; |
1731 | 0 | fs_file->meta->name2->next = NULL; |
1732 | 0 | } |
1733 | | |
1734 | 0 | if (fs_file->meta->attr != NULL) { |
1735 | 0 | tsk_fs_attrlist_markunused(fs_file->meta->attr); |
1736 | 0 | } |
1737 | 0 | else { |
1738 | 0 | fs_file->meta->attr = tsk_fs_attrlist_alloc(); |
1739 | 0 | } |
1740 | |
|
1741 | 0 | fs_file->meta->addr = inode; |
1742 | 0 | strncpy(fs_file->meta->name2->name, name, TSK_FS_META_NAME_LIST_NSIZE - 1); |
1743 | |
|
1744 | 0 | fs_file->meta->size = 0; |
1745 | 0 | fs_file->meta->attr_state = TSK_FS_META_ATTR_EMPTY; |
1746 | |
|
1747 | 0 | return 0; |
1748 | 0 | } |
1749 | | |
1750 | | /** |
1751 | | * \internal |
1752 | | * Create YAFFS2 Deleted Object |
1753 | | * |
1754 | | * @ param yaffs file system |
1755 | | * fs_file to copy file information to |
1756 | | * return 1 on error, 0 on success |
1757 | | */ |
1758 | | static uint8_t |
1759 | | yaffs_make_deleted( YAFFSFS_INFO * yaffsfs, TSK_FS_FILE * a_fs_file ) |
1760 | 0 | { |
1761 | 0 | TSK_FS_FILE *fs_file = a_fs_file; |
1762 | |
|
1763 | 0 | if (tsk_verbose) |
1764 | 0 | tsk_fprintf(stderr, "yaffs_make_deleted: Making virtual deleted node\n"); |
1765 | 0 | if (yaffs_make_directory(yaffsfs, fs_file, YAFFS_OBJECT_DELETED, YAFFS_OBJECT_DELETED_NAME)) |
1766 | 0 | return 1; |
1767 | | |
1768 | 0 | return 0; |
1769 | 0 | } |
1770 | | |
1771 | | /** |
1772 | | * \internal |
1773 | | * Create YAFFS2 Unlinked object |
1774 | | * |
1775 | | * @ param yaffs file system |
1776 | | * fs_file to copy file information to |
1777 | | * return 1 on error, 0 on success |
1778 | | */ |
1779 | | static uint8_t |
1780 | | yaffs_make_unlinked( YAFFSFS_INFO * yaffsfs, TSK_FS_FILE * a_fs_file ) |
1781 | 0 | { |
1782 | 0 | TSK_FS_FILE * fs_file = a_fs_file; |
1783 | |
|
1784 | 0 | if (tsk_verbose) |
1785 | 0 | tsk_fprintf(stderr, "yaffs_make_unlinked: Making virtual unlinked node\n"); |
1786 | |
|
1787 | 0 | if (yaffs_make_directory(yaffsfs, fs_file, YAFFS_OBJECT_UNLINKED, YAFFS_OBJECT_UNLINKED_NAME)) |
1788 | 0 | return 1; |
1789 | | |
1790 | 0 | return 0; |
1791 | 0 | } |
1792 | | |
1793 | | /** |
1794 | | * \internal |
1795 | | * Create YAFFS2 orphan object |
1796 | | * |
1797 | | * @ param yaffs file system |
1798 | | * fs_file to copy file information to |
1799 | | * return 1 on error, 0 on success |
1800 | | */ |
1801 | | static uint8_t |
1802 | | yaffs_make_orphan_dir( YAFFSFS_INFO * yaffsfs, TSK_FS_FILE * a_fs_file ) |
1803 | 0 | { |
1804 | 0 | TSK_FS_FILE * fs_file = a_fs_file; |
1805 | 0 | TSK_FS_NAME *fs_name = tsk_fs_name_alloc(256, 0); |
1806 | 0 | if (fs_name == NULL) |
1807 | 0 | return TSK_ERR; |
1808 | | |
1809 | 0 | if (tsk_verbose) |
1810 | 0 | tsk_fprintf(stderr, "yaffs_make_orphan_dir: Making orphan dir node\n"); |
1811 | |
|
1812 | 0 | if (tsk_fs_dir_make_orphan_dir_name(&(yaffsfs->fs_info), fs_name)) { |
1813 | 0 | tsk_fs_name_free(fs_name); |
1814 | 0 | return TSK_ERR; |
1815 | 0 | } |
1816 | | |
1817 | 0 | if (yaffs_make_directory(yaffsfs, fs_file, yaffsfs->fs_info.last_inum, (char *)fs_name)) { |
1818 | 0 | tsk_fs_name_free(fs_name); |
1819 | 0 | return 1; |
1820 | 0 | } |
1821 | 0 | tsk_fs_name_free(fs_name); |
1822 | 0 | return 0; |
1823 | 0 | } |
1824 | | |
1825 | | /* yaffsfs_inode_lookup - lookup inode, external interface |
1826 | | * |
1827 | | * Returns 1 on error and 0 on success |
1828 | | * |
1829 | | */ |
1830 | | |
1831 | | static uint8_t |
1832 | | yaffs_inode_lookup(TSK_FS_INFO *a_fs, TSK_FS_FILE * a_fs_file, |
1833 | | TSK_INUM_T inum) |
1834 | 0 | { |
1835 | 0 | YAFFSFS_INFO *yfs = (YAFFSFS_INFO *)a_fs; |
1836 | 0 | YaffsCacheObject *obj; |
1837 | 0 | YaffsCacheVersion *version; |
1838 | 0 | YaffsHeader *header = NULL; |
1839 | 0 | YaffsSpare *spare = NULL; |
1840 | 0 | TSK_RETVAL_ENUM result; |
1841 | 0 | uint8_t type; |
1842 | 0 | const char *real_name; |
1843 | |
|
1844 | 0 | if (a_fs_file == NULL) { |
1845 | 0 | tsk_error_set_errno(TSK_ERR_FS_ARG); |
1846 | 0 | tsk_error_set_errstr("yaffsfs_inode_lookup: fs_file is NULL"); |
1847 | 0 | return 1; |
1848 | 0 | } |
1849 | | |
1850 | 0 | if (a_fs_file->meta == NULL) { |
1851 | 0 | if ((a_fs_file->meta = |
1852 | 0 | tsk_fs_meta_alloc(YAFFS_FILE_CONTENT_LEN)) == NULL) |
1853 | 0 | return 1; |
1854 | 0 | } |
1855 | 0 | else { |
1856 | 0 | tsk_fs_meta_reset(a_fs_file->meta); |
1857 | 0 | } |
1858 | | |
1859 | 0 | if (tsk_verbose) |
1860 | 0 | tsk_fprintf(stderr, "yaffs_inode_lookup: looking up %" PRIuINUM "\n",inum); |
1861 | |
|
1862 | 0 | switch(inum) { |
1863 | 0 | case YAFFS_OBJECT_UNLINKED: |
1864 | 0 | yaffs_make_unlinked(yfs, a_fs_file); |
1865 | 0 | return 0; |
1866 | | |
1867 | 0 | case YAFFS_OBJECT_DELETED: |
1868 | 0 | yaffs_make_deleted(yfs, a_fs_file); |
1869 | 0 | return 0; |
1870 | 0 | } |
1871 | | |
1872 | 0 | if (inum == yfs->fs_info.last_inum) { |
1873 | 0 | yaffs_make_orphan_dir(yfs, a_fs_file); |
1874 | 0 | return 0; |
1875 | 0 | } |
1876 | | |
1877 | 0 | result = yaffscache_version_find_by_inode(yfs, inum, &version, &obj); |
1878 | 0 | if (result != TSK_OK) { |
1879 | 0 | if (tsk_verbose) |
1880 | 0 | tsk_fprintf(stderr, "yaffs_inode_lookup: yaffscache_version_find_by_inode failed! (inode = %d)\n", inum); |
1881 | 0 | return 1; |
1882 | 0 | } |
1883 | | |
1884 | 0 | if (version->ycv_header_chunk == NULL) { |
1885 | 0 | return 1; |
1886 | 0 | } |
1887 | | |
1888 | 0 | if (yaffsfs_read_chunk(yfs, &header, &spare, version->ycv_header_chunk->ycc_offset) != TSK_OK) { |
1889 | 0 | if (tsk_verbose) |
1890 | 0 | tsk_fprintf(stderr, "yaffs_inode_lookup: yaffsfs_read_chunk failed!\n"); |
1891 | 0 | return 1; |
1892 | 0 | } |
1893 | | |
1894 | 0 | type = header->obj_type; |
1895 | |
|
1896 | 0 | switch(inum) { |
1897 | 0 | case YAFFS_OBJECT_LOSTNFOUND: |
1898 | 0 | real_name = YAFFS_OBJECT_LOSTNFOUND_NAME; |
1899 | 0 | break; |
1900 | 0 | case YAFFS_OBJECT_UNLINKED: |
1901 | 0 | real_name = YAFFS_OBJECT_UNLINKED_NAME; |
1902 | 0 | break; |
1903 | 0 | case YAFFS_OBJECT_DELETED: |
1904 | 0 | real_name = YAFFS_OBJECT_DELETED_NAME; |
1905 | 0 | break; |
1906 | 0 | default: |
1907 | 0 | real_name = header->name; |
1908 | 0 | break; |
1909 | 0 | } |
1910 | | |
1911 | 0 | switch(type) { |
1912 | 0 | case YAFFS_TYPE_FILE: |
1913 | 0 | if (tsk_verbose) |
1914 | 0 | tsk_fprintf(stderr, "yaffs_inode_lookup: is a file\n"); |
1915 | 0 | yaffs_make_regularfile(yfs, a_fs_file, inum, real_name); |
1916 | 0 | break; |
1917 | | |
1918 | 0 | case YAFFS_TYPE_DIRECTORY: |
1919 | 0 | if (tsk_verbose) |
1920 | 0 | tsk_fprintf(stderr, "yaffs_inode_lookup: is a directory\n"); |
1921 | 0 | yaffs_make_directory(yfs, a_fs_file, inum, real_name); |
1922 | 0 | break; |
1923 | | |
1924 | 0 | case YAFFS_TYPE_SOFTLINK: |
1925 | 0 | if (tsk_verbose) |
1926 | 0 | tsk_fprintf(stderr, "yaffs_inode_lookup: is a symbolic link\n"); |
1927 | 0 | yaffs_make_regularfile(yfs, a_fs_file, inum, real_name); |
1928 | 0 | a_fs_file->meta->type = TSK_FS_META_TYPE_LNK; |
1929 | 0 | break; |
1930 | | |
1931 | 0 | case YAFFS_TYPE_HARDLINK: |
1932 | 0 | case YAFFS_TYPE_UNKNOWN: |
1933 | 0 | if (tsk_verbose) |
1934 | 0 | tsk_fprintf(stderr, "yaffs_inode_lookup: is *** UNHANDLED *** (type %d, header at 0x%x)\n", type, version->ycv_header_chunk->ycc_offset); |
1935 | | // We can still set a few things |
1936 | 0 | a_fs_file->meta->type = TSK_FS_META_TYPE_UNDEF; |
1937 | 0 | a_fs_file->meta->addr = inum; |
1938 | 0 | if (yaffs_is_version_allocated(yfs, inum)) { |
1939 | 0 | a_fs_file->meta->flags = |
1940 | 0 | (TSK_FS_META_FLAG_ENUM)(TSK_FS_META_FLAG_USED | TSK_FS_META_FLAG_ALLOC); |
1941 | 0 | } |
1942 | 0 | else { |
1943 | 0 | a_fs_file->meta->flags = |
1944 | 0 | (TSK_FS_META_FLAG_ENUM)(TSK_FS_META_FLAG_USED | TSK_FS_META_FLAG_UNALLOC); |
1945 | 0 | } |
1946 | 0 | if (a_fs_file->meta->name2 == NULL) { |
1947 | 0 | if ((a_fs_file->meta->name2 = (TSK_FS_META_NAME_LIST *) |
1948 | 0 | tsk_malloc(sizeof(TSK_FS_META_NAME_LIST))) == NULL) { |
1949 | 0 | return 1; |
1950 | 0 | } |
1951 | 0 | a_fs_file->meta->name2->next = NULL; |
1952 | 0 | } |
1953 | 0 | strncpy(a_fs_file->meta->name2->name, real_name, |
1954 | 0 | TSK_FS_META_NAME_LIST_NSIZE); |
1955 | 0 | break; |
1956 | 0 | default: |
1957 | 0 | if (tsk_verbose) |
1958 | 0 | tsk_fprintf(stderr, "yaffs_inode_lookup: type is invalid (type %d, header at 0x%x)\n", type, version->ycv_header_chunk->ycc_offset); |
1959 | 0 | return 1; |
1960 | 0 | } |
1961 | | |
1962 | | /* Who owns this? I'm following the way FATFS does it by freeing + NULLing |
1963 | | * this and mallocing if used. |
1964 | | */ |
1965 | 0 | free(a_fs_file->meta->link); |
1966 | 0 | a_fs_file->meta->link = NULL; |
1967 | |
|
1968 | 0 | if (type != YAFFS_TYPE_HARDLINK) { |
1969 | 0 | a_fs_file->meta->mode = (TSK_FS_META_MODE_ENUM)(header->file_mode & TWELVE_BITS_MASK); // chop at 12 bits; |
1970 | 0 | a_fs_file->meta->uid = header->user_id; |
1971 | 0 | a_fs_file->meta->gid = header->group_id; |
1972 | 0 | a_fs_file->meta->mtime = header->mtime; |
1973 | 0 | a_fs_file->meta->atime = header->atime; |
1974 | 0 | a_fs_file->meta->ctime = header->ctime; |
1975 | 0 | } |
1976 | |
|
1977 | 0 | if (type == YAFFS_TYPE_FILE) { |
1978 | 0 | a_fs_file->meta->size = header->file_size; |
1979 | | // NOTE: This isn't in Android 3.3 kernel but is in YAFFS2 git |
1980 | | //a_fs_file->meta->size |= ((TSK_OFF_T) header->file_size_high) << 32; |
1981 | 0 | } |
1982 | |
|
1983 | 0 | if (type == YAFFS_TYPE_HARDLINK) { |
1984 | | // TODO: Store equivalent_id somewhere? */ |
1985 | 0 | } |
1986 | |
|
1987 | 0 | if (type == YAFFS_TYPE_SOFTLINK) { |
1988 | 0 | a_fs_file->meta->link = (char*)tsk_malloc(YAFFS_HEADER_ALIAS_LENGTH); |
1989 | 0 | if (a_fs_file->meta->link == NULL) { |
1990 | 0 | free(header); |
1991 | 0 | free(spare); |
1992 | 0 | return 1; |
1993 | 0 | } |
1994 | | |
1995 | 0 | memcpy(a_fs_file->meta->link, header->alias, YAFFS_HEADER_ALIAS_LENGTH); |
1996 | 0 | } |
1997 | | |
1998 | 0 | free(header); |
1999 | 0 | free(spare); |
2000 | 0 | return 0; |
2001 | 0 | } |
2002 | | |
2003 | | |
2004 | | |
2005 | | /* yaffsfs_inode_walk - inode iterator |
2006 | | * |
2007 | | * flags used: TSK_FS_META_FLAG_USED, TSK_FS_META_FLAG_UNUSED, |
2008 | | * TSK_FS_META_FLAG_ALLOC, TSK_FS_META_FLAG_UNALLOC, TSK_FS_META_FLAG_ORPHAN |
2009 | | * |
2010 | | * Return 1 on error and 0 on success |
2011 | | */ |
2012 | | |
2013 | | static uint8_t |
2014 | | yaffsfs_inode_walk(TSK_FS_INFO *fs, TSK_INUM_T start_inum, |
2015 | | TSK_INUM_T end_inum, TSK_FS_META_FLAG_ENUM flags, |
2016 | | TSK_FS_META_WALK_CB a_action, void *a_ptr) |
2017 | 0 | { |
2018 | 0 | YAFFSFS_INFO *yfs = (YAFFSFS_INFO *)fs; |
2019 | 0 | TSK_RETVAL_ENUM result; |
2020 | |
|
2021 | 0 | uint32_t start_obj_id; |
2022 | 0 | uint32_t start_ver_number; |
2023 | 0 | uint32_t end_obj_id; |
2024 | 0 | uint32_t end_ver_number; |
2025 | |
|
2026 | 0 | uint32_t obj_id; |
2027 | |
|
2028 | 0 | YaffsCacheObject *curr_obj; |
2029 | 0 | YaffsCacheVersion *curr_version; |
2030 | |
|
2031 | 0 | result = yaffscache_inode_to_obj_id_and_version(start_inum, &start_obj_id, &start_ver_number); |
2032 | |
|
2033 | 0 | result = yaffscache_inode_to_obj_id_and_version(end_inum, &end_obj_id, &end_ver_number); |
2034 | |
|
2035 | 0 | if (end_obj_id < start_obj_id) { |
2036 | 0 | tsk_error_reset(); |
2037 | 0 | tsk_error_set_errno(TSK_ERR_FS_WALK_RNG); |
2038 | 0 | tsk_error_set_errstr("yaffsfs_inode_walk: end object id must be >= start object id: " |
2039 | 0 | "%" PRIx32 " must be >= %" PRIx32 "", |
2040 | 0 | end_obj_id, start_obj_id); |
2041 | 0 | return 1; |
2042 | 0 | } |
2043 | | |
2044 | | /* The ORPHAN flag is unsupported for YAFFS2 */ |
2045 | 0 | if (flags & TSK_FS_META_FLAG_ORPHAN) { |
2046 | 0 | if (tsk_verbose) { |
2047 | 0 | tsk_fprintf(stderr, "yaffsfs_inode_walk: ORPHAN flag unsupported by YAFFS2"); |
2048 | 0 | } |
2049 | 0 | } |
2050 | |
|
2051 | 0 | if (((flags & TSK_FS_META_FLAG_ALLOC) == 0) && |
2052 | 0 | ((flags & TSK_FS_META_FLAG_UNALLOC) == 0)) { |
2053 | 0 | flags = (TSK_FS_META_FLAG_ENUM)(flags | TSK_FS_META_FLAG_ALLOC | TSK_FS_META_FLAG_UNALLOC); |
2054 | 0 | } |
2055 | | |
2056 | | /* If neither of the USED or UNUSED flags are set, then set them |
2057 | | * both |
2058 | | */ |
2059 | 0 | if (((flags & TSK_FS_META_FLAG_USED) == 0) && |
2060 | 0 | ((flags & TSK_FS_META_FLAG_UNUSED) == 0)) { |
2061 | 0 | flags = (TSK_FS_META_FLAG_ENUM)(flags | TSK_FS_META_FLAG_USED | TSK_FS_META_FLAG_UNUSED); |
2062 | 0 | } |
2063 | |
|
2064 | 0 | std::unique_ptr<TSK_FS_FILE, decltype(&tsk_fs_file_close)> fs_file{ |
2065 | 0 | tsk_fs_file_alloc(fs), |
2066 | 0 | tsk_fs_file_close |
2067 | 0 | }; |
2068 | |
|
2069 | 0 | if (!fs_file) |
2070 | 0 | return 1; |
2071 | 0 | if ((fs_file->meta = |
2072 | 0 | tsk_fs_meta_alloc(YAFFS_FILE_CONTENT_LEN)) == NULL) |
2073 | 0 | return 1; |
2074 | | |
2075 | 0 | for (obj_id = start_obj_id; obj_id <= end_obj_id; obj_id++) { |
2076 | 0 | int retval; |
2077 | |
|
2078 | 0 | result = yaffscache_version_find_by_inode(yfs, obj_id, &curr_version, &curr_obj); |
2079 | 0 | if (result == TSK_OK) { |
2080 | |
|
2081 | 0 | TSK_INUM_T curr_inode; |
2082 | 0 | YaffsCacheVersion *version; |
2083 | | |
2084 | | // ALLOC, UNALLOC, or both are set at this point |
2085 | 0 | if (flags & TSK_FS_META_FLAG_ALLOC) { |
2086 | | // Allocated only - just look at current version |
2087 | 0 | if (yaffscache_obj_id_and_version_to_inode(obj_id, curr_obj->yco_latest->ycv_version, &curr_inode) != TSK_OK) { |
2088 | 0 | return 1; |
2089 | 0 | } |
2090 | | |
2091 | | // It's possible for the current version to be unallocated if the last header was a deleted or unlinked header |
2092 | 0 | if (yaffs_is_version_allocated(yfs, curr_inode)) { |
2093 | 0 | if (yaffs_inode_lookup(fs, fs_file.get(), curr_inode) != TSK_OK) { |
2094 | 0 | return 1; |
2095 | 0 | } |
2096 | | |
2097 | 0 | retval = a_action(fs_file.get(), a_ptr); |
2098 | 0 | if (retval == TSK_WALK_STOP) { |
2099 | 0 | return 0; |
2100 | 0 | } |
2101 | 0 | else if (retval == TSK_WALK_ERROR) { |
2102 | 0 | return 1; |
2103 | 0 | } |
2104 | 0 | } |
2105 | 0 | } |
2106 | 0 | if (flags & TSK_FS_META_FLAG_UNALLOC) { |
2107 | 0 | for (version = curr_obj->yco_latest; version != NULL; version = version->ycv_prior) { |
2108 | 0 | if (yaffscache_obj_id_and_version_to_inode(obj_id, version->ycv_version, &curr_inode) != TSK_OK) { |
2109 | 0 | return 1; |
2110 | 0 | } |
2111 | | |
2112 | 0 | if (!yaffs_is_version_allocated(yfs, curr_inode)) { |
2113 | 0 | if (yaffs_inode_lookup(fs, fs_file.get(), curr_inode) != TSK_OK) { |
2114 | 0 | return 1; |
2115 | 0 | } |
2116 | | |
2117 | 0 | retval = a_action(fs_file.get(), a_ptr); |
2118 | 0 | if (retval == TSK_WALK_STOP) { |
2119 | 0 | return 0; |
2120 | 0 | } |
2121 | 0 | else if (retval == TSK_WALK_ERROR) { |
2122 | 0 | return 1; |
2123 | 0 | } |
2124 | 0 | } |
2125 | 0 | } |
2126 | 0 | } |
2127 | | |
2128 | 0 | curr_obj = curr_obj->yco_next; |
2129 | 0 | } |
2130 | 0 | } |
2131 | | |
2132 | | /* |
2133 | | * Cleanup. |
2134 | | */ |
2135 | 0 | return 0; |
2136 | 0 | } |
2137 | | |
2138 | | static TSK_FS_BLOCK_FLAG_ENUM |
2139 | | yaffsfs_block_getflags(TSK_FS_INFO *fs, TSK_DADDR_T a_addr) |
2140 | 0 | { |
2141 | 0 | YAFFSFS_INFO *yfs = (YAFFSFS_INFO *)fs; |
2142 | 0 | TSK_FS_BLOCK_FLAG_ENUM flags = TSK_FS_BLOCK_FLAG_UNUSED; |
2143 | |
|
2144 | 0 | TSK_OFF_T offset = (a_addr * (fs->block_pre_size + fs->block_size + fs->block_post_size)) + yfs->page_size; |
2145 | 0 | YaffsSpare *spare = NULL; |
2146 | 0 | YaffsHeader *header = NULL; |
2147 | 0 | if (yaffsfs_read_spare(yfs, &spare, offset) != TSK_OK) { |
2148 | | /* NOTE: Uh, how do we signal error? */ |
2149 | 0 | return flags; |
2150 | 0 | } |
2151 | | |
2152 | 0 | if (yaffsfs_is_spare_valid(yfs, spare) == TSK_OK) { |
2153 | | /* XXX: Do we count blocks of older versions unallocated? |
2154 | | * If so, we need a smarter way to do this :/ |
2155 | | * |
2156 | | * Walk the object from this block and see if this |
2157 | | * block is used in the latest version. Could pre- |
2158 | | * calculate this at cache time as well. |
2159 | | */ |
2160 | | |
2161 | |
|
2162 | 0 | if (spare->chunk_id == 0) { |
2163 | 0 | flags = (TSK_FS_BLOCK_FLAG_ENUM)(flags | TSK_FS_BLOCK_FLAG_META); |
2164 | 0 | } else { |
2165 | 0 | flags = (TSK_FS_BLOCK_FLAG_ENUM)(flags | TSK_FS_BLOCK_FLAG_CONT); |
2166 | 0 | } |
2167 | | |
2168 | | // Have obj id and offset |
2169 | | // 1. Is the current version of this object allocated? |
2170 | | // 2. If this is a header, is it the header of the current version? |
2171 | | // 3. Is the chunk id too big given the current header? |
2172 | | // 4. Is there a more recent version of this chunk id? |
2173 | 0 | YaffsCacheObject * obj = NULL; |
2174 | 0 | yaffscache_object_find(yfs, spare->object_id, &obj); |
2175 | | |
2176 | | // The result really shouldn't be NULL since we loaded every chunk |
2177 | 0 | if (obj != NULL) { |
2178 | 0 | if (! yaffs_is_version_allocated(yfs, spare->object_id)) { |
2179 | | // If the current version isn't allocated, then no chunks in it are |
2180 | 0 | flags = (TSK_FS_BLOCK_FLAG_ENUM)(flags | TSK_FS_BLOCK_FLAG_UNALLOC); |
2181 | 0 | } |
2182 | 0 | else if (obj->yco_latest == NULL || obj->yco_latest->ycv_header_chunk == NULL) { |
2183 | 0 | flags = (TSK_FS_BLOCK_FLAG_ENUM)(flags | TSK_FS_BLOCK_FLAG_UNALLOC); |
2184 | 0 | } |
2185 | 0 | else if (spare->chunk_id == 0) { |
2186 | 0 | if (obj->yco_latest->ycv_header_chunk->ycc_offset == offset - yfs->page_size) { |
2187 | | // Have header chunk and it's the most recent header chunk |
2188 | 0 | flags = (TSK_FS_BLOCK_FLAG_ENUM)(flags | TSK_FS_BLOCK_FLAG_ALLOC); |
2189 | 0 | } |
2190 | 0 | else { |
2191 | | // Have header chunk but isn't the most recent |
2192 | 0 | flags = (TSK_FS_BLOCK_FLAG_ENUM)(flags | TSK_FS_BLOCK_FLAG_UNALLOC); |
2193 | 0 | } |
2194 | 0 | } |
2195 | 0 | else { |
2196 | | // Read in the full header |
2197 | 0 | yaffsfs_read_header(yfs, &header, obj->yco_latest->ycv_header_chunk->ycc_offset); |
2198 | | |
2199 | | // chunk_id is 1-based, so for example chunk id 2 would be too big for a file |
2200 | | // 500 bytes long |
2201 | 0 | if (header->file_size <= ((spare->chunk_id - 1) * (fs->block_size))) { |
2202 | 0 | flags = (TSK_FS_BLOCK_FLAG_ENUM)(flags | TSK_FS_BLOCK_FLAG_UNALLOC); |
2203 | 0 | } |
2204 | 0 | else { |
2205 | | // Since at this point we know there should be a chunk with this chunk id in the file, if |
2206 | | // this is the most recent version of the chunk assume it's part of the current version of the object. |
2207 | 0 | YaffsCacheChunk * curr = obj->yco_latest->ycv_last_chunk; |
2208 | 0 | while(curr != NULL) { // curr should really never make it to the beginning of the list |
2209 | | |
2210 | | // Did we find our chunk? |
2211 | 0 | if (curr->ycc_offset == offset - yfs->page_size) { |
2212 | 0 | flags = (TSK_FS_BLOCK_FLAG_ENUM)(flags | TSK_FS_BLOCK_FLAG_ALLOC); |
2213 | 0 | break; |
2214 | 0 | } |
2215 | | |
2216 | | // Did we find a different chunk with our chunk id? |
2217 | 0 | if (curr->ycc_chunk_id == spare->chunk_id) { |
2218 | 0 | flags = (TSK_FS_BLOCK_FLAG_ENUM)(flags | TSK_FS_BLOCK_FLAG_UNALLOC); |
2219 | 0 | break; |
2220 | 0 | } |
2221 | 0 | curr = curr->ycc_prev; |
2222 | 0 | } |
2223 | 0 | } |
2224 | 0 | } |
2225 | 0 | } |
2226 | |
|
2227 | 0 | } else { |
2228 | 0 | flags = (TSK_FS_BLOCK_FLAG_ENUM)(flags | TSK_FS_BLOCK_FLAG_UNUSED | TSK_FS_BLOCK_FLAG_UNALLOC); |
2229 | 0 | } |
2230 | |
|
2231 | 0 | free(spare); |
2232 | 0 | free(header); |
2233 | 0 | return flags; |
2234 | 0 | } |
2235 | | |
2236 | | |
2237 | | /* yaffsfs_block_walk - block iterator |
2238 | | * |
2239 | | * flags: TSK_FS_BLOCK_FLAG_ALLOC, TSK_FS_BLOCK_FLAG_UNALLOC, TSK_FS_BLOCK_FLAG_CONT, |
2240 | | * TSK_FS_BLOCK_FLAG_META |
2241 | | * |
2242 | | * Return 1 on error and 0 on success |
2243 | | */ |
2244 | | static uint8_t |
2245 | | yaffsfs_block_walk(TSK_FS_INFO *a_fs, TSK_DADDR_T a_start_blk, |
2246 | | TSK_DADDR_T a_end_blk, TSK_FS_BLOCK_WALK_FLAG_ENUM a_flags, |
2247 | | TSK_FS_BLOCK_WALK_CB a_action, void *a_ptr) |
2248 | 0 | { |
2249 | 0 | TSK_FS_BLOCK *fs_block; |
2250 | 0 | TSK_DADDR_T addr; |
2251 | | |
2252 | | // clean up any error messages that are lying around |
2253 | 0 | tsk_error_reset(); |
2254 | | |
2255 | | /* |
2256 | | * Sanity checks. |
2257 | | */ |
2258 | 0 | if (a_start_blk < a_fs->first_block || a_start_blk > a_fs->last_block) { |
2259 | 0 | tsk_error_reset(); |
2260 | 0 | tsk_error_set_errno(TSK_ERR_FS_WALK_RNG); |
2261 | 0 | tsk_error_set_errstr("yaffsfs_block_walk: start block: %" PRIuDADDR, |
2262 | 0 | a_start_blk); |
2263 | 0 | return 1; |
2264 | 0 | } |
2265 | 0 | if (a_end_blk < a_fs->first_block || a_end_blk > a_fs->last_block |
2266 | 0 | || a_end_blk < a_start_blk) { |
2267 | 0 | tsk_error_reset(); |
2268 | 0 | tsk_error_set_errno(TSK_ERR_FS_WALK_RNG); |
2269 | 0 | tsk_error_set_errstr("yaffsfs_block_walk: end block: %" PRIuDADDR , |
2270 | 0 | a_end_blk); |
2271 | 0 | return 1; |
2272 | 0 | } |
2273 | | |
2274 | | /* Sanity check on a_flags -- make sure at least one ALLOC is set */ |
2275 | 0 | if (((a_flags & TSK_FS_BLOCK_WALK_FLAG_ALLOC) == 0) && |
2276 | 0 | ((a_flags & TSK_FS_BLOCK_WALK_FLAG_UNALLOC) == 0)) { |
2277 | 0 | a_flags = (TSK_FS_BLOCK_WALK_FLAG_ENUM) |
2278 | 0 | (a_flags | TSK_FS_BLOCK_WALK_FLAG_ALLOC | |
2279 | 0 | TSK_FS_BLOCK_WALK_FLAG_UNALLOC); |
2280 | 0 | } |
2281 | 0 | if (((a_flags & TSK_FS_BLOCK_WALK_FLAG_META) == 0) && |
2282 | 0 | ((a_flags & TSK_FS_BLOCK_WALK_FLAG_CONT) == 0)) { |
2283 | 0 | a_flags = (TSK_FS_BLOCK_WALK_FLAG_ENUM) |
2284 | 0 | (a_flags | TSK_FS_BLOCK_WALK_FLAG_CONT | TSK_FS_BLOCK_WALK_FLAG_META); |
2285 | 0 | } |
2286 | | |
2287 | |
|
2288 | 0 | if ((fs_block = tsk_fs_block_alloc(a_fs)) == NULL) { |
2289 | 0 | return 1; |
2290 | 0 | } |
2291 | | |
2292 | 0 | for (addr = a_start_blk; addr <= a_end_blk; addr++) { |
2293 | 0 | int retval; |
2294 | 0 | int myflags; |
2295 | |
|
2296 | 0 | myflags = yaffsfs_block_getflags(a_fs, addr); |
2297 | | |
2298 | | // test if we should call the callback with this one |
2299 | 0 | if ((myflags & TSK_FS_BLOCK_FLAG_META) |
2300 | 0 | && (!(a_flags & TSK_FS_BLOCK_WALK_FLAG_META))) |
2301 | 0 | continue; |
2302 | 0 | else if ((myflags & TSK_FS_BLOCK_FLAG_CONT) |
2303 | 0 | && (!(a_flags & TSK_FS_BLOCK_WALK_FLAG_CONT))) |
2304 | 0 | continue; |
2305 | 0 | else if ((myflags & TSK_FS_BLOCK_FLAG_ALLOC) |
2306 | 0 | && (!(a_flags & TSK_FS_BLOCK_WALK_FLAG_ALLOC))) |
2307 | 0 | continue; |
2308 | 0 | else if ((myflags & TSK_FS_BLOCK_FLAG_UNALLOC) |
2309 | 0 | && (!(a_flags & TSK_FS_BLOCK_WALK_FLAG_UNALLOC))) |
2310 | 0 | continue; |
2311 | | |
2312 | 0 | if (tsk_fs_block_get(a_fs, fs_block, addr) == NULL) { |
2313 | 0 | tsk_error_set_errstr2("yaffsfs_block_walk: block %" PRIuDADDR, |
2314 | 0 | addr); |
2315 | 0 | tsk_fs_block_free(fs_block); |
2316 | 0 | return 1; |
2317 | 0 | } |
2318 | | |
2319 | 0 | retval = a_action(fs_block, a_ptr); |
2320 | 0 | if (retval == TSK_WALK_STOP) { |
2321 | 0 | break; |
2322 | 0 | } |
2323 | 0 | else if (retval == TSK_WALK_ERROR) { |
2324 | 0 | tsk_fs_block_free(fs_block); |
2325 | 0 | return 1; |
2326 | 0 | } |
2327 | 0 | } |
2328 | | |
2329 | | /* |
2330 | | * Cleanup. |
2331 | | */ |
2332 | 0 | tsk_fs_block_free(fs_block); |
2333 | 0 | return 0; |
2334 | 0 | } |
2335 | | |
2336 | | static uint8_t |
2337 | | yaffsfs_fscheck(TSK_FS_INFO * /*fs*/, FILE * /*hFile*/) |
2338 | 0 | { |
2339 | 0 | tsk_error_reset(); |
2340 | 0 | tsk_error_set_errno(TSK_ERR_FS_UNSUPFUNC); |
2341 | 0 | tsk_error_set_errstr("fscheck not implemented yet for YAFFS"); |
2342 | 0 | return 1; |
2343 | 0 | } |
2344 | | |
2345 | | |
2346 | | /** |
2347 | | * Print details about the file system to a file handle. |
2348 | | * |
2349 | | * @param fs File system to print details on |
2350 | | * @param hFile File handle to print text to |
2351 | | * |
2352 | | * @returns 1 on error and 0 on success |
2353 | | */ |
2354 | | static uint8_t |
2355 | | yaffsfs_fsstat(TSK_FS_INFO * fs, FILE * hFile) |
2356 | 0 | { |
2357 | 0 | YAFFSFS_INFO *yfs = (YAFFSFS_INFO *) fs; |
2358 | 0 | unsigned int obj_count, version_count; |
2359 | 0 | uint32_t obj_first, obj_last, version_first, version_last; |
2360 | | |
2361 | | // clean up any error messages that are lying around |
2362 | 0 | tsk_error_reset(); |
2363 | |
|
2364 | 0 | tsk_fprintf(hFile, "FILE SYSTEM INFORMATION\n"); |
2365 | 0 | tsk_fprintf(hFile, "--------------------------------------------\n"); |
2366 | |
|
2367 | 0 | tsk_fprintf(hFile, "File System Type: YAFFS2\n"); |
2368 | 0 | tsk_fprintf(hFile, "Page Size: %u\n", yfs->page_size); |
2369 | 0 | tsk_fprintf(hFile, "Spare Size: %u\n", yfs->spare_size); |
2370 | 0 | tsk_fprintf(hFile, "Spare Offsets: Sequence number: %d, Object ID: %d, Chunk ID: %d, nBytes: %d\n", |
2371 | 0 | yfs->spare_seq_offset, yfs->spare_obj_id_offset, yfs->spare_chunk_id_offset, yfs->spare_nbytes_offset); |
2372 | |
|
2373 | 0 | tsk_fprintf(hFile, "\nMETADATA INFORMATION\n"); |
2374 | 0 | tsk_fprintf(hFile, "--------------------------------------------\n"); |
2375 | | |
2376 | |
|
2377 | 0 | yaffscache_objects_stats(yfs, |
2378 | 0 | &obj_count, &obj_first, &obj_last, |
2379 | 0 | &version_count, &version_first, &version_last); |
2380 | |
|
2381 | 0 | tsk_fprintf(hFile, "Number of Allocated Objects: %u\n", obj_count); |
2382 | 0 | tsk_fprintf(hFile, "Object Id Range: %" PRIu32 " - %" PRIu32 "\n", |
2383 | 0 | obj_first, obj_last); |
2384 | 0 | tsk_fprintf(hFile, "Number of Total Object Versions: %u\n", version_count); |
2385 | 0 | tsk_fprintf(hFile, "Object Version Range: %" PRIu32 " - %" PRIu32 "\n", |
2386 | 0 | version_first, version_last); |
2387 | |
|
2388 | 0 | return 0; |
2389 | 0 | } |
2390 | | |
2391 | | /************************* istat *******************************/ |
2392 | | |
2393 | | typedef struct { |
2394 | | FILE *hFile; |
2395 | | int idx; |
2396 | | } YAFFSFS_PRINT_ADDR; |
2397 | | |
2398 | | /* Callback for istat to print the block addresses */ |
2399 | | static TSK_WALK_RET_ENUM |
2400 | | print_addr_act(YAFFSFS_INFO * /*fs_file*/, TSK_OFF_T /*a_off*/, |
2401 | | TSK_DADDR_T addr, char * /*buf*/, size_t /*size*/, |
2402 | | TSK_FS_BLOCK_FLAG_ENUM flags, void *a_ptr) |
2403 | 0 | { |
2404 | 0 | YAFFSFS_PRINT_ADDR *print = (YAFFSFS_PRINT_ADDR *) a_ptr; |
2405 | |
|
2406 | 0 | if (flags & TSK_FS_BLOCK_FLAG_CONT) { |
2407 | 0 | tsk_fprintf(print->hFile, "%" PRIuDADDR " ", addr); |
2408 | |
|
2409 | 0 | if (++(print->idx) == 8) { |
2410 | 0 | tsk_fprintf(print->hFile, "\n"); |
2411 | 0 | print->idx = 0; |
2412 | 0 | } |
2413 | 0 | } |
2414 | |
|
2415 | 0 | return TSK_WALK_CONT; |
2416 | 0 | } |
2417 | | |
2418 | | /** |
2419 | | * Print details on a specific file to a file handle. |
2420 | | * |
2421 | | * @param fs File system file is located in |
2422 | | * @param hFile File handle to print text to |
2423 | | * @param inum Address of file in file system |
2424 | | * @param numblock The number of blocks in file to force print (can go beyond file size) |
2425 | | * @param sec_skew Clock skew in seconds to also print times in |
2426 | | * |
2427 | | * @returns 1 on error and 0 on success |
2428 | | */ |
2429 | | static uint8_t |
2430 | | yaffsfs_istat(TSK_FS_INFO *fs, TSK_FS_ISTAT_FLAG_ENUM flags, FILE * hFile, TSK_INUM_T inum, |
2431 | | TSK_DADDR_T numblock, int32_t sec_skew) |
2432 | 0 | { |
2433 | 0 | TSK_FS_META *fs_meta; |
2434 | 0 | YAFFSFS_INFO *yfs = (YAFFSFS_INFO *)fs; |
2435 | 0 | char ls[12]; |
2436 | 0 | YAFFSFS_PRINT_ADDR print; |
2437 | 0 | char timeBuf[128]; |
2438 | 0 | YaffsCacheObject * obj = NULL; |
2439 | 0 | YaffsCacheVersion * version = NULL; |
2440 | 0 | YaffsHeader * header = NULL; |
2441 | |
|
2442 | 0 | yaffscache_version_find_by_inode(yfs, inum, &version, &obj); |
2443 | |
|
2444 | 0 | std::unique_ptr<TSK_FS_FILE, decltype(&tsk_fs_file_close)> fs_file{ |
2445 | 0 | tsk_fs_file_open_meta(fs, NULL, inum), |
2446 | 0 | tsk_fs_file_close |
2447 | 0 | }; |
2448 | |
|
2449 | 0 | if (!fs_file) { |
2450 | 0 | return 1; |
2451 | 0 | } |
2452 | 0 | fs_meta = fs_file->meta; |
2453 | |
|
2454 | 0 | tsk_fprintf(hFile, "inode: %" PRIuINUM "\n", inum); |
2455 | 0 | tsk_fprintf(hFile, "%sAllocated\n", |
2456 | 0 | (fs_meta->flags & TSK_FS_META_FLAG_ALLOC) ? "" : "Not "); |
2457 | |
|
2458 | 0 | if (fs_meta->link) |
2459 | 0 | tsk_fprintf(hFile, "symbolic link to: %s\n", fs_meta->link); |
2460 | |
|
2461 | 0 | tsk_fprintf(hFile, "uid / gid: %" PRIuUID " / %" PRIuGID "\n", |
2462 | 0 | fs_meta->uid, fs_meta->gid); |
2463 | |
|
2464 | 0 | tsk_fs_meta_make_ls(fs_meta, ls, sizeof(ls)); |
2465 | 0 | tsk_fprintf(hFile, "mode: %s\n", ls); |
2466 | |
|
2467 | 0 | tsk_fprintf(hFile, "size: %" PRIdOFF "\n", fs_meta->size); |
2468 | 0 | tsk_fprintf(hFile, "num of links: %d\n", fs_meta->nlink); |
2469 | |
|
2470 | 0 | if (version != NULL) { |
2471 | 0 | yaffsfs_read_header(yfs, &header, version->ycv_header_chunk->ycc_offset); |
2472 | 0 | if (header != NULL) { |
2473 | 0 | tsk_fprintf(hFile, "Name: %s\n", header->name); |
2474 | 0 | } |
2475 | 0 | } |
2476 | |
|
2477 | 0 | if (sec_skew != 0) { |
2478 | 0 | tsk_fprintf(hFile, "\nAdjusted Inode Times:\n"); |
2479 | 0 | fs_meta->mtime -= sec_skew; |
2480 | 0 | fs_meta->atime -= sec_skew; |
2481 | 0 | fs_meta->ctime -= sec_skew; |
2482 | |
|
2483 | 0 | tsk_fprintf(hFile, "Accessed:\t%s\n", |
2484 | 0 | tsk_fs_time_to_str(fs_meta->atime, timeBuf)); |
2485 | 0 | tsk_fprintf(hFile, "File Modified:\t%s\n", |
2486 | 0 | tsk_fs_time_to_str(fs_meta->mtime, timeBuf)); |
2487 | 0 | tsk_fprintf(hFile, "Inode Modified:\t%s\n", |
2488 | 0 | tsk_fs_time_to_str(fs_meta->ctime, timeBuf)); |
2489 | |
|
2490 | 0 | fs_meta->mtime += sec_skew; |
2491 | 0 | fs_meta->atime += sec_skew; |
2492 | 0 | fs_meta->ctime += sec_skew; |
2493 | |
|
2494 | 0 | tsk_fprintf(hFile, "\nOriginal Inode Times:\n"); |
2495 | 0 | } |
2496 | 0 | else { |
2497 | 0 | tsk_fprintf(hFile, "\nInode Times:\n"); |
2498 | 0 | } |
2499 | |
|
2500 | 0 | tsk_fprintf(hFile, "Accessed:\t%s\n", |
2501 | 0 | tsk_fs_time_to_str(fs_meta->atime, timeBuf)); |
2502 | 0 | tsk_fprintf(hFile, "File Modified:\t%s\n", |
2503 | 0 | tsk_fs_time_to_str(fs_meta->mtime, timeBuf)); |
2504 | 0 | tsk_fprintf(hFile, "Inode Modified:\t%s\n", |
2505 | 0 | tsk_fs_time_to_str(fs_meta->ctime, timeBuf)); |
2506 | |
|
2507 | 0 | if (version != NULL) { |
2508 | 0 | tsk_fprintf(hFile, "\nHeader Chunk:\n"); |
2509 | 0 | tsk_fprintf(hFile, "%" PRIuDADDR "\n", (version->ycv_header_chunk->ycc_offset / (yfs->page_size + yfs->spare_size))); |
2510 | 0 | } |
2511 | |
|
2512 | 0 | if (numblock > 0) { |
2513 | 0 | TSK_OFF_T lower_size = numblock * fs->block_size; |
2514 | 0 | fs_meta->size = (lower_size < fs_meta->size)?(lower_size):(fs_meta->size); |
2515 | 0 | } |
2516 | 0 | tsk_fprintf(hFile, "\nData Chunks:\n"); |
2517 | | |
2518 | |
|
2519 | 0 | if (flags & TSK_FS_ISTAT_RUNLIST) { |
2520 | 0 | const TSK_FS_ATTR *fs_attr_default = |
2521 | 0 | tsk_fs_file_attr_get_type(fs_file.get(), |
2522 | 0 | TSK_FS_ATTR_TYPE_DEFAULT, 0, 0); |
2523 | 0 | if (fs_attr_default && (fs_attr_default->flags & TSK_FS_ATTR_NONRES)) { |
2524 | 0 | if (tsk_fs_attr_print(fs_attr_default, hFile)) { |
2525 | 0 | tsk_fprintf(hFile, "\nError creating run lists "); |
2526 | 0 | tsk_error_print(hFile); |
2527 | 0 | tsk_error_reset(); |
2528 | 0 | } |
2529 | 0 | } |
2530 | 0 | } |
2531 | 0 | else { |
2532 | 0 | print.idx = 0; |
2533 | 0 | print.hFile = hFile; |
2534 | |
|
2535 | 0 | if (tsk_fs_file_walk(fs_file.get(), TSK_FS_FILE_WALK_FLAG_AONLY, |
2536 | 0 | (TSK_FS_FILE_WALK_CB)print_addr_act, (void *)&print)) { |
2537 | 0 | tsk_fprintf(hFile, "\nError reading file: "); |
2538 | 0 | tsk_error_print(hFile); |
2539 | 0 | tsk_error_reset(); |
2540 | 0 | } |
2541 | 0 | else if (print.idx != 0) { |
2542 | 0 | tsk_fprintf(hFile, "\n"); |
2543 | 0 | } |
2544 | 0 | } |
2545 | |
|
2546 | 0 | return 0; |
2547 | 0 | } |
2548 | | |
2549 | | /* yaffsfs_close - close an yaffsfs file system */ |
2550 | | static void |
2551 | | yaffsfs_close(TSK_FS_INFO *fs) |
2552 | 0 | { |
2553 | 0 | if (fs != NULL) { |
2554 | 0 | YAFFSFS_INFO *yfs = (YAFFSFS_INFO *)fs; |
2555 | |
|
2556 | 0 | fs->tag = 0; |
2557 | | |
2558 | | // Walk and free the cache structures |
2559 | 0 | yaffscache_objects_free(yfs); |
2560 | 0 | yaffscache_chunks_free(yfs); |
2561 | | |
2562 | | //tsk_deinit_lock(&yaffsfs->lock); |
2563 | 0 | tsk_fs_free(fs); |
2564 | 0 | } |
2565 | 0 | } |
2566 | | |
2567 | | typedef struct _dir_open_cb_args { |
2568 | | YAFFSFS_INFO *yfs; |
2569 | | TSK_FS_DIR *dir; |
2570 | | TSK_INUM_T parent_addr; |
2571 | | } dir_open_cb_args; |
2572 | | |
2573 | | static TSK_RETVAL_ENUM |
2574 | 0 | yaffs_dir_open_meta_cb(YaffsCacheObject * /*obj*/, YaffsCacheVersion *version, void *args) { |
2575 | 0 | dir_open_cb_args *cb_args = (dir_open_cb_args *) args; |
2576 | 0 | YaffsCacheChunk *chunk = version->ycv_header_chunk; |
2577 | 0 | TSK_INUM_T curr_inode = 0; |
2578 | 0 | uint32_t obj_id = chunk->ycc_obj_id; |
2579 | 0 | uint32_t chunk_id = chunk->ycc_chunk_id; |
2580 | 0 | uint32_t vnum = version->ycv_version; |
2581 | 0 | YaffsHeader *header = NULL; |
2582 | 0 | TSK_FS_NAME * fs_name; |
2583 | 0 | char *file_ext; |
2584 | 0 | char version_string[64]; // Allow a max of 64 bytes in the version string |
2585 | |
|
2586 | 0 | yaffscache_obj_id_and_version_to_inode(obj_id, vnum, &curr_inode); |
2587 | |
|
2588 | 0 | if (chunk_id != 0) { |
2589 | 0 | return TSK_ERR; |
2590 | 0 | } |
2591 | | |
2592 | 0 | if (tsk_verbose) |
2593 | 0 | fprintf(stderr, "dir_open_find_children_cb: %08" PRIxINUM " -> %08" PRIx32 ":%d\n", cb_args->parent_addr, obj_id, vnum); |
2594 | | |
2595 | |
|
2596 | 0 | if (yaffsfs_read_header(cb_args->yfs, &header, chunk->ycc_offset) != TSK_OK) { |
2597 | 0 | return TSK_ERR; |
2598 | 0 | } |
2599 | | |
2600 | 0 | if ((fs_name = tsk_fs_name_alloc(YAFFSFS_MAXNAMLEN + 64, 0)) == NULL) { |
2601 | 0 | free(header); |
2602 | 0 | return TSK_ERR; |
2603 | 0 | } |
2604 | | |
2605 | 0 | switch (obj_id) { |
2606 | 0 | case YAFFS_OBJECT_LOSTNFOUND: |
2607 | 0 | strncpy(fs_name->name, YAFFS_OBJECT_LOSTNFOUND_NAME, |
2608 | 0 | fs_name->name_size - 64); |
2609 | 0 | break; |
2610 | 0 | case YAFFS_OBJECT_UNLINKED: |
2611 | 0 | strncpy(fs_name->name, YAFFS_OBJECT_UNLINKED_NAME, |
2612 | 0 | fs_name->name_size - 64); |
2613 | 0 | break; |
2614 | 0 | case YAFFS_OBJECT_DELETED: |
2615 | 0 | strncpy(fs_name->name, YAFFS_OBJECT_DELETED_NAME, |
2616 | 0 | fs_name->name_size - 64); |
2617 | 0 | break; |
2618 | 0 | default: |
2619 | 0 | strncpy(fs_name->name, header->name, fs_name->name_size - 64); |
2620 | 0 | break; |
2621 | 0 | } |
2622 | 0 | fs_name->name[fs_name->name_size - 65] = 0; |
2623 | | |
2624 | | // Only put object/version string onto unallocated versions |
2625 | 0 | if (! yaffs_is_version_allocated(cb_args->yfs, curr_inode)) { |
2626 | | // Also copy the extension so that it also shows up after the version string, which allows |
2627 | | // easier searching by file extension. Max extension length is 5 characters after the dot, |
2628 | | // and require at least one character before the dot |
2629 | 0 | file_ext = strrchr(fs_name->name, '.'); |
2630 | 0 | if ((file_ext != NULL) && (file_ext != fs_name->name) && (strlen(file_ext) < 7)) { |
2631 | 0 | snprintf(version_string, 64, "#%d,%d%s", obj_id, vnum, file_ext); |
2632 | 0 | } |
2633 | 0 | else { |
2634 | 0 | snprintf(version_string, 64, "#%d,%d", obj_id, vnum); |
2635 | 0 | } |
2636 | 0 | strncat(fs_name->name, version_string, 64); |
2637 | 0 | fs_name->flags = TSK_FS_NAME_FLAG_UNALLOC; |
2638 | 0 | } |
2639 | 0 | else { |
2640 | 0 | fs_name->flags = TSK_FS_NAME_FLAG_ALLOC; |
2641 | 0 | } |
2642 | |
|
2643 | 0 | fs_name->meta_addr = curr_inode; |
2644 | |
|
2645 | 0 | switch (header->obj_type) { |
2646 | 0 | case YAFFS_TYPE_FILE: |
2647 | 0 | fs_name->type = TSK_FS_NAME_TYPE_REG; |
2648 | 0 | break; |
2649 | | |
2650 | 0 | case YAFFS_TYPE_DIRECTORY: |
2651 | 0 | fs_name->type = TSK_FS_NAME_TYPE_DIR; |
2652 | 0 | break; |
2653 | | |
2654 | 0 | case YAFFS_TYPE_SOFTLINK: |
2655 | 0 | case YAFFS_TYPE_HARDLINK: |
2656 | 0 | fs_name->type = TSK_FS_NAME_TYPE_LNK; |
2657 | 0 | break; |
2658 | | |
2659 | 0 | case YAFFS_TYPE_SPECIAL: |
2660 | 0 | fs_name->type = TSK_FS_NAME_TYPE_UNDEF; // Could be a socket |
2661 | 0 | break; |
2662 | | |
2663 | 0 | default: |
2664 | 0 | if (tsk_verbose) |
2665 | 0 | fprintf(stderr, "yaffs_dir_open_meta_cb: unhandled object type\n"); |
2666 | 0 | fs_name->type = TSK_FS_NAME_TYPE_UNDEF; |
2667 | 0 | break; |
2668 | 0 | } |
2669 | | |
2670 | 0 | free(header); |
2671 | |
|
2672 | 0 | if (tsk_fs_dir_add(cb_args->dir, fs_name)) { |
2673 | 0 | tsk_fs_name_free(fs_name); |
2674 | 0 | return TSK_ERR; |
2675 | 0 | } |
2676 | | |
2677 | | /* A copy is made in tsk_fs_dir_add, so we can free this one */ |
2678 | 0 | tsk_fs_name_free(fs_name); |
2679 | |
|
2680 | 0 | return TSK_OK; |
2681 | 0 | } |
2682 | | |
2683 | | static TSK_RETVAL_ENUM |
2684 | | yaffsfs_dir_open_meta( |
2685 | | TSK_FS_INFO *a_fs, |
2686 | | TSK_FS_DIR ** a_fs_dir, |
2687 | | TSK_INUM_T a_addr, |
2688 | | [[maybe_unused]] int recursion_depth) |
2689 | 0 | { |
2690 | 0 | TSK_FS_DIR *fs_dir; |
2691 | 0 | TSK_FS_NAME *fs_name; |
2692 | 0 | YAFFSFS_INFO *yfs = (YAFFSFS_INFO *)a_fs; |
2693 | 0 | int should_walk_children = 0; |
2694 | 0 | uint32_t obj_id; |
2695 | 0 | uint32_t ver_number; |
2696 | |
|
2697 | 0 | if (a_addr < a_fs->first_inum || a_addr > a_fs->last_inum) { |
2698 | 0 | tsk_error_reset(); |
2699 | 0 | tsk_error_set_errno(TSK_ERR_FS_WALK_RNG); |
2700 | 0 | tsk_error_set_errstr("yaffs_dir_open_meta: Invalid inode value: %" |
2701 | 0 | PRIuINUM, a_addr); |
2702 | 0 | return TSK_ERR; |
2703 | 0 | } |
2704 | 0 | else if (a_fs_dir == NULL) { |
2705 | 0 | tsk_error_reset(); |
2706 | 0 | tsk_error_set_errno(TSK_ERR_FS_ARG); |
2707 | 0 | tsk_error_set_errstr("yaffs_dir_open_meta: NULL fs_dir argument given"); |
2708 | 0 | return TSK_ERR; |
2709 | 0 | } |
2710 | | |
2711 | 0 | fs_dir = *a_fs_dir; |
2712 | |
|
2713 | 0 | if (fs_dir) { |
2714 | 0 | tsk_fs_dir_reset(fs_dir); |
2715 | 0 | fs_dir->addr = a_addr; |
2716 | 0 | } |
2717 | 0 | else if ((*a_fs_dir = fs_dir = tsk_fs_dir_alloc(a_fs, a_addr, 128)) == NULL) { |
2718 | 0 | return TSK_ERR; |
2719 | 0 | } |
2720 | | |
2721 | 0 | if (tsk_verbose) |
2722 | 0 | fprintf(stderr,"yaffs_dir_open_meta: called for directory %" PRIu32 "\n", (uint32_t) a_addr); |
2723 | | |
2724 | | // handle the orphan directory if its contents were requested |
2725 | 0 | if (a_addr == TSK_FS_ORPHANDIR_INUM(a_fs)) { |
2726 | 0 | return tsk_fs_dir_find_orphans(a_fs, fs_dir); |
2727 | 0 | } |
2728 | | |
2729 | 0 | if ((fs_name = tsk_fs_name_alloc(YAFFSFS_MAXNAMLEN, 0)) == NULL) { |
2730 | 0 | return TSK_ERR; |
2731 | 0 | } |
2732 | | |
2733 | | |
2734 | 0 | if ((fs_dir->fs_file = |
2735 | 0 | tsk_fs_file_open_meta(a_fs, NULL, a_addr)) == NULL) { |
2736 | 0 | tsk_error_errstr2_concat(" - yaffs_dir_open_meta"); |
2737 | 0 | tsk_fs_name_free(fs_name); |
2738 | 0 | return TSK_ERR; |
2739 | 0 | } |
2740 | | |
2741 | | // extract obj_id and ver_number from inum |
2742 | 0 | yaffscache_inode_to_obj_id_and_version(a_addr, &obj_id, &ver_number); |
2743 | | |
2744 | | // Decide if we should walk the directory structure |
2745 | 0 | if (obj_id == YAFFS_OBJECT_DELETED || |
2746 | 0 | obj_id == YAFFS_OBJECT_UNLINKED) { |
2747 | 0 | should_walk_children = 1; |
2748 | 0 | } |
2749 | 0 | else { |
2750 | 0 | YaffsCacheObject *obj; |
2751 | 0 | YaffsCacheVersion *versionFound; |
2752 | 0 | TSK_RETVAL_ENUM result = yaffscache_version_find_by_inode(yfs, a_addr, &versionFound, &obj); |
2753 | 0 | if (result != TSK_OK) { |
2754 | 0 | if (tsk_verbose) |
2755 | 0 | tsk_fprintf(stderr, "yaffsfs_dir_open_meta: yaffscache_version_find_by_inode failed! (inode: %d\n", a_addr); |
2756 | 0 | tsk_fs_name_free(fs_name); |
2757 | 0 | return TSK_ERR; |
2758 | 0 | } |
2759 | | |
2760 | | /* Only attach files onto the latest version of the directory */ |
2761 | 0 | should_walk_children = (obj->yco_latest == versionFound); |
2762 | 0 | } |
2763 | | |
2764 | | // Search the cache for the children of this object and add them to fs_dir |
2765 | 0 | if (should_walk_children) { |
2766 | 0 | dir_open_cb_args args; |
2767 | 0 | args.yfs = yfs; |
2768 | 0 | args.dir = fs_dir; |
2769 | 0 | args.parent_addr = a_addr; |
2770 | 0 | yaffscache_find_children(yfs, a_addr, yaffs_dir_open_meta_cb, &args); |
2771 | 0 | } |
2772 | | |
2773 | | // add special entries to root directory |
2774 | 0 | if (obj_id == YAFFS_OBJECT_ROOT) { |
2775 | 0 | strncpy(fs_name->name, YAFFS_OBJECT_UNLINKED_NAME, fs_name->name_size); |
2776 | 0 | fs_name->meta_addr = YAFFS_OBJECT_UNLINKED; |
2777 | 0 | fs_name->type = TSK_FS_NAME_TYPE_DIR; |
2778 | 0 | fs_name->flags = TSK_FS_NAME_FLAG_ALLOC; |
2779 | 0 | if (tsk_fs_dir_add(fs_dir, fs_name)) { |
2780 | 0 | tsk_fs_name_free(fs_name); |
2781 | 0 | return TSK_ERR; |
2782 | 0 | } |
2783 | | |
2784 | 0 | strncpy(fs_name->name, YAFFS_OBJECT_DELETED_NAME, fs_name->name_size); |
2785 | 0 | fs_name->meta_addr = YAFFS_OBJECT_DELETED; |
2786 | 0 | fs_name->type = TSK_FS_NAME_TYPE_DIR; |
2787 | 0 | fs_name->flags = TSK_FS_NAME_FLAG_ALLOC; |
2788 | 0 | if (tsk_fs_dir_add(fs_dir, fs_name)) { |
2789 | 0 | tsk_fs_name_free(fs_name); |
2790 | 0 | return TSK_ERR; |
2791 | 0 | } |
2792 | | |
2793 | | // orphan directory |
2794 | 0 | if (tsk_fs_dir_make_orphan_dir_name(a_fs, fs_name)) { |
2795 | 0 | tsk_fs_name_free(fs_name); |
2796 | 0 | return TSK_ERR; |
2797 | 0 | } |
2798 | 0 | fs_name->meta_addr = yfs->fs_info.last_inum; |
2799 | 0 | fs_name->type = TSK_FS_NAME_TYPE_DIR; |
2800 | 0 | fs_name->flags = TSK_FS_NAME_FLAG_ALLOC; |
2801 | 0 | if (tsk_fs_dir_add(fs_dir, fs_name)) { |
2802 | 0 | tsk_fs_name_free(fs_name); |
2803 | 0 | return TSK_ERR; |
2804 | 0 | } |
2805 | 0 | } |
2806 | | |
2807 | 0 | tsk_fs_name_free(fs_name); |
2808 | 0 | return TSK_OK; |
2809 | 0 | } |
2810 | | |
2811 | | static TSK_FS_ATTR_TYPE_ENUM |
2812 | | yaffsfs_get_default_attr_type(const TSK_FS_FILE * /*a_file*/) |
2813 | 0 | { |
2814 | 0 | return TSK_FS_ATTR_TYPE_DEFAULT; |
2815 | 0 | } |
2816 | | |
2817 | | static uint8_t |
2818 | | yaffsfs_load_attrs(TSK_FS_FILE *file) |
2819 | 0 | { |
2820 | 0 | TSK_FS_ATTR *attr; |
2821 | 0 | TSK_FS_META *meta; |
2822 | 0 | TSK_FS_INFO *fs; |
2823 | 0 | YAFFSFS_INFO *yfs; |
2824 | 0 | TSK_FS_ATTR_RUN *data_run; |
2825 | 0 | TSK_DADDR_T file_block_count; |
2826 | 0 | YaffsCacheObject *obj; |
2827 | 0 | YaffsCacheVersion *version; |
2828 | 0 | TSK_RETVAL_ENUM result; |
2829 | 0 | TSK_LIST *chunks_seen = NULL; |
2830 | 0 | YaffsCacheChunk *curr; |
2831 | 0 | TSK_FS_ATTR_RUN *data_run_new; |
2832 | | |
2833 | |
|
2834 | 0 | if (file == NULL || file->meta == NULL || file->fs_info == NULL) |
2835 | 0 | { |
2836 | 0 | tsk_error_set_errno(TSK_ERR_FS_ARG); |
2837 | 0 | tsk_error_set_errstr |
2838 | 0 | ("yaffsfs_load_attrs: called with NULL pointers"); |
2839 | 0 | return 1; |
2840 | 0 | } |
2841 | | |
2842 | 0 | meta = file->meta; |
2843 | 0 | yfs = (YAFFSFS_INFO *)file->fs_info; |
2844 | 0 | fs = &yfs->fs_info; |
2845 | | |
2846 | | // see if we have already loaded the runs |
2847 | 0 | if ((meta->attr != NULL) |
2848 | 0 | && (meta->attr_state == TSK_FS_META_ATTR_STUDIED)) { |
2849 | 0 | return 0; |
2850 | 0 | } |
2851 | 0 | else if (meta->attr_state == TSK_FS_META_ATTR_ERROR) { |
2852 | 0 | return 1; |
2853 | 0 | } |
2854 | | // not sure why this would ever happen, but... |
2855 | 0 | else if (meta->attr != NULL) { |
2856 | 0 | tsk_fs_attrlist_markunused(meta->attr); |
2857 | 0 | } |
2858 | 0 | else if (meta->attr == NULL) { |
2859 | 0 | meta->attr = tsk_fs_attrlist_alloc(); |
2860 | 0 | } |
2861 | | |
2862 | 0 | attr = tsk_fs_attrlist_getnew(meta->attr, TSK_FS_ATTR_NONRES); |
2863 | 0 | if (attr == NULL) { |
2864 | 0 | meta->attr_state = TSK_FS_META_ATTR_ERROR; |
2865 | 0 | return 1; |
2866 | 0 | } |
2867 | | |
2868 | 0 | if (meta->size == 0) { |
2869 | 0 | data_run = NULL; |
2870 | 0 | } |
2871 | 0 | else { |
2872 | | /* BC: I'm not entirely sure this is needed. My guess is that |
2873 | | * this was done instead of maintaining the head of the list of |
2874 | | * runs. In theory, the tsk_fs_attr_add_run() method should handle |
2875 | | * the fillers. */ |
2876 | 0 | data_run = tsk_fs_attr_run_alloc(); |
2877 | 0 | if (data_run == NULL) { |
2878 | 0 | tsk_fs_attr_run_free(data_run); |
2879 | 0 | meta->attr_state = TSK_FS_META_ATTR_ERROR; |
2880 | 0 | return 1; |
2881 | 0 | } |
2882 | | |
2883 | 0 | data_run->offset = 0; |
2884 | 0 | data_run->addr = 0; |
2885 | 0 | data_run->len = (meta->size + fs->block_size - 1) / fs->block_size; |
2886 | 0 | data_run->flags = TSK_FS_ATTR_RUN_FLAG_FILLER; |
2887 | 0 | } |
2888 | | |
2889 | | |
2890 | | // initialize the data run |
2891 | 0 | if (tsk_fs_attr_set_run(file, attr, data_run, NULL, |
2892 | 0 | TSK_FS_ATTR_TYPE_DEFAULT, TSK_FS_ATTR_ID_DEFAULT, |
2893 | 0 | meta->size, meta->size, roundup(meta->size, fs->block_size), (TSK_FS_ATTR_FLAG_ENUM)0, 0)) { |
2894 | 0 | meta->attr_state = TSK_FS_META_ATTR_ERROR; |
2895 | 0 | return 1; |
2896 | 0 | } |
2897 | | |
2898 | | // If the file has size zero, return now |
2899 | 0 | if (meta->size == 0) { |
2900 | 0 | meta->attr_state = TSK_FS_META_ATTR_STUDIED; |
2901 | 0 | return 0; |
2902 | 0 | } |
2903 | | |
2904 | | |
2905 | | /* Get the version for the given object. */ |
2906 | 0 | result = yaffscache_version_find_by_inode(yfs, meta->addr, &version, &obj); |
2907 | 0 | if (result != TSK_OK || version == NULL) { |
2908 | 0 | if (tsk_verbose) |
2909 | 0 | tsk_fprintf(stderr, "yaffsfs_load_attrs: yaffscache_version_find_by_inode failed!\n"); |
2910 | 0 | meta->attr_state = TSK_FS_META_ATTR_ERROR; |
2911 | 0 | return 1; |
2912 | 0 | } |
2913 | | |
2914 | 0 | if (tsk_verbose) |
2915 | 0 | yaffscache_object_dump(stderr, obj); |
2916 | |
|
2917 | 0 | file_block_count = data_run->len; |
2918 | | /* Cycle through the chunks for this version of this object */ |
2919 | 0 | curr = version->ycv_last_chunk; |
2920 | 0 | while (curr != NULL && curr->ycc_obj_id == obj->yco_obj_id) { |
2921 | |
|
2922 | 0 | if (curr->ycc_chunk_id == 0) { |
2923 | 0 | if (tsk_verbose) |
2924 | 0 | tsk_fprintf(stderr, "yaffsfs_load_attrs: skipping header chunk\n"); |
2925 | 0 | } |
2926 | 0 | else if (tsk_list_find(chunks_seen, curr->ycc_chunk_id)) { |
2927 | 0 | if (tsk_verbose) |
2928 | 0 | tsk_fprintf(stderr, "yaffsfs_load_attrs: skipping duplicate chunk\n"); |
2929 | 0 | } |
2930 | 0 | else if (curr->ycc_chunk_id > file_block_count) { |
2931 | 0 | if (tsk_verbose) |
2932 | 0 | tsk_fprintf(stderr, "yaffsfs_load_attrs: skipping chunk past end\n"); |
2933 | 0 | } |
2934 | | /* We like this chunk */ |
2935 | 0 | else { |
2936 | | // add it to our internal list |
2937 | 0 | if (tsk_list_add(&chunks_seen, curr->ycc_chunk_id)) { |
2938 | 0 | meta->attr_state = TSK_FS_META_ATTR_ERROR; |
2939 | 0 | tsk_list_free(chunks_seen); |
2940 | 0 | chunks_seen = NULL; |
2941 | 0 | return 1; |
2942 | 0 | } |
2943 | | |
2944 | 0 | data_run_new = tsk_fs_attr_run_alloc(); |
2945 | 0 | if (data_run_new == NULL) { |
2946 | 0 | tsk_fs_attr_run_free(data_run_new); |
2947 | 0 | meta->attr_state = TSK_FS_META_ATTR_ERROR; |
2948 | 0 | return 1; |
2949 | 0 | } |
2950 | | |
2951 | 0 | data_run_new->offset = (curr->ycc_chunk_id - 1); |
2952 | 0 | data_run_new->addr = curr->ycc_offset / (fs->block_pre_size + fs->block_size + fs->block_post_size); |
2953 | 0 | data_run_new->len = 1; |
2954 | 0 | data_run_new->flags = TSK_FS_ATTR_RUN_FLAG_NONE; |
2955 | |
|
2956 | 0 | if (tsk_verbose) |
2957 | 0 | tsk_fprintf(stderr, "yaffsfs_load_attrs: @@@ Chunk %d : %08x is at offset 0x%016llx\n", |
2958 | 0 | curr->ycc_chunk_id, curr->ycc_seq_number, curr->ycc_offset); |
2959 | |
|
2960 | 0 | tsk_fs_attr_add_run(fs, attr, data_run_new); |
2961 | 0 | } |
2962 | | |
2963 | 0 | curr = curr->ycc_prev; |
2964 | 0 | } |
2965 | | |
2966 | 0 | tsk_list_free(chunks_seen); |
2967 | 0 | meta->attr_state = TSK_FS_META_ATTR_STUDIED; |
2968 | 0 | return 0; |
2969 | 0 | } |
2970 | | |
2971 | | static uint8_t |
2972 | | yaffsfs_jentry_walk(TSK_FS_INFO * /*info*/, int /*entry*/, |
2973 | | TSK_FS_JENTRY_WALK_CB /*cb*/, void * /*fn*/) |
2974 | 0 | { |
2975 | 0 | tsk_error_reset(); |
2976 | 0 | tsk_error_set_errno(TSK_ERR_FS_UNSUPFUNC); |
2977 | 0 | tsk_error_set_errstr("Journal support for YAFFS is not implemented"); |
2978 | 0 | return 1; |
2979 | 0 | } |
2980 | | |
2981 | | static uint8_t |
2982 | | yaffsfs_jblk_walk(TSK_FS_INFO * /*info*/, TSK_DADDR_T /*daddr*/, |
2983 | | TSK_DADDR_T /*daddrt*/, int /*entry*/, TSK_FS_JBLK_WALK_CB /*cb*/, |
2984 | | void * /*fn*/) |
2985 | 0 | { |
2986 | 0 | tsk_error_reset(); |
2987 | 0 | tsk_error_set_errno(TSK_ERR_FS_UNSUPFUNC); |
2988 | 0 | tsk_error_set_errstr("Journal support for YAFFS is not implemented"); |
2989 | 0 | return 1; |
2990 | 0 | } |
2991 | | |
2992 | | static uint8_t |
2993 | | yaffsfs_jopen(TSK_FS_INFO * /*info*/, TSK_INUM_T /*inum*/) |
2994 | 0 | { |
2995 | 0 | tsk_error_reset(); |
2996 | 0 | tsk_error_set_errno(TSK_ERR_FS_UNSUPFUNC); |
2997 | 0 | tsk_error_set_errstr("Journal support for YAFFS is not implemented"); |
2998 | 0 | return 1; |
2999 | 0 | } |
3000 | | |
3001 | | /** |
3002 | | * \internal |
3003 | | * Open part of a disk image as a Yaffs/2 file system. |
3004 | | * |
3005 | | * @param img_info Disk image to analyze |
3006 | | * @param offset Byte offset where file system starts |
3007 | | * @param ftype Specific type of file system |
3008 | | * @param a_pass NOT USED |
3009 | | * @param test Going to use this - 1 if we're doing auto-detect, 0 if not (display more verbose messages if the user specified YAFFS2) |
3010 | | * @returns NULL on error or if data is not an Yaffs/3 file system |
3011 | | */ |
3012 | | TSK_FS_INFO * |
3013 | | yaffs2_open( |
3014 | | TSK_IMG_INFO * img_info, |
3015 | | TSK_OFF_T offset, |
3016 | | TSK_FS_TYPE_ENUM ftype, |
3017 | | [[maybe_unused]] const char* a_pass, |
3018 | | uint8_t test) |
3019 | 0 | { |
3020 | 0 | const unsigned int psize = img_info->page_size; |
3021 | 0 | const unsigned int ssize = img_info->spare_size; |
3022 | 0 | YaffsHeader * first_header = NULL; |
3023 | 0 | std::map<std::string, std::string> configParams; |
3024 | 0 | YAFFS_CONFIG_STATUS config_file_status; |
3025 | | |
3026 | | // clean up any error messages that are lying around |
3027 | 0 | tsk_error_reset(); |
3028 | |
|
3029 | 0 | if (TSK_FS_TYPE_ISYAFFS2(ftype) == 0) { |
3030 | 0 | tsk_error_reset(); |
3031 | 0 | tsk_error_set_errno(TSK_ERR_FS_ARG); |
3032 | 0 | tsk_error_set_errstr("Invalid FS Type in yaffsfs_open"); |
3033 | 0 | return nullptr; |
3034 | 0 | } |
3035 | | |
3036 | 0 | if (img_info->sector_size == 0) { |
3037 | 0 | tsk_error_reset(); |
3038 | 0 | tsk_error_set_errno(TSK_ERR_FS_ARG); |
3039 | 0 | tsk_error_set_errstr("yaffs2_open: sector size is 0"); |
3040 | 0 | return nullptr; |
3041 | 0 | } |
3042 | | |
3043 | 0 | const auto deleter = [](YAFFSFS_INFO* yaffsfs) { |
3044 | 0 | yaffsfs_close(&yaffsfs->fs_info); |
3045 | 0 | }; |
3046 | |
|
3047 | 0 | std::unique_ptr<YAFFSFS_INFO, decltype(deleter)> yaffsfs{ |
3048 | 0 | (YAFFSFS_INFO *) tsk_fs_malloc(sizeof(YAFFSFS_INFO)), |
3049 | 0 | deleter |
3050 | 0 | }; |
3051 | |
|
3052 | 0 | if (!yaffsfs) { |
3053 | 0 | return nullptr; |
3054 | 0 | } |
3055 | | |
3056 | 0 | yaffsfs->cache_objects = NULL; |
3057 | 0 | yaffsfs->chunkMap = NULL; |
3058 | |
|
3059 | 0 | TSK_FS_INFO* fs = &(yaffsfs->fs_info); |
3060 | |
|
3061 | 0 | fs->tag = TSK_FS_INFO_TAG; |
3062 | 0 | fs->ftype = ftype; |
3063 | 0 | fs->flags = (TSK_FS_INFO_FLAG_ENUM)0; |
3064 | 0 | fs->img_info = img_info; |
3065 | 0 | fs->offset = offset; |
3066 | 0 | fs->endian = TSK_LIT_ENDIAN; |
3067 | | |
3068 | | // Read config file (if it exists) |
3069 | 0 | config_file_status = yaffs_load_config_file(img_info, configParams); |
3070 | | // BL-6929(JTS): When using external readers, this call will fail. |
3071 | | // Not having a config should not be a fatal error. |
3072 | | /*if (config_file_status == YAFFS_CONFIG_ERROR) { |
3073 | | // tsk_error was set by yaffs_load_config |
3074 | | goto on_error; |
3075 | | } |
3076 | 0 | else*/ if (config_file_status == YAFFS_CONFIG_OK) { |
3077 | | // Validate the input |
3078 | | // If it fails validation, return (tsk_error will be set up already) |
3079 | 0 | if (1 == yaffs_validate_config_file(configParams)) { |
3080 | 0 | return nullptr; |
3081 | 0 | } |
3082 | 0 | } |
3083 | | |
3084 | | // If we read these fields from the config file, use those values. Otherwise use the defaults |
3085 | 0 | if (configParams.find(YAFFS_CONFIG_PAGE_SIZE_STR) != configParams.end()) { |
3086 | 0 | yaffsfs->page_size = atoi(configParams[YAFFS_CONFIG_PAGE_SIZE_STR].c_str()); |
3087 | 0 | } |
3088 | 0 | else { |
3089 | 0 | yaffsfs->page_size = psize == 0 ? YAFFS_DEFAULT_PAGE_SIZE : psize; |
3090 | 0 | } |
3091 | |
|
3092 | 0 | if (configParams.find(YAFFS_CONFIG_SPARE_SIZE_STR) != configParams.end()) { |
3093 | 0 | yaffsfs->spare_size = atoi(configParams[YAFFS_CONFIG_SPARE_SIZE_STR].c_str()); |
3094 | 0 | } |
3095 | 0 | else { |
3096 | 0 | yaffsfs->spare_size = ssize == 0 ? YAFFS_DEFAULT_SPARE_SIZE : ssize; |
3097 | 0 | } |
3098 | |
|
3099 | 0 | if (configParams.find(YAFFS_CONFIG_CHUNKS_PER_BLOCK_STR) != configParams.end()) { |
3100 | 0 | yaffsfs->chunks_per_block = atoi(configParams[YAFFS_CONFIG_CHUNKS_PER_BLOCK_STR].c_str()); |
3101 | 0 | } |
3102 | 0 | else { |
3103 | 0 | yaffsfs->chunks_per_block = 64; |
3104 | 0 | } |
3105 | | |
3106 | | // TODO: Why are 2 different memory allocation methods used in the same code? |
3107 | | // This makes things unnecessary complex. |
3108 | 0 | yaffsfs->max_obj_id = 1; |
3109 | 0 | yaffsfs->max_version = 0; |
3110 | | |
3111 | | // Keep track of whether we're doing auto-detection of the file system |
3112 | 0 | if (test) { |
3113 | 0 | yaffsfs->autoDetect = 1; |
3114 | 0 | } |
3115 | 0 | else { |
3116 | 0 | yaffsfs->autoDetect = 0; |
3117 | 0 | } |
3118 | | |
3119 | | // Determine the layout of the spare area |
3120 | | // If it was specified in the config file, use those values. Otherwise do the auto-detection |
3121 | 0 | if (configParams.find(YAFFS_CONFIG_SEQ_NUM_STR) != configParams.end()) { |
3122 | | // In the validation step, we ensured that if one of the offsets was set, we have all of them |
3123 | 0 | yaffsfs->spare_seq_offset = atoi(configParams[YAFFS_CONFIG_SEQ_NUM_STR].c_str()); |
3124 | 0 | yaffsfs->spare_obj_id_offset = atoi(configParams[YAFFS_CONFIG_OBJ_ID_STR].c_str()); |
3125 | 0 | yaffsfs->spare_chunk_id_offset = atoi(configParams[YAFFS_CONFIG_CHUNK_ID_STR].c_str()); |
3126 | | |
3127 | | // Check that the offsets are valid for the given spare area size (fields are 4 bytes long) |
3128 | 0 | if ((yaffsfs->spare_seq_offset + 4 > yaffsfs->spare_size) || |
3129 | 0 | (yaffsfs->spare_obj_id_offset + 4 > yaffsfs->spare_size) || |
3130 | 0 | (yaffsfs->spare_chunk_id_offset + 4 > yaffsfs->spare_size)) { |
3131 | 0 | tsk_error_reset(); |
3132 | 0 | tsk_error_set_errno(TSK_ERR_FS); |
3133 | 0 | tsk_error_set_errstr("yaffs2_open: Offset(s) in config file too large for spare area (size %d). %s", yaffsfs->spare_size, YAFFS_HELP_MESSAGE); |
3134 | 0 | return nullptr; |
3135 | 0 | } |
3136 | | |
3137 | | |
3138 | | // nBytes isn't currently used, so just set to zero |
3139 | 0 | yaffsfs->spare_nbytes_offset = 0; |
3140 | 0 | } |
3141 | 0 | else { |
3142 | | // Decide how many blocks to test. If we're not doing auto-detection, set to zero (no limit) |
3143 | 0 | unsigned int maxBlocksToTest; |
3144 | 0 | if (yaffsfs->autoDetect) { |
3145 | 0 | maxBlocksToTest = YAFFS_DEFAULT_MAX_TEST_BLOCKS; |
3146 | 0 | } |
3147 | 0 | else { |
3148 | 0 | maxBlocksToTest = 0; |
3149 | 0 | } |
3150 | |
|
3151 | 0 | if (yaffs_initialize_spare_format(yaffsfs.get(), maxBlocksToTest) != TSK_OK) { |
3152 | 0 | tsk_error_reset(); |
3153 | 0 | tsk_error_set_errno(TSK_ERR_FS_MAGIC); |
3154 | 0 | tsk_error_set_errstr("not a YAFFS file system (bad spare format). %s", YAFFS_HELP_MESSAGE); |
3155 | 0 | if (tsk_verbose) |
3156 | 0 | fprintf(stderr, "yaffsfs_open: could not find valid spare area format\n%s\n", YAFFS_HELP_MESSAGE); |
3157 | 0 | return nullptr; |
3158 | 0 | } |
3159 | 0 | } |
3160 | | |
3161 | | /* |
3162 | | * Read the first record, make sure it's a valid header... |
3163 | | * |
3164 | | * Used for verification and autodetection of |
3165 | | * the FS type. |
3166 | | */ |
3167 | 0 | if (yaffsfs_read_header(yaffsfs.get(), &first_header, 0)) { |
3168 | 0 | tsk_error_reset(); |
3169 | 0 | tsk_error_set_errno(TSK_ERR_FS_MAGIC); |
3170 | 0 | tsk_error_set_errstr("not a YAFFS file system (first record). %s", YAFFS_HELP_MESSAGE); |
3171 | 0 | if (tsk_verbose) |
3172 | 0 | fprintf(stderr, "yaffsfs_open: invalid first record\n%s\n", YAFFS_HELP_MESSAGE); |
3173 | 0 | return nullptr; |
3174 | 0 | } |
3175 | 0 | free(first_header); |
3176 | 0 | first_header = NULL; |
3177 | |
|
3178 | 0 | fs->duname = "Chunk"; |
3179 | | |
3180 | | /* |
3181 | | * Calculate the meta data info |
3182 | | */ |
3183 | | //fs->last_inum = 0xffffffff; // Will update this as we go |
3184 | 0 | fs->last_inum = 0; |
3185 | 0 | fs->root_inum = YAFFS_OBJECT_ROOT; |
3186 | 0 | fs->first_inum = YAFFS_OBJECT_FIRST; |
3187 | | //fs->inum_count = fs->last_inum; // For now this will be the last_inum - 1 (after we calculate it) |
3188 | | |
3189 | | /* |
3190 | | * Calculate the block info |
3191 | | */ |
3192 | 0 | fs->dev_bsize = img_info->sector_size; |
3193 | 0 | fs->block_size = yaffsfs->page_size; |
3194 | 0 | fs->block_pre_size = 0; |
3195 | 0 | fs->block_post_size = yaffsfs->spare_size; |
3196 | 0 | fs->block_count = img_info->size / (fs->block_pre_size + fs->block_size + fs->block_post_size); |
3197 | 0 | fs->first_block = 0; |
3198 | 0 | fs->last_block_act = fs->last_block = fs->block_count ? fs->block_count - 1 : 0; |
3199 | | |
3200 | | /* Set the generic function pointers */ |
3201 | 0 | fs->inode_walk = yaffsfs_inode_walk; |
3202 | 0 | fs->block_walk = yaffsfs_block_walk; |
3203 | 0 | fs->block_getflags = yaffsfs_block_getflags; |
3204 | |
|
3205 | 0 | fs->get_default_attr_type = yaffsfs_get_default_attr_type; |
3206 | 0 | fs->load_attrs = yaffsfs_load_attrs; |
3207 | |
|
3208 | 0 | fs->file_add_meta = yaffs_inode_lookup; |
3209 | 0 | fs->dir_open_meta = yaffsfs_dir_open_meta; |
3210 | 0 | fs->fsstat = yaffsfs_fsstat; |
3211 | 0 | fs->fscheck = yaffsfs_fscheck; |
3212 | 0 | fs->istat = yaffsfs_istat; |
3213 | 0 | fs->name_cmp = tsk_fs_unix_name_cmp; |
3214 | |
|
3215 | 0 | fs->close = yaffsfs_close; |
3216 | | |
3217 | | /* Journal */ |
3218 | 0 | fs->jblk_walk = yaffsfs_jblk_walk; |
3219 | 0 | fs->jentry_walk = yaffsfs_jentry_walk; |
3220 | 0 | fs->jopen = yaffsfs_jopen; |
3221 | | |
3222 | | /* Initialize the caches */ |
3223 | 0 | if (tsk_verbose) |
3224 | 0 | fprintf(stderr, "yaffsfs_open: building cache...\n"); |
3225 | | |
3226 | | /* Build cache */ |
3227 | | /* NOTE: The only modifications to the cache happen here, during at |
3228 | | * the open. Should be fine with no lock, even if access to the |
3229 | | * cache is shared among threads. |
3230 | | */ |
3231 | | //tsk_init_lock(&yaffsfs->lock); |
3232 | 0 | yaffsfs->chunkMap = new std::map<uint32_t, YaffsCacheChunkGroup>; |
3233 | 0 | if (TSK_OK != yaffsfs_parse_image_load_cache(yaffsfs.get())) { |
3234 | 0 | return nullptr; |
3235 | 0 | } |
3236 | | |
3237 | 0 | if (tsk_verbose) { |
3238 | 0 | fprintf(stderr, "yaffsfs_open: done building cache!\n"); |
3239 | | //yaffscache_objects_dump(yaffsfs, stderr); |
3240 | 0 | } |
3241 | | |
3242 | | // Update the number of inums now that we've read in the file system |
3243 | 0 | fs->inum_count = fs->last_inum - 1; |
3244 | |
|
3245 | 0 | std::unique_ptr<TSK_FS_DIR, decltype(&tsk_fs_dir_close)> test_dir{ |
3246 | 0 | tsk_fs_dir_open_meta(fs, fs->root_inum), |
3247 | 0 | tsk_fs_dir_close |
3248 | 0 | }; |
3249 | |
|
3250 | 0 | if (!test_dir) { |
3251 | 0 | tsk_error_reset(); |
3252 | 0 | tsk_error_set_errno(TSK_ERR_FS_MAGIC); |
3253 | 0 | tsk_error_set_errstr("not a YAFFS file system (no root directory). %s", YAFFS_HELP_MESSAGE); |
3254 | 0 | if (tsk_verbose) |
3255 | 0 | fprintf(stderr, "yaffsfs_open: invalid file system\n%s\n", YAFFS_HELP_MESSAGE); |
3256 | 0 | return nullptr; |
3257 | 0 | } |
3258 | | |
3259 | 0 | return fs; |
3260 | 0 | } |