/src/sleuthkit/tsk/fs/logical_fs.cpp
Line | Count | Source (jump to first uncovered line) |
1 | | /* |
2 | | ** The Sleuth Kit |
3 | | ** |
4 | | ** Copyright (c) 2022 Basis Technology Corp. All rights reserved |
5 | | ** Contact: Brian Carrier [carrier <at> sleuthkit [dot] org] |
6 | | ** |
7 | | ** This software is distributed under the Common Public License 1.0 |
8 | | ** |
9 | | */ |
10 | | |
11 | | /** |
12 | | *\file logical_fs.cpp |
13 | | * Contains the internal TSK logical file system functions. |
14 | | */ |
15 | | |
16 | | #include <algorithm> |
17 | | #include <map> |
18 | | #include <memory> |
19 | | #include <set> |
20 | | #include <string> |
21 | | #include <vector> |
22 | | |
23 | | #include <string.h> |
24 | | #include <cwctype> |
25 | | |
26 | | #include "tsk_fs_i.h" |
27 | | #include "tsk_fs.h" |
28 | | #include "tsk_logical_fs.h" |
29 | | #include "tsk/img/legacy_cache.h" |
30 | | #include "tsk/img/logical_img.h" |
31 | | |
32 | | #ifdef TSK_WIN32 |
33 | | #include <windows.h> |
34 | | #endif |
35 | | |
36 | | using std::vector; |
37 | | using std::string; |
38 | | using std::wstring; |
39 | | |
40 | | static uint8_t |
41 | | logicalfs_inode_walk( |
42 | | [[maybe_unused]] TSK_FS_INFO *fs, |
43 | | [[maybe_unused]] TSK_INUM_T start_inum, |
44 | | [[maybe_unused]] TSK_INUM_T end_inum, |
45 | | [[maybe_unused]] TSK_FS_META_FLAG_ENUM flags, |
46 | | [[maybe_unused]] TSK_FS_META_WALK_CB a_action, |
47 | | [[maybe_unused]] void *a_ptr) |
48 | 0 | { |
49 | 0 | tsk_error_reset(); |
50 | 0 | tsk_error_set_errno(TSK_ERR_FS_UNSUPFUNC); |
51 | 0 | tsk_error_set_errstr("block_walk for logical directory is not implemented"); |
52 | 0 | return 1; |
53 | 0 | } |
54 | | |
55 | | static uint8_t |
56 | | logicalfs_block_walk( |
57 | | [[maybe_unused]] TSK_FS_INFO *a_fs, |
58 | | [[maybe_unused]] TSK_DADDR_T a_start_blk, |
59 | | [[maybe_unused]] TSK_DADDR_T a_end_blk, |
60 | | [[maybe_unused]] TSK_FS_BLOCK_WALK_FLAG_ENUM a_flags, |
61 | | [[maybe_unused]] TSK_FS_BLOCK_WALK_CB a_action, |
62 | | [[maybe_unused]] void *a_ptr) |
63 | 0 | { |
64 | 0 | tsk_error_reset(); |
65 | 0 | tsk_error_set_errno(TSK_ERR_FS_UNSUPFUNC); |
66 | 0 | tsk_error_set_errstr("block_walk for logical directory is not implemented"); |
67 | 0 | return 1; |
68 | 0 | } |
69 | | |
70 | | static TSK_FS_BLOCK_FLAG_ENUM |
71 | | logicalfs_block_getflags( |
72 | | [[maybe_unused]] TSK_FS_INFO *fs, |
73 | | [[maybe_unused]] TSK_DADDR_T a_addr) |
74 | 0 | { |
75 | 0 | return TSK_FS_BLOCK_FLAG_UNUSED; |
76 | 0 | } |
77 | | |
78 | | static TSK_FS_ATTR_TYPE_ENUM |
79 | | logicalfs_get_default_attr_type([[maybe_unused]] const TSK_FS_FILE * a_file) |
80 | 0 | { |
81 | 0 | return TSK_FS_ATTR_TYPE_DEFAULT; |
82 | 0 | } |
83 | | |
84 | | /* |
85 | | * Convert a FILETIME to a timet |
86 | | * |
87 | | * @param ft The FILETIME to convert |
88 | | * |
89 | | * @return The converted timet |
90 | | */ |
91 | | /* |
92 | | #ifdef TSK_WIN32 |
93 | | static time_t |
94 | | filetime_to_timet(FILETIME const& ft) |
95 | | { |
96 | | ULARGE_INTEGER ull; |
97 | | ull.LowPart = ft.dwLowDateTime; |
98 | | ull.HighPart = ft.dwHighDateTime; |
99 | | return ull.QuadPart / 10000000ULL - 11644473600ULL; |
100 | | } |
101 | | #endif |
102 | | */ |
103 | | |
104 | | /** |
105 | | * Check if the given path contains the folder separator |
106 | | * |
107 | | * @param path The path to test |
108 | | * |
109 | | * @return true if path contains folder separator, false otherwise |
110 | | */ |
111 | | static bool |
112 | 0 | contains_folder_separator(const TSK_TCHAR* path) { |
113 | 0 | if (path == NULL) { |
114 | 0 | return false; |
115 | 0 | } |
116 | 0 |
|
117 | 0 | #ifdef TSK_WIN32 |
118 | 0 | TSK_TCHAR slash = '\\'; |
119 | 0 | #else |
120 | 0 | TSK_TCHAR slash = '/'; |
121 | 0 | #endif |
122 | 0 | return TSTRCHR(path, slash) != NULL; |
123 | 0 | } |
124 | | |
125 | | /** |
126 | | * Test whether child_path is a subfolder under parent_path. |
127 | | * |
128 | | * @param parent_path Parent path |
129 | | * @param child_path Child path |
130 | | * |
131 | | * @return true if child_path is a subfolder of parent_path |
132 | | */ |
133 | | static bool |
134 | 0 | path_is_subfolder(const TSK_TCHAR* parent_path, const TSK_TCHAR* child_path) { |
135 | 0 |
|
136 | 0 | if (parent_path == NULL || child_path == NULL) { |
137 | 0 | return false; |
138 | 0 | } |
139 | 0 |
|
140 | 0 | size_t parent_path_len = TSTRLEN(parent_path); |
141 | 0 | if (parent_path_len + 1 >= TSTRLEN(child_path)) { |
142 | 0 | return false; |
143 | 0 | } |
144 | 0 |
|
145 | 0 | #ifdef TSK_WIN32 |
146 | 0 | if (0 != _wcsnicmp(parent_path, child_path, parent_path_len)) { |
147 | 0 | return false; |
148 | 0 | } |
149 | 0 | #endif |
150 | 0 |
|
151 | 0 | // Make sure child_path is (parent_path)/(rest) |
152 | 0 | #ifdef TSK_WIN32 |
153 | 0 | TSK_TCHAR slash = '\\'; |
154 | 0 | #else |
155 | 0 | TSK_TCHAR slash = '/'; |
156 | 0 | #endif |
157 | 0 | return child_path[parent_path_len] == slash; |
158 | 0 | } |
159 | | |
160 | | /** |
161 | | * Return a pointer to full_path starting after the path_start string. |
162 | | * Assumes full_path starts with path_start and a slash. |
163 | | * |
164 | | * Ex: |
165 | | * full_path: dir1/dir2/dir3/dir4 |
166 | | * path_start: dir1/dir2 |
167 | | * Returns: dir3/dir4 |
168 | | * |
169 | | * @param full_path The full path |
170 | | * @param path_start The parent path that should be removed (should not include trailing slash) |
171 | | * |
172 | | * @return Pointer to remaining string, NULL on error |
173 | | */ |
174 | | static const TSK_TCHAR* |
175 | 0 | get_end_of_path(const TSK_TCHAR* full_path, const TSK_TCHAR* path_start) { |
176 | 0 | if (full_path == NULL || path_start == NULL) { |
177 | 0 | return NULL; |
178 | 0 | } |
179 | 0 |
|
180 | 0 | if (TSTRLEN(path_start) + 1 >= TSTRLEN(full_path)) { |
181 | 0 | return NULL; |
182 | 0 | } |
183 | 0 |
|
184 | 0 | // +1 for trailing slash |
185 | 0 | return &(full_path[TSTRLEN(path_start) + 1]); |
186 | 0 | } |
187 | | |
188 | | /* |
189 | | * Create a LOGICALFS_SEARCH_HELPER that will run a search for |
190 | | * the given inum. |
191 | | * |
192 | | * @param target_inum The inum to search for |
193 | | * |
194 | | * @return The search helper object (must be freed by caller) |
195 | | */ |
196 | | static LOGICALFS_SEARCH_HELPER* |
197 | 0 | create_inum_search_helper(TSK_INUM_T target_inum) { |
198 | 0 | LOGICALFS_SEARCH_HELPER *helper = (LOGICALFS_SEARCH_HELPER *)tsk_malloc(sizeof(LOGICALFS_SEARCH_HELPER)); |
199 | 0 | if (helper == NULL) |
200 | 0 | return NULL; |
201 | | |
202 | 0 | helper->target_found = false; |
203 | 0 | helper->search_type = LOGICALFS_SEARCH_BY_INUM; |
204 | 0 | helper->target_path = NULL; |
205 | 0 | helper->target_inum = target_inum; |
206 | 0 | helper->found_path = NULL; |
207 | 0 | return helper; |
208 | 0 | } |
209 | | |
210 | | /* |
211 | | * Create a LOGICALFS_SEARCH_HELPER that will run a search over |
212 | | * the entire image. Used to find the max inum. |
213 | | * |
214 | | * @return The search helper object (must be freed by caller) |
215 | | */ |
216 | | static LOGICALFS_SEARCH_HELPER* |
217 | 0 | create_max_inum_search_helper() { |
218 | 0 | LOGICALFS_SEARCH_HELPER *helper = (LOGICALFS_SEARCH_HELPER *)tsk_malloc(sizeof(LOGICALFS_SEARCH_HELPER)); |
219 | 0 | if (helper == NULL) |
220 | 0 | return NULL; |
221 | 0 |
|
222 | 0 | helper->target_found = false; |
223 | 0 | helper->search_type = LOGICALFS_NO_SEARCH; |
224 | 0 | helper->target_path = NULL; |
225 | 0 | helper->found_path = NULL; |
226 | 0 | return helper; |
227 | 0 | } |
228 | | |
229 | | /* |
230 | | * Create a LOGICALFS_SEARCH_HELPER that will run a search for |
231 | | * the given path. |
232 | | * |
233 | | * @param target_path The path to search for |
234 | | * |
235 | | * @return The search helper object (must be freed by caller) |
236 | | */ |
237 | | static LOGICALFS_SEARCH_HELPER* |
238 | 0 | create_path_search_helper(const TSK_TCHAR *target_path) { |
239 | 0 | LOGICALFS_SEARCH_HELPER *helper = (LOGICALFS_SEARCH_HELPER *)tsk_malloc(sizeof(LOGICALFS_SEARCH_HELPER)); |
240 | 0 | if (helper == NULL) |
241 | 0 | return NULL; |
242 | 0 |
|
243 | 0 | helper->target_found = false; |
244 | 0 | helper->search_type = LOGICALFS_SEARCH_BY_PATH; |
245 | 0 | helper->target_path = (TSK_TCHAR*)tsk_malloc(sizeof(TSK_TCHAR) * (TSTRLEN(target_path) + 1)); |
246 | 0 | TSTRNCPY(helper->target_path, target_path, TSTRLEN(target_path) + 1); |
247 | 0 | helper->found_inum = LOGICAL_INVALID_INUM; |
248 | 0 | helper->found_path = NULL; |
249 | 0 | return helper; |
250 | 0 | } |
251 | | |
252 | | /* |
253 | | * Free the search helper object |
254 | | * |
255 | | * @param helper The object to free |
256 | | */ |
257 | | static void |
258 | 0 | free_search_helper(LOGICALFS_SEARCH_HELPER* helper) { |
259 | 0 | if (helper->target_path != NULL) { |
260 | 0 | free(helper->target_path); |
261 | 0 | } |
262 | 0 | if (helper->found_path != NULL) { |
263 | 0 | free(helper->found_path); |
264 | 0 | } |
265 | 0 | free(helper); |
266 | 0 | } |
267 | | |
268 | | /* |
269 | | * Convert a wide string to UTF8. |
270 | | * |
271 | | * @param source The wide string to convert. |
272 | | * |
273 | | * @return The converted string (must be freed by caller) or "INVALID FILE NAME" if conversion fails. NULL if memory allocation fails. |
274 | | */ |
275 | | #ifdef TSK_WIN32 |
276 | | static char* |
277 | | convert_wide_string_to_utf8(const wchar_t *source) { |
278 | | |
279 | | const char invalidName[] = "INVALID FILE NAME"; |
280 | | UTF16 *utf16 = (UTF16 *)source; |
281 | | size_t ilen = wcslen(source); |
282 | | size_t maxUTF8len = ilen * 4; |
283 | | if (maxUTF8len < strlen(invalidName) + 1) { |
284 | | maxUTF8len = strlen(invalidName) + 1; |
285 | | } |
286 | | char *dest = (char*)tsk_malloc(maxUTF8len); |
287 | | if (dest == NULL) { |
288 | | return NULL; |
289 | | } |
290 | | UTF8 *utf8 = (UTF8*)dest; |
291 | | |
292 | | TSKConversionResult retVal = |
293 | | tsk_UTF16toUTF8_lclorder((const UTF16 **)&utf16, |
294 | | &utf16[ilen], &utf8, |
295 | | &utf8[maxUTF8len], TSKlenientConversion); |
296 | | |
297 | | if (retVal != TSKconversionOK) { |
298 | | // If the conversion failed, use a default name |
299 | | if (tsk_verbose) |
300 | | tsk_fprintf(stderr, |
301 | | "convert_wide_string_to_utf8: error converting logical file name to UTF-8\n"); |
302 | | strcpy(dest, invalidName); |
303 | | } |
304 | | return dest; |
305 | | } |
306 | | #endif |
307 | | |
308 | | /* |
309 | | * Check if we should set the type as directory. |
310 | | * We currently treat sym links as regular files to avoid |
311 | | * issues trying to read then as directories. |
312 | | */ |
313 | | #ifdef TSK_WIN32 |
314 | | int |
315 | | shouldTreatAsDirectory(DWORD dwFileAttributes) { |
316 | | return ((dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) |
317 | | && (!(dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT))); |
318 | | } |
319 | | #endif |
320 | | |
321 | | /* |
322 | | * Use data in the WIN32_FIND_DATA to populate a TSK_FS_FILE object. |
323 | | * Expects a_fs_file and a_fs_file->meta to be allocated |
324 | | * |
325 | | * @param fd The find data results |
326 | | * @param a_fs_file The file to populate |
327 | | * |
328 | | * @return TSK_OK if successful, TSK_ERR otherwise |
329 | | */ |
330 | | #ifdef TSK_WIN32 |
331 | | TSK_RETVAL_ENUM |
332 | | populate_fs_file_from_win_find_data(const WIN32_FIND_DATA* fd, TSK_FS_FILE * a_fs_file) { |
333 | | |
334 | | if (a_fs_file == NULL || a_fs_file->meta == NULL) { |
335 | | tsk_error_reset(); |
336 | | tsk_error_set_errno(TSK_ERR_FS_ARG); |
337 | | tsk_error_set_errstr("populate_fs_file_from_win_find_data - a_fs_file argument not initialized"); |
338 | | return TSK_ERR; |
339 | | } |
340 | | |
341 | | // For the current use case, we leave the timestamps set to zero. |
342 | | //a_fs_file->meta->crtime = filetime_to_timet(fd->ftCreationTime); |
343 | | //a_fs_file->meta->atime = filetime_to_timet(fd->ftLastAccessTime); |
344 | | //a_fs_file->meta->mtime = filetime_to_timet(fd->ftLastWriteTime); |
345 | | |
346 | | // Set the type |
347 | | if (shouldTreatAsDirectory(fd->dwFileAttributes)) { |
348 | | a_fs_file->meta->type = TSK_FS_META_TYPE_DIR; |
349 | | } |
350 | | else { |
351 | | a_fs_file->meta->type = TSK_FS_META_TYPE_REG; |
352 | | } |
353 | | |
354 | | // All files are allocated |
355 | | a_fs_file->meta->flags = TSK_FS_META_FLAG_ALLOC; |
356 | | |
357 | | // Set the file size |
358 | | LARGE_INTEGER ull; |
359 | | ull.LowPart = fd->nFileSizeLow; |
360 | | ull.HighPart = fd->nFileSizeHigh; |
361 | | a_fs_file->meta->size = ull.QuadPart; |
362 | | |
363 | | return TSK_OK; |
364 | | } |
365 | | #endif |
366 | | |
367 | | /* |
368 | | * Create the wildcard search path used to find directory contents |
369 | | * |
370 | | * @param base_path The path to the directory to open |
371 | | * |
372 | | * @return The search path with wildcard appended (must be freed by caller) |
373 | | */ |
374 | 0 | TSK_TCHAR * create_search_path(const TSK_TCHAR *base_path) { |
375 | 0 | size_t len = TSTRLEN(base_path); |
376 | 0 | TSK_TCHAR * searchPath; |
377 | 0 | size_t searchPathLen = len + 4; |
378 | 0 | searchPath = (TSK_TCHAR *)tsk_malloc(sizeof(TSK_TCHAR) * (searchPathLen)); |
379 | 0 | if (searchPath == NULL) { |
380 | 0 | return NULL; |
381 | 0 | } |
382 | | |
383 | | #ifdef TSK_WIN32 |
384 | | TSTRNCPY(searchPath, base_path, len + 1); |
385 | | TSTRNCAT(searchPath, L"\\*", 4); |
386 | | #else |
387 | 0 | TSTRNCPY(searchPath, base_path, len + 1); |
388 | 0 | TSTRNCAT(searchPath, "/*", 3); |
389 | 0 | #endif |
390 | 0 | return searchPath; |
391 | 0 | } |
392 | | |
393 | | /* |
394 | | * Create the wildcard search path used to find directory contents using |
395 | | * the absolute directory and unicode prefix. We only call this method for |
396 | | * long paths because it does not work in cygwin - prepending "\\?\" only |
397 | | * works for absolute paths starting with a drive letter. |
398 | | * |
399 | | * @param base_path The path to the directory to open |
400 | | * |
401 | | * @return The search path with wildcard appended (must be freed by caller) |
402 | | */ |
403 | | #ifdef TSK_WIN32 |
404 | | TSK_TCHAR * create_search_path_long_path(const TSK_TCHAR *base_path) { |
405 | | |
406 | | // First convert the base path to an absolute path |
407 | | TCHAR absPath[LOGICAL_MAX_PATH_UNICODE]; |
408 | | GetFullPathNameW(base_path, LOGICAL_MAX_PATH_UNICODE, absPath, NULL); |
409 | | |
410 | | size_t len = TSTRLEN(absPath); |
411 | | TSK_TCHAR * searchPath; |
412 | | size_t searchPathLen = len + 9; |
413 | | searchPath = (TSK_TCHAR *)tsk_malloc(sizeof(TSK_TCHAR) * (searchPathLen)); |
414 | | if (searchPath == NULL) { |
415 | | return NULL; |
416 | | } |
417 | | |
418 | | TSTRNCPY(searchPath, L"\\\\?\\", 5); |
419 | | TSTRNCAT(searchPath, absPath, len + 1); |
420 | | TSTRNCAT(searchPath, L"\\*", 4); |
421 | | return searchPath; |
422 | | } |
423 | | #else |
424 | | TSK_TCHAR * create_search_path_long_path( |
425 | | [[maybe_unused]] const TSK_TCHAR *base_path) |
426 | 0 | { |
427 | | // Nothing to do here if it's not Windows |
428 | 0 | return NULL; |
429 | 0 | } |
430 | | #endif |
431 | | |
432 | | /* |
433 | | * Load the names of child files and/or directories into the given vectors. |
434 | | * |
435 | | * @param base_path The parent path |
436 | | * @param file_names Will be populated with file names contained in the parent dir (if requested) |
437 | | * @param dir_names Will be populated with dir names contained in the parent dir (if requested) |
438 | | * @param mode Specifies whether files, directories, or both should be loaded |
439 | | * |
440 | | * @return TSK_OK if successful, TSK_ERR otherwise |
441 | | */ |
442 | | #ifdef TSK_WIN32 |
443 | | static TSK_RETVAL_ENUM |
444 | | load_dir_and_file_lists_win(const TSK_TCHAR *base_path, vector<wstring>& file_names, vector<wstring>& dir_names, LOGICALFS_DIR_LOADING_MODE mode) { |
445 | | |
446 | | WIN32_FIND_DATAW fd; |
447 | | HANDLE hFind; |
448 | | |
449 | | // Create the search string (base path + "\*") |
450 | | TSK_TCHAR * search_path_wildcard = create_search_path(base_path); |
451 | | if (search_path_wildcard == NULL) { |
452 | | return TSK_ERR; |
453 | | } |
454 | | |
455 | | // If the paths is too long, attempt to make a different version that will work |
456 | | if (TSTRLEN(search_path_wildcard) >= MAX_PATH) { |
457 | | free(search_path_wildcard); |
458 | | search_path_wildcard = create_search_path_long_path(base_path); |
459 | | if (search_path_wildcard == NULL) { |
460 | | tsk_error_reset(); |
461 | | tsk_error_set_errno(TSK_ERR_FS_GENFS); |
462 | | tsk_error_set_errstr("load_dir_and_file_lists: Error looking up contents of directory (path too long) %" PRIttocTSK, base_path); |
463 | | return TSK_ERR; |
464 | | } |
465 | | } |
466 | | |
467 | | // Look up all files and folders in the base directory |
468 | | hFind = ::FindFirstFileW(search_path_wildcard, &fd); |
469 | | if (hFind != INVALID_HANDLE_VALUE) { |
470 | | do { |
471 | | if (shouldTreatAsDirectory(fd.dwFileAttributes)) { |
472 | | if (mode == LOGICALFS_LOAD_ALL || mode == LOGICALFS_LOAD_DIRS_ONLY) { |
473 | | // For the moment at least, skip . and .. |
474 | | if (0 != wcsncmp(fd.cFileName, L"..", 3) && 0 != wcsncmp(fd.cFileName, L".", 3)) { |
475 | | dir_names.push_back(wstring(fd.cFileName)); |
476 | | } |
477 | | } |
478 | | } |
479 | | else { |
480 | | if (mode == LOGICALFS_LOAD_ALL || mode == LOGICALFS_LOAD_FILES_ONLY) { |
481 | | // For now, consider everything else to be a file |
482 | | file_names.push_back(wstring(fd.cFileName)); |
483 | | } |
484 | | } |
485 | | } while (::FindNextFileW(hFind, &fd)); |
486 | | ::FindClose(hFind); |
487 | | free(search_path_wildcard); |
488 | | return TSK_OK; |
489 | | } |
490 | | else { |
491 | | free(search_path_wildcard); |
492 | | tsk_error_reset(); |
493 | | tsk_error_set_errno(TSK_ERR_FS_GENFS); |
494 | | tsk_error_set_errstr("load_dir_and_file_lists: Error looking up contents of directory %" PRIttocTSK, base_path); |
495 | | return TSK_ERR; |
496 | | } |
497 | | } |
498 | | #endif |
499 | | |
500 | 0 | void unlock(LegacyCache* cache) { |
501 | 0 | cache->unlock(); |
502 | 0 | }; |
503 | | |
504 | | /* |
505 | | * Finds closest cache match for the given path. |
506 | | * If best_path is not NULL, caller must free. |
507 | | * |
508 | | * @param logical_fs_info The logical file system |
509 | | * @param target_path The full path being searched for |
510 | | * @param best_path The best match found in the cache (NULL if none are found, must be freed by caller otherwise) |
511 | | * @param best_inum The inum matching the best path found |
512 | | * |
513 | | * @return TSK_ERR if an error occurred, TSK_OK otherwise |
514 | | */ |
515 | | static TSK_RETVAL_ENUM |
516 | 0 | find_closest_path_match_in_cache(LOGICALFS_INFO *logical_fs_info, TSK_TCHAR *target_path, TSK_TCHAR **best_path, TSK_INUM_T *best_inum) { |
517 | 0 | TSK_IMG_INFO* img_info = logical_fs_info->fs_info.img_info; |
518 | 0 | IMG_LOGICAL_INFO* logical_img_info = (IMG_LOGICAL_INFO*)img_info; |
519 | 0 |
|
520 | 0 | auto cache = logical_img_info->cache; |
521 | 0 | cache->lock(); |
522 | 0 | std::unique_ptr<LegacyCache, decltype(&unlock)> lock_guard(cache, unlock); |
523 | 0 |
|
524 | 0 | *best_inum = LOGICAL_INVALID_INUM; |
525 | 0 | *best_path = NULL; |
526 | 0 | int best_match_index = -1; |
527 | 0 | size_t longest_match = 0; |
528 | 0 | size_t target_len = TSTRLEN(target_path); |
529 | 0 |
|
530 | 0 | for (int i = 0; i < LOGICAL_INUM_CACHE_LEN; i++) { |
531 | 0 | if (logical_img_info->inum_cache[i].path != NULL) { |
532 | 0 |
|
533 | 0 | // Check that: |
534 | 0 | // - We haven't already found the exact match (longest_match = target_len) |
535 | 0 | // - The cache entry could potentially be a longer match than what we have so far |
536 | 0 | // - The cache entry isn't longer than what we're looking for |
537 | 0 | size_t cache_path_len = TSTRLEN(logical_img_info->inum_cache[i].path); |
538 | 0 | if ((longest_match != target_len) && (cache_path_len > longest_match) && (cache_path_len <= target_len)) { |
539 | 0 | size_t matching_len = 0; |
540 | 0 | #ifdef TSK_WIN32 |
541 | 0 | if (0 == _wcsnicmp(target_path, logical_img_info->inum_cache[i].path, cache_path_len)) { |
542 | 0 | matching_len = cache_path_len; |
543 | 0 | } |
544 | 0 | #endif |
545 | 0 |
|
546 | 0 | // Save this path if: |
547 | 0 | // - It is longer than our previous best match |
548 | 0 | // - It is either the full length of the path we're searching for or is a valid |
549 | 0 | // substring of our path |
550 | 0 | if ((matching_len > longest_match) && |
551 | 0 | ((matching_len == target_len) || ((matching_len < target_len) && |
552 | 0 | ((target_path[matching_len] == L'/') || (target_path[matching_len] == L'\\'))))) { |
553 | 0 |
|
554 | 0 | // We found the full path or a partial match |
555 | 0 | longest_match = matching_len; |
556 | 0 | best_match_index = i; |
557 | 0 |
|
558 | 0 | // For the moment, consider any potential best match to have been useful. We could |
559 | 0 | // change this to only reset the age of the actual best match. |
560 | 0 | logical_img_info->inum_cache[i].cache_age = LOGICAL_INUM_CACHE_MAX_AGE; |
561 | 0 | } |
562 | 0 | else { |
563 | 0 | // The cache entry was not useful so decrease the age |
564 | 0 | if (logical_img_info->inum_cache[i].cache_age > 1) { |
565 | 0 | logical_img_info->inum_cache[i].cache_age--; |
566 | 0 | } |
567 | 0 | } |
568 | 0 | } |
569 | 0 | else { |
570 | 0 | // The cache entry was not useful so decrease the age |
571 | 0 | if (logical_img_info->inum_cache[i].cache_age > 1) { |
572 | 0 | logical_img_info->inum_cache[i].cache_age--; |
573 | 0 | } |
574 | 0 | } |
575 | 0 | } |
576 | 0 | } |
577 | 0 |
|
578 | 0 | // If we found a full or partial match, store the values |
579 | 0 | if (best_match_index >= 0) { |
580 | 0 | *best_inum = logical_img_info->inum_cache[best_match_index].inum; |
581 | 0 | *best_path = (TSK_TCHAR*)tsk_malloc(sizeof(TSK_TCHAR) * (TSTRLEN(logical_img_info->inum_cache[best_match_index].path) + 1)); |
582 | 0 | if (*best_path == NULL) { |
583 | 0 | return TSK_ERR; |
584 | 0 | } |
585 | 0 | TSTRNCPY(*best_path, logical_img_info->inum_cache[best_match_index].path, TSTRLEN(logical_img_info->inum_cache[best_match_index].path) + 1); |
586 | 0 | } |
587 | 0 |
|
588 | 0 | return TSK_OK; |
589 | 0 | } |
590 | | |
591 | | /* |
592 | | * Finds closest sibling match for the given folder. |
593 | | * If best_path is not NULL, caller must free. |
594 | | * |
595 | | * This method does not alter cache ages. |
596 | | * |
597 | | * @param logical_fs_info The logical file system |
598 | | * @param target_path The full path being searched for |
599 | | * @param parent_path The parent path that we found in the cache |
600 | | * @param parent_inum The addr of the parent path |
601 | | * @param best_name The best match found in the cache - file name only. (NULL if none are found, must be freed by caller otherwise) |
602 | | * @param best_inum The inum matching the best path found. LOGICAL_INVALID_INUM if none are found. |
603 | | * |
604 | | * @return TSK_ERR if an error occurred, TSK_OK otherwise |
605 | | */ |
606 | | static TSK_RETVAL_ENUM |
607 | 0 | find_closest_sibling_match_in_cache(LOGICALFS_INFO* logical_fs_info, const TSK_TCHAR* target_path, const TSK_TCHAR* parent_path, TSK_INUM_T parent_inum, TSK_TCHAR** best_name, TSK_INUM_T* best_inum) { |
608 | 0 | TSK_IMG_INFO* img_info = logical_fs_info->fs_info.img_info; |
609 | 0 | IMG_LOGICAL_INFO* logical_img_info = (IMG_LOGICAL_INFO*)img_info; |
610 | 0 |
|
611 | 0 | *best_inum = LOGICAL_INVALID_INUM; |
612 | 0 | *best_name = NULL; |
613 | 0 |
|
614 | 0 | int best_match_index = -1; |
615 | 0 | TSK_INUM_T highest_inum = LOGICAL_INVALID_INUM; |
616 | 0 |
|
617 | 0 | for (int i = 0; i < LOGICAL_INUM_CACHE_LEN; i++) { |
618 | 0 | if (logical_img_info->inum_cache[i].path != NULL |
619 | 0 | && logical_img_info->inum_cache[i].inum > parent_inum |
620 | 0 | && logical_img_info->inum_cache[i].inum > highest_inum) { |
621 | 0 |
|
622 | 0 | // This entry is useful if: |
623 | 0 | // - path is directly under the target's parent folder |
624 | 0 | // - path comes before the target path alphabetically |
625 | 0 | // - inum is larger than our previous best match |
626 | 0 | if (!path_is_subfolder(parent_path, logical_img_info->inum_cache[i].path)) { |
627 | 0 | continue; |
628 | 0 | } |
629 | 0 |
|
630 | 0 | const TSK_TCHAR* rest = get_end_of_path(logical_img_info->inum_cache[i].path, parent_path); |
631 | 0 | if (contains_folder_separator(rest)) { |
632 | 0 | continue; |
633 | 0 | } |
634 | 0 |
|
635 | 0 | if (TSTRICMP(target_path, logical_img_info->inum_cache[i].path) > 0) { |
636 | 0 | highest_inum = logical_img_info->inum_cache[i].inum; |
637 | 0 | best_match_index = i; |
638 | 0 | } |
639 | 0 | } |
640 | 0 | } |
641 | 0 |
|
642 | 0 | // If we found something, store the values |
643 | 0 | if (best_match_index >= 0) { |
644 | 0 |
|
645 | 0 | const TSK_TCHAR* name = get_end_of_path(logical_img_info->inum_cache[best_match_index].path, parent_path); |
646 | 0 | if (name == NULL) { |
647 | 0 | if (tsk_verbose) { |
648 | 0 | tsk_fprintf(stderr, "find_closest_sibling_match_in_cache: get_end_of_path returned null for child: %" PRIttocTSK " parent: %" PRIttocTSK "\n", |
649 | 0 | logical_img_info->inum_cache[best_match_index].path, parent_path); |
650 | 0 | } |
651 | 0 | return TSK_ERR; |
652 | 0 | } |
653 | 0 | *best_name = (TSK_TCHAR*)tsk_malloc(sizeof(TSK_TCHAR) * (TSTRLEN(name) + 1)); |
654 | 0 | if (*best_name == NULL) { |
655 | 0 | return TSK_ERR; |
656 | 0 | } |
657 | 0 | TSTRNCPY(*best_name, name, TSTRLEN(name) + 1); |
658 | 0 | *best_inum = logical_img_info->inum_cache[best_match_index].inum; |
659 | 0 | } |
660 | 0 |
|
661 | 0 | return TSK_OK; |
662 | 0 | } |
663 | | |
664 | | /* |
665 | | * Look up the path corresponding to the given inum in the cache. |
666 | | * Returned path must be freed by caller. |
667 | | * |
668 | | * @param logical_fs_info The logical file system |
669 | | * @param target_inum The inum we're searching for |
670 | | * |
671 | | * @return The path corresponding to the given inum or NULL if not found or an error occurred. Must be freed by caller. |
672 | | */ |
673 | | static TSK_TCHAR* |
674 | 0 | find_path_for_inum_in_cache(LOGICALFS_INFO *logical_fs_info, TSK_INUM_T target_inum) { |
675 | 0 | TSK_IMG_INFO* img_info = logical_fs_info->fs_info.img_info; |
676 | 0 | IMG_LOGICAL_INFO* logical_img_info = (IMG_LOGICAL_INFO*)img_info; |
677 | |
|
678 | 0 | auto cache = logical_img_info->cache; |
679 | 0 | cache->lock(); |
680 | 0 | std::unique_ptr<LegacyCache, decltype(&unlock)> lock_guard(cache, unlock); |
681 | |
|
682 | 0 | TSK_TCHAR *target_path = NULL; |
683 | 0 | for (int i = 0; i < LOGICAL_INUM_CACHE_LEN; i++) { |
684 | 0 | if (target_path == NULL && logical_img_info->inum_cache[i].inum == target_inum) { |
685 | | // The cache entry was useful so reset the age |
686 | 0 | logical_img_info->inum_cache[i].cache_age = LOGICAL_INUM_CACHE_MAX_AGE; |
687 | | |
688 | | // Copy the path |
689 | 0 | const size_t len = TSTRLEN(logical_img_info->inum_cache[i].path); |
690 | 0 | target_path = (TSK_TCHAR*)tsk_malloc(sizeof(TSK_TCHAR) * (len + 1)); |
691 | 0 | if (target_path == NULL) { |
692 | 0 | return NULL; |
693 | 0 | } |
694 | 0 | TSTRNCPY(target_path, logical_img_info->inum_cache[i].path, len + 1); |
695 | 0 | } |
696 | 0 | else { |
697 | | // The cache entry was not useful so decrease the age |
698 | 0 | if (logical_img_info->inum_cache[i].cache_age > 1) { |
699 | 0 | logical_img_info->inum_cache[i].cache_age--; |
700 | 0 | } |
701 | 0 | } |
702 | 0 | } |
703 | | |
704 | 0 | return target_path; |
705 | 0 | } |
706 | | |
707 | | /* |
708 | | * Add a directory to the cache |
709 | | * |
710 | | * @param logical_fs_info The logical file system |
711 | | * @param path The directory path |
712 | | * @param inum The inum corresponding to the path |
713 | | * @param always_cache If false, only cache the entry if we have empty space (and it will get a smaller age) |
714 | | * |
715 | | * @return TSK_OK if successful, TSK_ERR on error |
716 | | */ |
717 | | static TSK_RETVAL_ENUM |
718 | 0 | add_directory_to_cache(LOGICALFS_INFO *logical_fs_info, const TSK_TCHAR *path, TSK_INUM_T inum, bool always_cache) { |
719 | | |
720 | | // If the path is very long then don't cache it to make sure the cache stays reasonably small. |
721 | 0 | if (TSTRLEN(path) > LOGICAL_INUM_CACHE_MAX_PATH_LEN) { |
722 | 0 | return TSK_OK; |
723 | 0 | } |
724 | | |
725 | 0 | TSK_IMG_INFO* img_info = logical_fs_info->fs_info.img_info; |
726 | 0 | IMG_LOGICAL_INFO* logical_img_info = (IMG_LOGICAL_INFO*)img_info; |
727 | |
|
728 | 0 | auto cache = logical_img_info->cache; |
729 | 0 | cache->lock(); |
730 | 0 | std::unique_ptr<LegacyCache, decltype(&unlock)> lock_guard(cache, unlock); |
731 | | |
732 | | // Check if this entry is already in the cache. |
733 | 0 | for (int i = 0; i < LOGICAL_INUM_CACHE_LEN; i++) { |
734 | 0 | if (logical_img_info->inum_cache[i].inum == inum) { |
735 | | // If we found it and we're always caching then reset the age |
736 | 0 | if (always_cache && logical_img_info->inum_cache[i].cache_age < LOGICAL_INUM_CACHE_MAX_AGE) { |
737 | 0 | logical_img_info->inum_cache[i].cache_age = LOGICAL_INUM_CACHE_MAX_AGE; |
738 | 0 | } |
739 | 0 | return TSK_OK; |
740 | 0 | } |
741 | 0 | } |
742 | | |
743 | | // Check if this entry is already in the cache. |
744 | 0 | for (int i = 0; i < LOGICAL_INUM_CACHE_LEN; i++) { |
745 | 0 | if (logical_img_info->inum_cache[i].inum == inum) { |
746 | | // If we found it and we're always caching then reset the age |
747 | 0 | if (always_cache && logical_img_info->inum_cache[i].cache_age < LOGICAL_INUM_CACHE_MAX_AGE) { |
748 | 0 | logical_img_info->inum_cache[i].cache_age = LOGICAL_INUM_CACHE_MAX_AGE; |
749 | 0 | } |
750 | 0 | return TSK_OK; |
751 | 0 | } |
752 | 0 | } |
753 | | |
754 | | // Find the next cache slot. If we find an unused slot, use that. Otherwise find the entry |
755 | | // with the lowest age. |
756 | 0 | int next_slot = 0; |
757 | 0 | int lowest_age = LOGICAL_INUM_CACHE_MAX_AGE + 1; |
758 | 0 | for (int i = 0; i < LOGICAL_INUM_CACHE_LEN; i++) { |
759 | 0 | if (logical_img_info->inum_cache[i].inum == LOGICAL_INVALID_INUM) { |
760 | 0 | next_slot = i; |
761 | 0 | break; |
762 | 0 | } |
763 | | |
764 | 0 | if (logical_img_info->inum_cache[i].cache_age < lowest_age) { |
765 | 0 | next_slot = i; |
766 | 0 | lowest_age = logical_img_info->inum_cache[i].cache_age; |
767 | 0 | } |
768 | 0 | } |
769 | | |
770 | | // If the always_cache flag is not set, only continue if we've found an empty space |
771 | 0 | if (!always_cache && logical_img_info->inum_cache[next_slot].inum != LOGICAL_INVALID_INUM) { |
772 | 0 | return TSK_OK; |
773 | 0 | } |
774 | 0 | clear_inum_cache_entry(logical_img_info, next_slot); |
775 | | |
776 | | // Copy the data |
777 | 0 | logical_img_info->inum_cache[next_slot].path = (TSK_TCHAR*)tsk_malloc(sizeof(TSK_TCHAR) * (TSTRLEN(path) + 1)); |
778 | 0 | if (logical_img_info->inum_cache[next_slot].path == NULL) { |
779 | 0 | return TSK_ERR; |
780 | 0 | } |
781 | 0 | TSTRNCPY(logical_img_info->inum_cache[next_slot].path, path, TSTRLEN(path) + 1); |
782 | 0 | logical_img_info->inum_cache[next_slot].inum = inum; |
783 | 0 | if (always_cache) { |
784 | 0 | logical_img_info->inum_cache[next_slot].cache_age = LOGICAL_INUM_CACHE_MAX_AGE; |
785 | 0 | } else { |
786 | | // We want to remove the random folders first when we run out of space |
787 | 0 | logical_img_info->inum_cache[next_slot].cache_age = LOGICAL_INUM_CACHE_MAX_AGE / 2; |
788 | 0 | } |
789 | 0 | return TSK_OK; |
790 | 0 | } |
791 | | |
792 | | // This should be done with a template, but I'm lazy. |
793 | | // Windows version |
794 | | #ifdef TSK_WIN32 |
795 | | bool case_insensitive_compare(const std::wstring& a, const std::wstring& b) { |
796 | | return std::lexicographical_compare( |
797 | | a.begin(), a.end(), |
798 | | b.begin(), b.end(), |
799 | | [](wchar_t a, wchar_t b) { |
800 | | return std::towlower(a) < std::towlower(b); |
801 | | } |
802 | | ); |
803 | | } |
804 | | #else |
805 | 0 | bool case_insensitive_compare(const string& a, const string& b) { |
806 | 0 | return std::lexicographical_compare( |
807 | 0 | a.begin(), a.end(), |
808 | 0 | b.begin(), b.end(), |
809 | 0 | [](char a, char b) { |
810 | 0 | return std::tolower(a) < std::tolower(b); |
811 | 0 | } |
812 | 0 | ); |
813 | 0 | } |
814 | | #endif |
815 | | |
816 | | /* |
817 | | * Main recursive method for walking the directories. Will load and sort all directories found |
818 | | * in parent_path, assign an inum to each and check if this is what we're searching for, calling |
819 | | * this method recursively if not. |
820 | | * |
821 | | * @param logical_fs_info LOGICALFS_INFO object |
822 | | * @param parent_path The full path on disk to the directory to open |
823 | | * @param last_inum_ptr Pointer to the last assigned inum. Will be updated for every directory found |
824 | | * @param sibling_name Name of sibling file to help limit search (use NULL if no sibling file is known) |
825 | | * @param sibling_inum Address of sibling file (use LOGICAL_INVALID_INUM if no sibling file is known) |
826 | | * @param search_helper Contains information on what type of search is being performed and will store the results in most cases. |
827 | | * |
828 | | * @return TSK_OK if successfull, TSK_ERR otherwise |
829 | | */ |
830 | | static TSK_RETVAL_ENUM |
831 | | search_directory_recursive(LOGICALFS_INFO *logical_fs_info, const TSK_TCHAR * parent_path, TSK_INUM_T *last_inum_ptr, |
832 | 0 | const TSK_TCHAR* sibling_name, TSK_INUM_T sibling_inum, LOGICALFS_SEARCH_HELPER* search_helper) { |
833 | |
|
834 | | #ifdef TSK_WIN32 |
835 | | vector<wstring> file_names; |
836 | | vector<wstring> dir_names; |
837 | | #else |
838 | 0 | vector<string> file_names; |
839 | 0 | vector<string> dir_names; |
840 | 0 | #endif |
841 | | |
842 | | // If we're searching for a file and this is the correct directory, load only the files in the folder and |
843 | | // return the correct one. |
844 | 0 | if (search_helper->search_type == LOGICALFS_SEARCH_BY_INUM |
845 | 0 | && (*last_inum_ptr == (search_helper->target_inum & LOGICAL_INUM_DIR_MASK)) |
846 | 0 | && ((search_helper->target_inum & LOGICAL_INUM_FILE_MASK) != 0)) { |
847 | |
|
848 | | #ifdef TSK_WIN32 |
849 | | if (TSK_OK != load_dir_and_file_lists_win(parent_path, file_names, dir_names, LOGICALFS_LOAD_FILES_ONLY)) { |
850 | | // Error message already set |
851 | | return TSK_ERR; |
852 | | } |
853 | | #endif |
854 | 0 | std::sort(file_names.begin(), file_names.end(), case_insensitive_compare); |
855 | | |
856 | | // Look for the file corresponding to the given inum |
857 | 0 | size_t file_index = (search_helper->target_inum & LOGICAL_INUM_FILE_MASK) - 1; |
858 | 0 | if (file_names.size() <= file_index) { |
859 | 0 | tsk_error_reset(); |
860 | 0 | tsk_error_set_errno(TSK_ERR_FS_INODE_NUM); |
861 | 0 | tsk_error_set_errstr("search_directory_recusive - inum %" PRIuINUM " not found", search_helper->target_inum); |
862 | 0 | return TSK_ERR; |
863 | 0 | } |
864 | | |
865 | 0 | search_helper->target_found = true; |
866 | 0 | size_t found_path_len = TSTRLEN(parent_path) + 1 + TSTRLEN(file_names[file_index].c_str()); |
867 | 0 | search_helper->found_path = (TSK_TCHAR*)tsk_malloc(sizeof(TSK_TCHAR) * (found_path_len + 1)); |
868 | 0 | TSTRNCPY(search_helper->found_path, parent_path, TSTRLEN(parent_path) + 1); |
869 | | #ifdef TSK_WIN32 |
870 | | TSTRNCAT(search_helper->found_path, L"\\", 2); |
871 | | #else |
872 | 0 | TSTRNCAT(search_helper->found_path, "/", 2); |
873 | 0 | #endif |
874 | 0 | TSTRNCAT(search_helper->found_path, file_names[file_index].c_str(), TSTRLEN(file_names[file_index].c_str()) + 1); |
875 | 0 | return TSK_OK; |
876 | 0 | } |
877 | | |
878 | | #ifdef TSK_WIN32 |
879 | | if (TSK_OK != load_dir_and_file_lists_win(parent_path, file_names, dir_names, LOGICALFS_LOAD_DIRS_ONLY)) { |
880 | | // Error message already set |
881 | | return TSK_ERR; |
882 | | } |
883 | | #endif |
884 | | |
885 | | // Sort the directory names |
886 | 0 | sort(dir_names.begin(), dir_names.end(), case_insensitive_compare); |
887 | | |
888 | | // Set up the beginning of full path to the file on disk |
889 | | // The directoy name being added should generally be less than 270 characters, but if necessary we will |
890 | | // make more space available. |
891 | 0 | size_t allocated_dir_name_len = 270; |
892 | 0 | const size_t current_path_len = TSTRLEN(parent_path) + 1 + allocated_dir_name_len; |
893 | 0 | TSK_TCHAR* current_path = (TSK_TCHAR*)tsk_malloc(sizeof(TSK_TCHAR) * (current_path_len + 1)); |
894 | 0 | if (current_path == NULL) |
895 | 0 | return TSK_ERR; |
896 | 0 | TSTRNCPY(current_path, parent_path, current_path_len + 1); |
897 | | #ifdef TSK_WIN32 |
898 | | TSTRNCAT(current_path, L"\\", 2); |
899 | | #else |
900 | 0 | TSTRNCAT(current_path, "/", 2); |
901 | 0 | #endif |
902 | 0 | size_t parent_path_len = TSTRLEN(current_path); |
903 | | |
904 | | // If we were given a sibling directory, look for it in the list so we can start the search there |
905 | 0 | size_t starting_dir_index = 0; |
906 | 0 | if (sibling_inum != LOGICAL_INVALID_INUM && sibling_name != NULL) { |
907 | 0 | for (size_t i = 0; i < dir_names.size(); i++) { |
908 | | #ifdef TSK_WIN32 |
909 | | if (0 == _wcsicmp(dir_names[i].c_str(), sibling_name)) { |
910 | | // Found it. Save the index and adjust the last inum (LOGICAL_INUM_DIR_INC will get added to last_inum_ptr) |
911 | | starting_dir_index = i; |
912 | | *last_inum_ptr = sibling_inum - LOGICAL_INUM_DIR_INC; |
913 | | break; |
914 | | } |
915 | | #endif |
916 | 0 | } |
917 | 0 | } |
918 | |
|
919 | 0 | for (size_t i = starting_dir_index; i < dir_names.size(); i++) { |
920 | | |
921 | | // If we don't have space for this name, increase the size of the buffer |
922 | 0 | if (TSTRLEN(dir_names[i].c_str()) > allocated_dir_name_len) { |
923 | 0 | free(current_path); |
924 | 0 | allocated_dir_name_len = TSTRLEN(dir_names[i].c_str()) + 20; |
925 | 0 | current_path = (TSK_TCHAR*)tsk_malloc(sizeof(TSK_TCHAR) * (TSTRLEN(parent_path) + 2 + allocated_dir_name_len)); |
926 | 0 | if (current_path == NULL) |
927 | 0 | return TSK_ERR; |
928 | 0 | TSTRNCPY(current_path, parent_path, TSTRLEN(parent_path) + 1); |
929 | | #ifdef TSK_WIN32 |
930 | | TSTRNCAT(current_path, L"\\", 2); |
931 | | #else |
932 | 0 | TSTRNCAT(current_path, "/", 2); |
933 | 0 | #endif |
934 | 0 | } |
935 | | |
936 | | // Append the current directory name to the parent path |
937 | 0 | TSTRNCPY(current_path + parent_path_len, dir_names[i].c_str(), TSTRLEN(dir_names[i].c_str()) + 1); |
938 | 0 | if (*last_inum_ptr == LOGICAL_INUM_DIR_MAX) { |
939 | | // We're run out of inums to assign. Return an error. |
940 | 0 | tsk_error_reset(); |
941 | 0 | tsk_error_set_errno(TSK_ERR_FS_INODE_NUM); |
942 | 0 | tsk_error_set_errstr("search_directory_recusive: Too many directories in logical file set"); |
943 | 0 | free(current_path); |
944 | 0 | return TSK_ERR; |
945 | 0 | } |
946 | 0 | TSK_INUM_T current_inum = *last_inum_ptr + LOGICAL_INUM_DIR_INC; |
947 | 0 | *last_inum_ptr = current_inum; |
948 | | |
949 | | // There's no perfect way to do the caching. Caching everything here had the problem that if we have a miss then the |
950 | | // whole cache gets overwritten while we search. So we'll generally only cache directories that get us closer to |
951 | | // our target (so if we search for something in the same or similar folders it'll be a fast search) and directories |
952 | | // that are close to the root one (one or two folders deep). |
953 | 0 | size_t current_path_len = TSTRLEN(current_path); |
954 | 0 | size_t path_offset = TSTRLEN(logical_fs_info->base_path) + 1; // The +1 advances past the slash after the root dir |
955 | 0 | bool is_near_root_folder = false; |
956 | 0 | if (((search_helper->search_type == LOGICALFS_SEARCH_BY_PATH) || (search_helper->search_type == LOGICALFS_NO_SEARCH)) |
957 | 0 | && path_offset < current_path_len) { |
958 | 0 | int slash_count = 0; |
959 | 0 | for (size_t i = path_offset; i < current_path_len; i++) { |
960 | 0 | if (current_path[i] == '/' || current_path[i] == '\\') { |
961 | 0 | slash_count++; |
962 | 0 | } |
963 | 0 | } |
964 | 0 | is_near_root_folder = (slash_count < 2); |
965 | 0 | } |
966 | 0 | if (search_helper->search_type == LOGICALFS_SEARCH_BY_PATH) { |
967 | 0 | if (is_near_root_folder || TSTRNCMP(current_path, search_helper->target_path, current_path_len) == 0) { |
968 | 0 | add_directory_to_cache(logical_fs_info, current_path, current_inum, true); |
969 | 0 | } |
970 | 0 | else { |
971 | | // This will only add to the cache if we have empty space |
972 | 0 | add_directory_to_cache(logical_fs_info, current_path, current_inum, false); |
973 | 0 | } |
974 | 0 | } |
975 | 0 | else if (search_helper->search_type == LOGICALFS_NO_SEARCH && is_near_root_folder) { |
976 | | // Cache the base directories when opening the file system |
977 | 0 | add_directory_to_cache(logical_fs_info, current_path, current_inum, true); |
978 | 0 | } |
979 | | |
980 | | // Check if we've found it |
981 | 0 | if ((search_helper->search_type == LOGICALFS_SEARCH_BY_PATH) |
982 | 0 | && (TSTRICMP(current_path, search_helper->target_path) == 0)) { |
983 | 0 | search_helper->target_found = true; |
984 | 0 | search_helper->found_inum = current_inum; |
985 | 0 | free(current_path); |
986 | 0 | return TSK_OK; |
987 | 0 | } |
988 | | |
989 | 0 | if ((search_helper->search_type == LOGICALFS_SEARCH_BY_INUM) |
990 | 0 | && (current_inum == search_helper->target_inum)) { |
991 | 0 | search_helper->target_found = true; |
992 | 0 | search_helper->found_path = (TSK_TCHAR*)tsk_malloc(sizeof(TSK_TCHAR) * (TSTRLEN(current_path) + 1)); |
993 | 0 | if (search_helper->found_path == NULL) |
994 | 0 | return TSK_ERR; |
995 | 0 | TSTRNCPY(search_helper->found_path, current_path, TSTRLEN(current_path) + 1); |
996 | 0 | free(current_path); |
997 | 0 | return TSK_OK; |
998 | 0 | } |
999 | 0 | TSK_RETVAL_ENUM result = search_directory_recursive(logical_fs_info, current_path, last_inum_ptr, NULL, LOGICAL_INVALID_INUM, search_helper); |
1000 | 0 | if (result != TSK_OK) { |
1001 | 0 | free(current_path); |
1002 | 0 | return result; |
1003 | 0 | } |
1004 | 0 | if (search_helper->target_found) { |
1005 | 0 | free(current_path); |
1006 | 0 | return TSK_OK; |
1007 | 0 | } |
1008 | 0 | } |
1009 | 0 | free(current_path); |
1010 | 0 | return TSK_OK; |
1011 | 0 | } |
1012 | | |
1013 | | /* |
1014 | | * Find the path corresponding to the given inum |
1015 | | * |
1016 | | * @param logical_fs_info The logical file system |
1017 | | * @param a_addr The inum to search for |
1018 | | * |
1019 | | * @return The path corresponding to the inum. Null on error. Must be freed by caller. |
1020 | | */ |
1021 | | static TSK_TCHAR * |
1022 | 0 | load_path_from_inum(LOGICALFS_INFO *logical_fs_info, TSK_INUM_T a_addr) { |
1023 | |
|
1024 | 0 | TSK_TCHAR *path = NULL; |
1025 | 0 | if (a_addr == logical_fs_info->fs_info.root_inum) { |
1026 | | // No need to do a search - it's just the root folder |
1027 | 0 | const size_t len = TSTRLEN(logical_fs_info->base_path); |
1028 | 0 | path = (TSK_TCHAR*)tsk_malloc(sizeof(TSK_TCHAR) * (len + 1)); |
1029 | 0 | if (path == NULL) |
1030 | 0 | return NULL; |
1031 | 0 | TSTRNCPY(path, logical_fs_info->base_path, len + 1); |
1032 | 0 | return path; |
1033 | 0 | } |
1034 | | |
1035 | | // Default starting position for the search is the base folder |
1036 | 0 | TSK_INUM_T starting_inum = logical_fs_info->fs_info.root_inum; |
1037 | 0 | const TSK_TCHAR *starting_path = logical_fs_info->base_path; |
1038 | | |
1039 | | // See if the directory is in the cache |
1040 | 0 | TSK_INUM_T dir_addr = a_addr & LOGICAL_INUM_DIR_MASK; |
1041 | 0 | TSK_TCHAR *cache_path = find_path_for_inum_in_cache(logical_fs_info, dir_addr); |
1042 | 0 | if (cache_path != NULL) { |
1043 | 0 | if (dir_addr == a_addr) { |
1044 | | // If we were looking for a directory, we're done |
1045 | 0 | return cache_path; |
1046 | 0 | } |
1047 | | |
1048 | | // Otherwise, set up the search parameters to start with the folder found |
1049 | 0 | starting_inum = dir_addr; |
1050 | 0 | starting_path = cache_path; |
1051 | |
|
1052 | 0 | } |
1053 | | |
1054 | | // Create the struct that holds search params and results |
1055 | 0 | LOGICALFS_SEARCH_HELPER *search_helper = create_inum_search_helper(a_addr); |
1056 | 0 | if (search_helper == NULL) { |
1057 | 0 | return NULL; |
1058 | 0 | } |
1059 | | |
1060 | | // Run the search |
1061 | 0 | TSK_RETVAL_ENUM result = search_directory_recursive(logical_fs_info, starting_path, &starting_inum, NULL, LOGICAL_INVALID_INUM, search_helper); |
1062 | |
|
1063 | 0 | if (cache_path != NULL) { |
1064 | 0 | free(cache_path); |
1065 | 0 | } |
1066 | |
|
1067 | 0 | if ((result != TSK_OK) || (!search_helper->target_found)) { |
1068 | 0 | tsk_error_reset(); |
1069 | 0 | tsk_error_set_errno(TSK_ERR_FS_INODE_NUM); |
1070 | 0 | tsk_error_set_errstr("load_path_from_inum - failed to find path corresponding to inum %" PRIuINUM, search_helper->target_inum); |
1071 | | // Free search_helper after using it to format the error string. |
1072 | 0 | free_search_helper(search_helper); |
1073 | 0 | return NULL; |
1074 | 0 | } |
1075 | | |
1076 | | // Copy the path |
1077 | 0 | const size_t len = TSTRLEN(search_helper->found_path); |
1078 | 0 | path = (TSK_TCHAR*)tsk_malloc(sizeof(TSK_TCHAR) * (len + 1)); |
1079 | 0 | if (path == NULL) { |
1080 | 0 | free_search_helper(search_helper); |
1081 | 0 | return NULL; |
1082 | 0 | } |
1083 | 0 | TSTRNCPY(path, search_helper->found_path, len + 1); |
1084 | 0 | free_search_helper(search_helper); |
1085 | 0 | return path; |
1086 | 0 | } |
1087 | | |
1088 | | static uint8_t |
1089 | | logicalfs_file_add_meta(TSK_FS_INFO *a_fs, TSK_FS_FILE * a_fs_file, |
1090 | | TSK_INUM_T inum) |
1091 | 0 | { |
1092 | 0 | LOGICALFS_INFO *logical_fs_info = (LOGICALFS_INFO*)a_fs; |
1093 | 0 | if (a_fs_file == NULL) { |
1094 | 0 | tsk_error_reset(); |
1095 | 0 | tsk_error_set_errno(TSK_ERR_FS_ARG); |
1096 | 0 | tsk_error_set_errstr("logicalfs_file_add_meta - null TSK_FS_FILE given"); |
1097 | 0 | return TSK_ERR; |
1098 | 0 | } |
1099 | 0 | if (a_fs_file->meta == NULL) { |
1100 | 0 | if ((a_fs_file->meta = tsk_fs_meta_alloc(0)) == NULL) { |
1101 | 0 | return TSK_ERR; |
1102 | 0 | } |
1103 | 0 | } |
1104 | 0 | else { |
1105 | 0 | tsk_fs_meta_reset(a_fs_file->meta); |
1106 | 0 | } |
1107 | 0 |
|
1108 | 0 | a_fs_file->meta->addr = inum; |
1109 | 0 |
|
1110 | 0 | // Get the full path to the given file |
1111 | 0 | TSK_TCHAR* path = load_path_from_inum(logical_fs_info, inum); |
1112 | 0 | if (path == NULL) { |
1113 | 0 | tsk_error_reset(); |
1114 | 0 | tsk_error_set_errno(TSK_ERR_FS_INODE_NUM); |
1115 | 0 | tsk_error_set_errstr("logicalfs_file_add_meta - Error loading directory with inum %" PRIuINUM, inum); |
1116 | 0 | return TSK_ERR; |
1117 | 0 | } |
1118 | 0 |
|
1119 | 0 | #ifdef TSK_WIN32 |
1120 | 0 | // Load the file |
1121 | 0 | WIN32_FIND_DATAW fd; |
1122 | 0 | HANDLE hFind; |
1123 | 0 | if (TSTRLEN(path) < MAX_PATH) { |
1124 | 0 | hFind = ::FindFirstFileW(path, &fd); |
1125 | 0 | } |
1126 | 0 | else { |
1127 | 0 | TCHAR absPath[LOGICAL_MAX_PATH_UNICODE + 4]; |
1128 | 0 | TSTRNCPY(absPath, L"\\\\?\\", 4); |
1129 | 0 | int absPathLen = GetFullPathNameW(path, LOGICAL_MAX_PATH_UNICODE, &(absPath[4]), NULL); |
1130 | 0 | if (absPathLen <= 0) { |
1131 | 0 | tsk_error_reset(); |
1132 | 0 | tsk_error_set_errno(TSK_ERR_FS_GENFS); |
1133 | 0 | tsk_error_set_errstr("logicalfs_file_add_meta: Error looking up contents of directory (path too long) %" PRIttocTSK, path); |
1134 | 0 | free(path); |
1135 | 0 | return TSK_ERR; |
1136 | 0 | } |
1137 | 0 | hFind = ::FindFirstFileW(absPath, &fd); |
1138 | 0 | } |
1139 | 0 |
|
1140 | 0 | if (hFind != INVALID_HANDLE_VALUE) { |
1141 | 0 |
|
1142 | 0 | TSK_RETVAL_ENUM result = populate_fs_file_from_win_find_data(&fd, a_fs_file); |
1143 | 0 | ::FindClose(hFind); |
1144 | 0 | free(path); |
1145 | 0 | return result; |
1146 | 0 | } |
1147 | 0 | else { |
1148 | 0 | tsk_error_reset(); |
1149 | 0 | tsk_error_set_errno(TSK_ERR_FS_GENFS); |
1150 | 0 | tsk_error_set_errstr("logicalfs_file_add_meta: Error loading directory %" PRIttocTSK, path); |
1151 | 0 | free(path); |
1152 | 0 | return TSK_ERR; |
1153 | 0 | } |
1154 | 0 | #endif |
1155 | 0 | free(path); |
1156 | 0 | return TSK_OK; |
1157 | 0 | } |
1158 | | |
1159 | | /* |
1160 | | * Find the max inum in the logical image |
1161 | | * |
1162 | | * @param logical_fs_info The logical file system |
1163 | | * |
1164 | | * @return The max inum, or LOGICAL_INVALID_INUM if an error occurred |
1165 | | */ |
1166 | | static TSK_INUM_T |
1167 | 0 | find_max_inum(LOGICALFS_INFO *logical_fs_info) { |
1168 | 0 |
|
1169 | 0 | // Create the struct that holds search params and results |
1170 | 0 | LOGICALFS_SEARCH_HELPER *search_helper = create_max_inum_search_helper(); |
1171 | 0 | if (search_helper == NULL) { |
1172 | 0 | return LOGICAL_INVALID_INUM; |
1173 | 0 | } |
1174 | 0 |
|
1175 | 0 | // Run the search to get the maximum directory inum |
1176 | 0 | TSK_INUM_T last_assigned_inum = logical_fs_info->fs_info.root_inum; |
1177 | 0 | TSK_RETVAL_ENUM result = search_directory_recursive(logical_fs_info, logical_fs_info->base_path, &last_assigned_inum, NULL, LOGICAL_INVALID_INUM, search_helper); |
1178 | 0 | free_search_helper(search_helper); |
1179 | 0 |
|
1180 | 0 | if (result != TSK_OK) { |
1181 | 0 | return LOGICAL_INVALID_INUM; |
1182 | 0 | } |
1183 | 0 |
|
1184 | 0 | // The maximum inum will be the inum of the last file in that folder. We don't care which file it is, |
1185 | 0 | // so just getting a count is sufficient. First we need the path on disk corresponding to the last |
1186 | 0 | // directory inum. |
1187 | 0 | TSK_TCHAR* path = load_path_from_inum(logical_fs_info, last_assigned_inum); |
1188 | 0 | if (path == NULL) { |
1189 | 0 | return LOGICAL_INVALID_INUM; |
1190 | 0 | } |
1191 | 0 |
|
1192 | 0 | // Finally we need to get a count of files in that last folder. The max inum is the |
1193 | 0 | // folder inum plus the number of files (if none, it'll just be the folder inum). |
1194 | 0 | #ifdef TSK_WIN32 |
1195 | 0 | vector<wstring> file_names; |
1196 | 0 | vector<wstring> dir_names; |
1197 | 0 | if (TSK_OK != load_dir_and_file_lists_win(path, file_names, dir_names, LOGICALFS_LOAD_FILES_ONLY)) { |
1198 | 0 | free(path); |
1199 | 0 | return LOGICAL_INVALID_INUM; |
1200 | 0 | } |
1201 | 0 | #else |
1202 | 0 | vector<string> file_names; |
1203 | 0 | vector<string> dir_names; |
1204 | 0 | #endif |
1205 | 0 | free(path); |
1206 | 0 | last_assigned_inum += file_names.size(); |
1207 | 0 | return last_assigned_inum; |
1208 | 0 | } |
1209 | | |
1210 | | /* |
1211 | | * Find the inum corresponding to the given path |
1212 | | * |
1213 | | * @param logical_fs_info The logical file system |
1214 | | * @param a_addr The inum to search for |
1215 | | * @param base_path Will be loaded with path corresponding to the inum |
1216 | | * @param base_path_len Size of base_path |
1217 | | * |
1218 | | * @return The corresponding inum, or LOGICAL_INVALID_INUM if an error occurs |
1219 | | */ |
1220 | | static TSK_INUM_T |
1221 | | #ifdef TSK_WIN32 |
1222 | | get_inum_from_directory_path(LOGICALFS_INFO *logical_fs_info, TSK_TCHAR *base_path, wstring& dir_path) { |
1223 | | #else |
1224 | 0 | get_inum_from_directory_path(LOGICALFS_INFO *logical_fs_info, TSK_TCHAR *base_path, string& dir_path) { |
1225 | 0 | #endif |
1226 | 0 |
|
1227 | 0 | // Get the full path on disk by combining the base path for the logical image with the relative path in dir_path |
1228 | 0 | size_t len = TSTRLEN(base_path) + dir_path.length() + 1; |
1229 | 0 | TSK_TCHAR *path_buf = (TSK_TCHAR*)tsk_malloc(sizeof(TSK_TCHAR) *(len + 2)); |
1230 | 0 | TSTRNCPY(path_buf, base_path, TSTRLEN(base_path) + 1); |
1231 | 0 | #ifdef TSK_WIN32 |
1232 | 0 | TSTRNCAT(path_buf, L"\\", 2); |
1233 | 0 | #else |
1234 | 0 | TSTRNCAT(path_buf, "/", 2); |
1235 | 0 | #endif |
1236 | 0 | TSTRNCAT(path_buf, dir_path.c_str(), TSTRLEN(dir_path.c_str()) + 1); |
1237 | 0 |
|
1238 | 0 | // Default starting position for search is the base folder |
1239 | 0 | TSK_INUM_T starting_inum = logical_fs_info->fs_info.root_inum; |
1240 | 0 | const TSK_TCHAR *starting_path = logical_fs_info->base_path; |
1241 | 0 |
|
1242 | 0 | // See how close we can get using the cache |
1243 | 0 | TSK_TCHAR *cache_path = NULL; |
1244 | 0 | TSK_INUM_T cache_inum = LOGICAL_INVALID_INUM; |
1245 | 0 | TSK_TCHAR* sibling_name = NULL; |
1246 | 0 | TSK_INUM_T sibling_inum = LOGICAL_INVALID_INUM; |
1247 | 0 |
|
1248 | 0 | TSK_RETVAL_ENUM result = find_closest_path_match_in_cache(logical_fs_info, path_buf, &cache_path, &cache_inum); |
1249 | 0 | if (result != TSK_OK) { |
1250 | 0 | return LOGICAL_INVALID_INUM; |
1251 | 0 | } |
1252 | 0 | if (cache_inum != LOGICAL_INVALID_INUM) { |
1253 | 0 | if (TSTRCMP(path_buf, cache_path) == 0) { |
1254 | 0 | // We found an exact match - no need to do a search |
1255 | 0 | free(cache_path); |
1256 | 0 | return cache_inum; |
1257 | 0 | } |
1258 | 0 | // Otherwise, we at least have a better place to start the search |
1259 | 0 | starting_inum = cache_inum; |
1260 | 0 | starting_path = cache_path; |
1261 | 0 |
|
1262 | 0 | // If the starting path is the parent of our target, check if there's an entry in the cache for another |
1263 | 0 | // folder directly under the parent that comes before our target. This will primarily speed up opening large directories |
1264 | 0 | // since each folder is looked up in order. |
1265 | 0 | // Ex: |
1266 | 0 | // Target dir: /a/b/50 |
1267 | 0 | // Best match: /a/b |
1268 | 0 | // Check if we have something like /a/b/40 in the cache (we can't use anything deeper like /a/b/40/1 - has to be at the same level) |
1269 | 0 | const TSK_TCHAR* rest = get_end_of_path(path_buf, starting_path); |
1270 | 0 | bool haveParentFolder = !contains_folder_separator(rest); |
1271 | 0 | if (haveParentFolder) { |
1272 | 0 | find_closest_sibling_match_in_cache(logical_fs_info, path_buf, starting_path, starting_inum, &sibling_name, &sibling_inum); |
1273 | 0 | } |
1274 | 0 | } |
1275 | 0 |
|
1276 | 0 | // Create the struct that holds search params and results |
1277 | 0 | LOGICALFS_SEARCH_HELPER *search_helper = create_path_search_helper(path_buf); |
1278 | 0 | free(path_buf); |
1279 | 0 | if (search_helper == NULL) { |
1280 | 0 | if (cache_path != NULL) { |
1281 | 0 | free(cache_path); |
1282 | 0 | } |
1283 | 0 | if (sibling_name != NULL) { |
1284 | 0 | free(sibling_name); |
1285 | 0 | } |
1286 | 0 | return LOGICAL_INVALID_INUM; |
1287 | 0 | } |
1288 | 0 |
|
1289 | 0 | // Run the search |
1290 | 0 | TSK_INUM_T last_assigned_inum = logical_fs_info->fs_info.root_inum; |
1291 | 0 | // use last_assigned_inum variable on non-win32 builds to prevent error |
1292 | 0 | (void)last_assigned_inum; |
1293 | 0 | result = search_directory_recursive(logical_fs_info, starting_path, &starting_inum, sibling_name, sibling_inum, search_helper); |
1294 | 0 |
|
1295 | 0 | if (cache_path != NULL) { |
1296 | 0 | free(cache_path); |
1297 | 0 | } |
1298 | 0 |
|
1299 | 0 | if (sibling_name != NULL) { |
1300 | 0 | free(sibling_name); |
1301 | 0 | } |
1302 | 0 |
|
1303 | 0 | // Return the target inum if found |
1304 | 0 | TSK_INUM_T target_inum; |
1305 | 0 | if ((result != TSK_OK) || (!search_helper->target_found)) { |
1306 | 0 | target_inum = LOGICAL_INVALID_INUM; |
1307 | 0 | } |
1308 | 0 | else { |
1309 | 0 | target_inum = search_helper->found_inum; |
1310 | 0 | } |
1311 | 0 | free_search_helper(search_helper); |
1312 | 0 | return target_inum; |
1313 | 0 | } |
1314 | | |
1315 | | static TSK_RETVAL_ENUM |
1316 | | logicalfs_dir_open_meta( |
1317 | | TSK_FS_INFO *a_fs, |
1318 | | TSK_FS_DIR ** a_fs_dir, |
1319 | | TSK_INUM_T a_addr, |
1320 | | [[maybe_unused]] int recursion_depth) |
1321 | 0 | { |
1322 | 0 | TSK_FS_DIR *fs_dir; |
1323 | 0 | LOGICALFS_INFO *logical_fs_info = (LOGICALFS_INFO*)a_fs; |
1324 | 0 |
|
1325 | 0 | if (a_fs_dir == NULL) { |
1326 | 0 | tsk_error_reset(); |
1327 | 0 | tsk_error_set_errno(TSK_ERR_FS_ARG); |
1328 | 0 | tsk_error_set_errstr("logicalfs_dir_open_meta: NULL fs_dir argument given"); |
1329 | 0 | return TSK_ERR; |
1330 | 0 | } |
1331 | 0 | if ((a_addr & LOGICAL_INUM_FILE_MASK) != 0) { |
1332 | 0 | tsk_error_reset(); |
1333 | 0 | tsk_error_set_errno(TSK_ERR_FS_ARG); |
1334 | 0 | tsk_error_set_errstr("logicalfs_dir_open_meta: Inode %" PRIuINUM " is not a directory", a_addr); |
1335 | 0 | return TSK_ERR; |
1336 | 0 | } |
1337 | 0 | if (a_addr == LOGICAL_INVALID_INUM) { |
1338 | 0 | tsk_error_reset(); |
1339 | 0 | tsk_error_set_errno(TSK_ERR_FS_ARG); |
1340 | 0 | tsk_error_set_errstr("logicalfs_dir_open_meta: Inode %" PRIuINUM " is not valid", a_addr); |
1341 | 0 | return TSK_ERR; |
1342 | 0 | } |
1343 | 0 |
|
1344 | 0 | fs_dir = *a_fs_dir; |
1345 | 0 | if (fs_dir) { |
1346 | 0 | tsk_fs_dir_reset(fs_dir); |
1347 | 0 | fs_dir->addr = a_addr; |
1348 | 0 | } |
1349 | 0 | else if ((*a_fs_dir = fs_dir = tsk_fs_dir_alloc(a_fs, a_addr, 128)) == NULL) { |
1350 | 0 | return TSK_ERR; |
1351 | 0 | } |
1352 | 0 |
|
1353 | 0 | // Load the base path for the given meta address |
1354 | 0 | TSK_TCHAR* path = load_path_from_inum(logical_fs_info, a_addr); |
1355 | 0 | if (path == NULL) { |
1356 | 0 | return TSK_ERR; |
1357 | 0 | } |
1358 | 0 |
|
1359 | 0 | #ifdef TSK_WIN32 |
1360 | 0 | // Populate the fs_file field |
1361 | 0 | WIN32_FIND_DATAW fd; |
1362 | 0 | HANDLE hFind; |
1363 | 0 | if (TSTRLEN(path) < MAX_PATH) { |
1364 | 0 | hFind = ::FindFirstFileW(path, &fd); |
1365 | 0 | } |
1366 | 0 | else { |
1367 | 0 | TCHAR absPath[LOGICAL_MAX_PATH_UNICODE + 4]; |
1368 | 0 | TSTRNCPY(absPath, L"\\\\?\\", 4); |
1369 | 0 | int absPathLen = GetFullPathNameW(path, LOGICAL_MAX_PATH_UNICODE, &(absPath[4]), NULL); |
1370 | 0 | if (absPathLen <= 0) { |
1371 | 0 | tsk_error_reset(); |
1372 | 0 | tsk_error_set_errno(TSK_ERR_FS_GENFS); |
1373 | 0 | tsk_error_set_errstr("logicalfs_dir_open_meta: Error looking up contents of directory (path too long) %" PRIttocTSK, path); |
1374 | 0 | free(path); |
1375 | 0 | return TSK_ERR; |
1376 | 0 | } |
1377 | 0 | hFind = ::FindFirstFileW(absPath, &fd); |
1378 | 0 | } |
1379 | 0 | if (hFind != INVALID_HANDLE_VALUE) { |
1380 | 0 |
|
1381 | 0 | if ((fs_dir->fs_file = tsk_fs_file_alloc(a_fs)) == NULL) { |
1382 | 0 | free(path); |
1383 | 0 | return TSK_ERR; |
1384 | 0 | } |
1385 | 0 |
|
1386 | 0 | if ((fs_dir->fs_file->meta = tsk_fs_meta_alloc(0)) == NULL) { |
1387 | 0 | free(path); |
1388 | 0 | return TSK_ERR; |
1389 | 0 | } |
1390 | 0 |
|
1391 | 0 | TSK_RETVAL_ENUM result = populate_fs_file_from_win_find_data(&fd, fs_dir->fs_file); |
1392 | 0 | ::FindClose(hFind); |
1393 | 0 |
|
1394 | 0 | if (result != TSK_OK) { |
1395 | 0 | // Error message already set |
1396 | 0 | return TSK_ERR; |
1397 | 0 | } |
1398 | 0 |
|
1399 | 0 | } |
1400 | 0 | else { |
1401 | 0 | tsk_error_reset(); |
1402 | 0 | tsk_error_set_errno(TSK_ERR_FS_GENFS); |
1403 | 0 | tsk_error_set_errstr("logicalfs_dir_open_meta: Error loading directory %" PRIttocTSK, path); |
1404 | 0 | free(path); |
1405 | 0 | return TSK_ERR; |
1406 | 0 | } |
1407 | 0 | #endif |
1408 | 0 |
|
1409 | 0 | #ifdef TSK_WIN32 |
1410 | 0 | vector<wstring> file_names; |
1411 | 0 | vector<wstring> dir_names; |
1412 | 0 | if (TSK_OK != load_dir_and_file_lists_win(path, file_names, dir_names, LOGICALFS_LOAD_ALL)) { |
1413 | 0 | // Error message already set |
1414 | 0 | free(path); |
1415 | 0 | return TSK_ERR; |
1416 | 0 | } |
1417 | 0 | #else |
1418 | 0 | vector<string> file_names; |
1419 | 0 | vector<string> dir_names; |
1420 | 0 | #endif |
1421 | 0 |
|
1422 | 0 | // Sort the files and directories |
1423 | 0 | sort(file_names.begin(), file_names.end(), case_insensitive_compare); |
1424 | 0 | sort(dir_names.begin(), dir_names.end(), case_insensitive_compare); |
1425 | 0 |
|
1426 | 0 | // Add the folders |
1427 | 0 | for (auto it = begin(dir_names); it != end(dir_names); ++it) { |
1428 | 0 | TSK_INUM_T dir_inum = get_inum_from_directory_path(logical_fs_info, path, *it); |
1429 | 0 | if (dir_inum == LOGICAL_INVALID_INUM) { |
1430 | 0 | tsk_error_reset(); |
1431 | 0 | tsk_error_set_errno(TSK_ERR_FS_GENFS); |
1432 | 0 | tsk_error_set_errstr("logicalfs_dir_open_meta: Error looking up inum from path"); |
1433 | 0 | return TSK_ERR; |
1434 | 0 | } |
1435 | 0 |
|
1436 | 0 | TSK_FS_NAME *fs_name; |
1437 | 0 |
|
1438 | 0 | #ifdef TSK_WIN32 |
1439 | 0 | char *utf8Name = convert_wide_string_to_utf8(it->c_str()); |
1440 | 0 | if (utf8Name == NULL) { |
1441 | 0 | tsk_error_reset(); |
1442 | 0 | tsk_error_set_errno(TSK_ERR_FS_UNICODE); |
1443 | 0 | tsk_error_set_errstr("logicalfs_dir_open_meta: Error converting wide string"); |
1444 | 0 | return TSK_ERR; |
1445 | 0 | } |
1446 | 0 | size_t name_len = strlen(utf8Name); |
1447 | 0 | #else |
1448 | 0 | size_t name_len = strlen(it->c_str()); |
1449 | 0 | #endif |
1450 | 0 | if ((fs_name = tsk_fs_name_alloc(name_len, 0)) == NULL) { |
1451 | 0 | #ifdef TSK_WIN32 |
1452 | 0 | free(utf8Name); |
1453 | 0 | #endif |
1454 | 0 | free(path); |
1455 | 0 | return TSK_ERR; |
1456 | 0 | } |
1457 | 0 |
|
1458 | 0 | fs_name->type = TSK_FS_NAME_TYPE_DIR; |
1459 | 0 | fs_name->flags = TSK_FS_NAME_FLAG_ALLOC; |
1460 | 0 | fs_name->par_addr = a_addr; |
1461 | 0 | fs_name->meta_addr = dir_inum; |
1462 | 0 | #ifdef TSK_WIN32 |
1463 | 0 | strncpy(fs_name->name, utf8Name, name_len + 1); |
1464 | 0 | free(utf8Name); |
1465 | 0 | #else |
1466 | 0 | strncpy(fs_name->name, it->c_str(), name_len + 1); |
1467 | 0 | #endif |
1468 | 0 | if (tsk_fs_dir_add(fs_dir, fs_name)) { |
1469 | 0 | tsk_fs_name_free(fs_name); |
1470 | 0 | free(path); |
1471 | 0 | return TSK_ERR; |
1472 | 0 | } |
1473 | 0 | tsk_fs_name_free(fs_name); |
1474 | 0 | } |
1475 | 0 | free(path); |
1476 | 0 |
|
1477 | 0 | // Add the files |
1478 | 0 | TSK_INUM_T file_inum = a_addr | 1; // First inum is directory inum in the high part, 1 in the low part |
1479 | 0 | for (auto it = begin(file_names); it != end(file_names); ++it) { |
1480 | 0 | TSK_FS_NAME *fs_name; |
1481 | 0 | size_t name_len; |
1482 | 0 | #ifdef TSK_WIN32 |
1483 | 0 | char *utf8Name = convert_wide_string_to_utf8(it->c_str()); |
1484 | 0 | if (utf8Name == NULL) { |
1485 | 0 | tsk_error_reset(); |
1486 | 0 | tsk_error_set_errno(TSK_ERR_FS_UNICODE); |
1487 | 0 | tsk_error_set_errstr("logicalfs_dir_open_meta: Error converting wide string"); |
1488 | 0 | return TSK_ERR; |
1489 | 0 | } |
1490 | 0 | name_len = strlen(utf8Name); |
1491 | 0 | #else |
1492 | 0 | name_len = it->length(); |
1493 | 0 | #endif |
1494 | 0 | if ((fs_name = tsk_fs_name_alloc(name_len, 0)) == NULL) { |
1495 | 0 | #ifdef TSK_WIN32 |
1496 | 0 | free(utf8Name); |
1497 | 0 | #endif |
1498 | 0 | return TSK_ERR; |
1499 | 0 | } |
1500 | 0 |
|
1501 | 0 | fs_name->type = TSK_FS_NAME_TYPE_REG; |
1502 | 0 | fs_name->flags = TSK_FS_NAME_FLAG_ALLOC; |
1503 | 0 | fs_name->par_addr = a_addr; |
1504 | 0 | fs_name->meta_addr = file_inum; |
1505 | 0 | #ifdef TSK_WIN32 |
1506 | 0 | strncpy(fs_name->name, utf8Name, name_len + 1); |
1507 | 0 | free(utf8Name); |
1508 | 0 | #else |
1509 | 0 | strncpy(fs_name->name, it->c_str(), name_len + 1); |
1510 | 0 | #endif |
1511 | 0 | if (tsk_fs_dir_add(fs_dir, fs_name)) { |
1512 | 0 | tsk_fs_name_free(fs_name); |
1513 | 0 | return TSK_ERR; |
1514 | 0 | } |
1515 | 0 | tsk_fs_name_free(fs_name); |
1516 | 0 |
|
1517 | 0 | file_inum++; |
1518 | 0 | } |
1519 | 0 |
|
1520 | 0 | return TSK_OK; |
1521 | 0 | } |
1522 | | |
1523 | | static uint8_t |
1524 | | logicalfs_load_attrs(TSK_FS_FILE *file) |
1525 | 0 | { |
1526 | 0 | if (file == NULL || file->meta == NULL || file->fs_info == NULL) |
1527 | 0 | { |
1528 | 0 | tsk_error_set_errno(TSK_ERR_FS_ARG); |
1529 | 0 | tsk_error_set_errstr |
1530 | 0 | ("logicalfs_load_attrs: called with NULL pointers"); |
1531 | 0 | return 1; |
1532 | 0 | } |
1533 | 0 |
|
1534 | 0 | TSK_FS_META* meta = file->meta; |
1535 | 0 |
|
1536 | 0 | // See if we have already loaded the runs |
1537 | 0 | if ((meta->attr != NULL) |
1538 | 0 | && (meta->attr_state == TSK_FS_META_ATTR_STUDIED)) { |
1539 | 0 | return 0; |
1540 | 0 | } |
1541 | 0 | else if (meta->attr_state == TSK_FS_META_ATTR_ERROR) { |
1542 | 0 | return 1; |
1543 | 0 | } |
1544 | 0 | else if (meta->attr != NULL) { |
1545 | 0 | tsk_fs_attrlist_markunused(meta->attr); |
1546 | 0 | } |
1547 | 0 | else if (meta->attr == NULL) { |
1548 | 0 | meta->attr = tsk_fs_attrlist_alloc(); |
1549 | 0 | } |
1550 | 0 |
|
1551 | 0 | TSK_FS_ATTR_RUN *data_run; |
1552 | 0 | TSK_FS_ATTR *attr = tsk_fs_attrlist_getnew(meta->attr, TSK_FS_ATTR_NONRES); |
1553 | 0 | if (attr == NULL) { |
1554 | 0 | meta->attr_state = TSK_FS_META_ATTR_ERROR; |
1555 | 0 | return 1; |
1556 | 0 | } |
1557 | 0 |
|
1558 | 0 | if (meta->size == 0) { |
1559 | 0 | data_run = NULL; |
1560 | 0 | } |
1561 | 0 | else { |
1562 | 0 | data_run = tsk_fs_attr_run_alloc(); |
1563 | 0 | if (data_run == NULL) { |
1564 | 0 | meta->attr_state = TSK_FS_META_ATTR_ERROR; |
1565 | 0 | return 1; |
1566 | 0 | } |
1567 | 0 |
|
1568 | 0 | data_run->next = NULL; |
1569 | 0 | data_run->offset = 0; |
1570 | 0 | data_run->addr = 0; |
1571 | 0 | data_run->len = (meta->size + file->fs_info->block_size - 1) / file->fs_info->block_size; |
1572 | 0 | data_run->flags = TSK_FS_ATTR_RUN_FLAG_NONE; |
1573 | 0 | } |
1574 | 0 |
|
1575 | 0 | if (tsk_fs_attr_set_run(file, attr, NULL, NULL, |
1576 | 0 | TSK_FS_ATTR_TYPE_DEFAULT, TSK_FS_ATTR_ID_DEFAULT, |
1577 | 0 | meta->size, meta->size, |
1578 | 0 | roundup(meta->size, file->fs_info->block_size), |
1579 | 0 | (TSK_FS_ATTR_FLAG_ENUM)0, 0)) { |
1580 | 0 |
|
1581 | 0 | meta->attr_state = TSK_FS_META_ATTR_ERROR; |
1582 | 0 | return 1; |
1583 | 0 | } |
1584 | 0 |
|
1585 | 0 | // If the file has size zero, return now |
1586 | 0 | if (meta->size == 0) { |
1587 | 0 | meta->attr_state = TSK_FS_META_ATTR_STUDIED; |
1588 | 0 | return 0; |
1589 | 0 | } |
1590 | 0 |
|
1591 | 0 | // Otherwise add the data run |
1592 | 0 | if (0 != tsk_fs_attr_add_run(file->fs_info, attr, data_run)) { |
1593 | 0 | return 1; |
1594 | 0 | } |
1595 | 0 | meta->attr_state = TSK_FS_META_ATTR_STUDIED; |
1596 | 0 |
|
1597 | 0 | return 0; |
1598 | 0 | } |
1599 | | |
1600 | | /* |
1601 | | * Reads a block from a logical file. If the file is not long enough to complete the block, |
1602 | | * null bytes are padded on to the end of the bytes read. |
1603 | | * |
1604 | | * @param a_fs File system |
1605 | | * @param a_fs_file File being read |
1606 | | * @param a_offset Starting offset |
1607 | | * @param buf Holds bytes read from the file (should be the size of a block) |
1608 | | * |
1609 | | * @return Size of the block or -1 on error. |
1610 | | */ |
1611 | | ssize_t |
1612 | 0 | logicalfs_read_block(TSK_FS_INFO *a_fs, TSK_FS_FILE *a_fs_file, TSK_DADDR_T a_block_num, char *buf) { |
1613 | |
|
1614 | 0 | if ((a_fs == NULL) || (a_fs_file == NULL) || (a_fs_file->meta == NULL)) { |
1615 | 0 | tsk_error_reset(); |
1616 | 0 | tsk_error_set_errno(TSK_ERR_FS_ARG); |
1617 | 0 | tsk_error_set_errstr("logical_fs_read_block: Called with null arguments"); |
1618 | 0 | return -1; |
1619 | 0 | } |
1620 | | |
1621 | 0 | if (a_fs->ftype != TSK_FS_TYPE_LOGICAL) { |
1622 | 0 | tsk_error_reset(); |
1623 | 0 | tsk_error_set_errno(TSK_ERR_FS_ARG); |
1624 | 0 | tsk_error_set_errstr("logical_fs_read_block: Called with files system that is not TSK_FS_TYPE_LOGICAL"); |
1625 | 0 | return -1; |
1626 | 0 | } |
1627 | | |
1628 | 0 | unsigned int block_size = a_fs->block_size; |
1629 | | |
1630 | | // The caching used for logical file blocks is simpler than |
1631 | | // the version for images in img_io.c because we will always store complete |
1632 | | // blocks - the block size for logical files is set to the same size as |
1633 | | // the image cache. So each block in the cache will correspond to a |
1634 | | // file inum and block number. |
1635 | | |
1636 | | // cache_lock is used for both the cache in IMG_INFO and |
1637 | | // the shared variables in the img type specific INFO structs. |
1638 | | // Grab it now so that it is held before any reads. |
1639 | 0 | IMG_LOGICAL_INFO* logical_img_info = (IMG_LOGICAL_INFO*)a_fs->img_info; |
1640 | 0 | LOGICALFS_INFO *logical_fs_info = (LOGICALFS_INFO*)a_fs; |
1641 | |
|
1642 | 0 | auto cache = logical_img_info->cache; |
1643 | 0 | cache->lock(); |
1644 | 0 | std::unique_ptr<LegacyCache, decltype(&unlock)> lock_guard(cache, unlock); |
1645 | | |
1646 | | // Check if this block is in the cache |
1647 | 0 | int cache_next = 0; // index to lowest age cache (to use next) |
1648 | 0 | bool match_found = 0; |
1649 | 0 | for (int cache_index = 0; cache_index < TSK_IMG_INFO_CACHE_NUM; cache_index++) { |
1650 | | |
1651 | | // Look into the in-use cache entries |
1652 | 0 | if (cache->cache_len[cache_index] > 0) { |
1653 | 0 | if ((logical_img_info->cache_inum[cache_index] == a_fs_file->meta->addr) |
1654 | | // check if non-negative and cast to uint to avoid signed/unsigned comparison warning |
1655 | 0 | && (cache->cache_off[cache_index] >= 0 && (TSK_DADDR_T)cache->cache_off[cache_index] == a_block_num)) { |
1656 | | // We found it |
1657 | 0 | memcpy(buf, cache->cache[cache_index], block_size); |
1658 | 0 | match_found = true; |
1659 | | |
1660 | | // reset its "age" since it was useful |
1661 | 0 | cache->cache_age[cache_index] = LOGICAL_IMG_CACHE_AGE; |
1662 | | |
1663 | | // we don't break out of the loop so that we update all ages |
1664 | 0 | } |
1665 | 0 | else { |
1666 | | // Decrease its "age" since it was not useful. |
1667 | | // We don't let used ones go below 1 so that they are not |
1668 | | // confused with entries that have never been used. |
1669 | 0 | if (cache->cache_age[cache_index] > 2) { |
1670 | 0 | cache->cache_age[cache_index]--; |
1671 | 0 | } |
1672 | | |
1673 | | // See if this is the most eligible replacement |
1674 | 0 | if ((cache->cache_len[cache_next] > 0) |
1675 | 0 | && (cache->cache_age[cache_index] < |
1676 | 0 | cache->cache_age[cache_next])) { |
1677 | 0 | cache_next = cache_index; |
1678 | 0 | } |
1679 | 0 | } |
1680 | 0 | } |
1681 | 0 | } |
1682 | | |
1683 | | // If we found the block in the cache, we're done |
1684 | 0 | if (match_found) { |
1685 | 0 | return block_size; |
1686 | 0 | } |
1687 | | |
1688 | | // See if this file is already open |
1689 | 0 | LOGICAL_FILE_HANDLE_CACHE* file_handle_entry = NULL; |
1690 | 0 | for (int i = 0; i < LOGICAL_FILE_HANDLE_CACHE_LEN; i++) { |
1691 | 0 | if (logical_img_info->file_handle_cache[i].inum == a_fs_file->meta->addr) { |
1692 | | // File is already open |
1693 | 0 | file_handle_entry = &(logical_img_info->file_handle_cache[i]); |
1694 | 0 | } |
1695 | 0 | } |
1696 | | |
1697 | | // If we didn't find it, open the file and save to the cache |
1698 | 0 | if (file_handle_entry == NULL) { |
1699 | | // Load the path |
1700 | 0 | TSK_TCHAR* path = load_path_from_inum(logical_fs_info, a_fs_file->meta->addr); |
1701 | |
|
1702 | | #ifdef TSK_WIN32 |
1703 | | // Open the file |
1704 | | HANDLE fd; |
1705 | | if (TSTRLEN(path) < MAX_PATH) { |
1706 | | fd = CreateFileW(path, FILE_READ_DATA, |
1707 | | FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 0, |
1708 | | NULL); |
1709 | | } |
1710 | | else { |
1711 | | TCHAR absPath[LOGICAL_MAX_PATH_UNICODE + 4]; |
1712 | | TSTRNCPY(absPath, L"\\\\?\\", 4); |
1713 | | int absPathLen = GetFullPathNameW(path, LOGICAL_MAX_PATH_UNICODE, &(absPath[4]), NULL); |
1714 | | if (absPathLen <= 0) { |
1715 | | tsk_error_reset(); |
1716 | | tsk_error_set_errno(TSK_ERR_FS_GENFS); |
1717 | | tsk_error_set_errstr("logicalfs_read_block: Error looking up contents of directory (path too long) %" PRIttocTSK, path); |
1718 | | free(path); |
1719 | | return TSK_ERR; |
1720 | | } |
1721 | | fd = CreateFileW(absPath, FILE_READ_DATA, |
1722 | | FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 0, |
1723 | | NULL); |
1724 | | } |
1725 | | if (fd == INVALID_HANDLE_VALUE) { |
1726 | | int lastError = (int)GetLastError(); |
1727 | | tsk_error_reset(); |
1728 | | tsk_error_set_errno(TSK_ERR_FS_READ); |
1729 | | tsk_error_set_errstr("logical_fs_read_block: file \"%" PRIttocTSK |
1730 | | "\" - %d", path, lastError); |
1731 | | return -1; |
1732 | | } |
1733 | | #else |
1734 | 0 | int fd = 0; |
1735 | | // use path variable on non-win32 builds to prevent error |
1736 | 0 | (void)path; |
1737 | 0 | #endif |
1738 | | |
1739 | | // Set up this cache entry |
1740 | 0 | file_handle_entry = &(logical_img_info->file_handle_cache[logical_img_info->next_file_handle_cache_slot]); |
1741 | 0 | if (file_handle_entry->fd != 0) { |
1742 | | // Close the current file handle |
1743 | | #ifdef TSK_WIN32 |
1744 | | CloseHandle(file_handle_entry->fd); |
1745 | | #endif |
1746 | 0 | } |
1747 | 0 | file_handle_entry->fd = fd; |
1748 | 0 | file_handle_entry->inum = a_fs_file->meta->addr; |
1749 | 0 | file_handle_entry->seek_pos = 0; |
1750 | | |
1751 | | // Set up the next cache entry to use |
1752 | 0 | logical_img_info->next_file_handle_cache_slot++; |
1753 | 0 | if (logical_img_info->next_file_handle_cache_slot >= LOGICAL_FILE_HANDLE_CACHE_LEN) { |
1754 | 0 | logical_img_info->next_file_handle_cache_slot = 0; |
1755 | 0 | } |
1756 | 0 | } |
1757 | | |
1758 | | // Seek to the starting offset (if necessary) |
1759 | 0 | TSK_OFF_T offset_to_read = a_block_num * block_size; |
1760 | 0 | if (offset_to_read != file_handle_entry->seek_pos) { |
1761 | | #ifdef TSK_WIN32 |
1762 | | LARGE_INTEGER li; |
1763 | | li.QuadPart = a_block_num * block_size; |
1764 | | |
1765 | | li.LowPart = SetFilePointer(file_handle_entry->fd, li.LowPart, |
1766 | | &li.HighPart, FILE_BEGIN); |
1767 | | |
1768 | | if ((li.LowPart == INVALID_SET_FILE_POINTER) && |
1769 | | (GetLastError() != NO_ERROR)) { |
1770 | | |
1771 | | int lastError = (int)GetLastError(); |
1772 | | tsk_error_reset(); |
1773 | | tsk_error_set_errno(TSK_ERR_IMG_SEEK); |
1774 | | tsk_error_set_errstr("logical_fs_read_block: file addr %" PRIuINUM |
1775 | | " offset %" PRIdOFF " seek - %d", |
1776 | | a_fs_file->meta->addr, a_block_num, lastError); |
1777 | | return -1; |
1778 | | } |
1779 | | #endif |
1780 | 0 | file_handle_entry->seek_pos = offset_to_read; |
1781 | 0 | } |
1782 | | |
1783 | | // Read the data |
1784 | 0 | unsigned int len_to_read; |
1785 | 0 | if (((a_block_num + 1) * block_size) <= (unsigned long long)a_fs_file->meta->size) { |
1786 | | // If the file is large enough to read the entire block, then try to do so |
1787 | 0 | len_to_read = block_size; |
1788 | 0 | } |
1789 | 0 | else { |
1790 | | // Otherwise, we expect to only be able to read a smaller number of bytes |
1791 | 0 | len_to_read = a_fs_file->meta->size % block_size; |
1792 | 0 | memset(buf, 0, block_size); |
1793 | 0 | } |
1794 | |
|
1795 | | #ifdef TSK_WIN32 |
1796 | | DWORD nread; |
1797 | | if (FALSE == ReadFile(file_handle_entry->fd, buf, (DWORD)len_to_read, &nread, NULL)) { |
1798 | | int lastError = GetLastError(); |
1799 | | tsk_error_reset(); |
1800 | | tsk_error_set_errno(TSK_ERR_IMG_READ); |
1801 | | tsk_error_set_errstr("logicalfs_read_block: file addr %" PRIuINUM |
1802 | | " offset: %" PRIu64 " read len: %u - %d", |
1803 | | a_fs_file->meta->addr, a_block_num, block_size, |
1804 | | lastError); |
1805 | | return -1; |
1806 | | } |
1807 | | file_handle_entry->seek_pos += nread; |
1808 | | #else |
1809 | | // otherwise, not used; ensure used to prevent warning |
1810 | 0 | (void)len_to_read; |
1811 | 0 | #endif |
1812 | | |
1813 | | // Copy the block into the cache |
1814 | 0 | memcpy(cache->cache[cache_next], buf, block_size); |
1815 | 0 | cache->cache_len[cache_next] = block_size; |
1816 | 0 | cache->cache_age[cache_next] = LOGICAL_IMG_CACHE_AGE; |
1817 | 0 | cache->cache_off[cache_next] = a_block_num; |
1818 | 0 | logical_img_info->cache_inum[cache_next] = a_fs_file->meta->addr; |
1819 | | |
1820 | | // If we didn't read the expected number of bytes, return an error |
1821 | | #ifdef TSK_WIN32 |
1822 | | if (nread != len_to_read) { |
1823 | | int lastError = GetLastError(); |
1824 | | tsk_error_reset(); |
1825 | | tsk_error_set_errno(TSK_ERR_IMG_READ); |
1826 | | tsk_error_set_errstr("logicalfs_read_block: file addr %" PRIuINUM |
1827 | | " offset: %" PRIdOFF " read len: %u - %d", |
1828 | | a_fs_file->meta->addr, a_block_num, block_size, |
1829 | | lastError); |
1830 | | return -1; |
1831 | | } |
1832 | | #endif |
1833 | |
|
1834 | 0 | return block_size; |
1835 | 0 | } |
1836 | | |
1837 | | /* |
1838 | | * Reads data from a logical file. |
1839 | | * |
1840 | | * @param a_fs File system |
1841 | | * @param a_fs_file File being read |
1842 | | * @param a_offset Starting offset |
1843 | | * @param a_len Length to read |
1844 | | * @param a_buf Holds bytes read from the file (should have length at least a_len) |
1845 | | * |
1846 | | * @return Number of bytes read or -1 on error. |
1847 | | */ |
1848 | | ssize_t |
1849 | 0 | logicalfs_read(TSK_FS_INFO *a_fs, TSK_FS_FILE *a_fs_file, TSK_DADDR_T a_offset, size_t a_len, char *a_buf) { |
1850 | |
|
1851 | 0 | TSK_DADDR_T current_block_num = a_offset / a_fs->block_size; |
1852 | 0 | char block_buffer[LOGICAL_BLOCK_SIZE]; |
1853 | 0 | size_t cnt; |
1854 | 0 | char *dest = a_buf; |
1855 | 0 | size_t bytes_left = a_len; |
1856 | 0 | size_t bytes_read = 0; |
1857 | 0 | size_t filler_len = 0; |
1858 | |
|
1859 | 0 | if ((a_fs == NULL) || (a_fs_file == NULL) || (a_fs_file->meta == NULL)) { |
1860 | 0 | tsk_error_reset(); |
1861 | 0 | tsk_error_set_errno(TSK_ERR_FS_ARG); |
1862 | 0 | tsk_error_set_errstr("logicalfs_read: Called with null arguments"); |
1863 | 0 | return -1; |
1864 | 0 | } |
1865 | | |
1866 | 0 | if (a_offset >= (TSK_DADDR_T)a_fs_file->meta->size) { |
1867 | 0 | tsk_error_reset(); |
1868 | 0 | tsk_error_set_errno(TSK_ERR_FS_ARG); |
1869 | 0 | tsk_error_set_errstr("logicalfs_read: Attempted to read offset beyond end of file (file addr: %" |
1870 | 0 | PRIuINUM ", file size: %" PRIdOFF ", offset: %" PRIuDADDR ")", a_fs_file->meta->addr, a_fs_file->meta->size, a_offset); |
1871 | 0 | return -1; |
1872 | 0 | } |
1873 | | |
1874 | | // Only attempt to read to the end of the file at most |
1875 | 0 | if (a_offset + a_len > (TSK_DADDR_T)a_fs_file->meta->size) { |
1876 | 0 | bytes_left = a_fs_file->meta->size - a_offset; |
1877 | 0 | filler_len = a_offset + a_len - a_fs_file->meta->size; |
1878 | | |
1879 | | // Fill in the end of the buffer |
1880 | 0 | if (filler_len > 0) { |
1881 | 0 | memset(dest + bytes_left, 0, filler_len); |
1882 | 0 | } |
1883 | 0 | } |
1884 | | |
1885 | | // Read bytes prior to the first block boundary |
1886 | 0 | if (a_offset % a_fs->block_size != 0) { |
1887 | | // Read in the smaller of the requested length and the bytes at the end of the block |
1888 | 0 | size_t len_to_read = a_fs->block_size - (a_offset % a_fs->block_size); |
1889 | 0 | if (len_to_read > bytes_left) { |
1890 | 0 | len_to_read = bytes_left; |
1891 | 0 | } |
1892 | 0 | cnt = logicalfs_read_block(a_fs, a_fs_file, current_block_num, block_buffer); |
1893 | 0 | if (cnt != a_fs->block_size) { |
1894 | | // Error already set |
1895 | 0 | return cnt; |
1896 | 0 | } |
1897 | 0 | memcpy(dest, block_buffer + (a_offset % a_fs->block_size), len_to_read); |
1898 | 0 | dest += len_to_read; |
1899 | 0 | bytes_read += len_to_read; |
1900 | 0 | bytes_left -= len_to_read; |
1901 | 0 | current_block_num++; |
1902 | 0 | } |
1903 | | // Check if we're done |
1904 | 0 | if (bytes_left == 0) { |
1905 | 0 | return bytes_read; |
1906 | 0 | } |
1907 | | |
1908 | | // Read complete blocks |
1909 | 0 | while (bytes_left >= a_fs->block_size) { |
1910 | 0 | cnt = logicalfs_read_block(a_fs, a_fs_file, current_block_num, dest); |
1911 | 0 | if (cnt != a_fs->block_size) { |
1912 | | // Error already set |
1913 | 0 | return cnt; |
1914 | 0 | } |
1915 | 0 | dest += a_fs->block_size; |
1916 | 0 | bytes_read += a_fs->block_size; |
1917 | 0 | bytes_left -= a_fs->block_size; |
1918 | 0 | current_block_num++; |
1919 | 0 | } |
1920 | | |
1921 | | // Check if we're done |
1922 | 0 | if (bytes_left == 0) { |
1923 | 0 | return bytes_read; |
1924 | 0 | } |
1925 | | |
1926 | | // Read the final, incomplete block |
1927 | 0 | cnt = logicalfs_read_block(a_fs, a_fs_file, current_block_num, block_buffer); |
1928 | 0 | if (cnt != a_fs->block_size) { |
1929 | | // Error already set |
1930 | 0 | return cnt; |
1931 | 0 | } |
1932 | 0 | memcpy(dest, block_buffer, bytes_left); |
1933 | 0 | dest += bytes_left; |
1934 | 0 | bytes_read += bytes_left; |
1935 | |
|
1936 | 0 | return bytes_read; |
1937 | 0 | } |
1938 | | |
1939 | | /** |
1940 | | * Print details about the file system to a file handle. |
1941 | | * |
1942 | | * @param fs File system to print details on |
1943 | | * @param hFile File handle to print text to |
1944 | | * |
1945 | | * @returns 1 on error and 0 on success |
1946 | | */ |
1947 | | static uint8_t |
1948 | | logicalfs_fsstat(TSK_FS_INFO * fs, FILE * hFile) |
1949 | 0 | { |
1950 | 0 | LOGICALFS_INFO * dirfs = (LOGICALFS_INFO*)fs; |
1951 | 0 | tsk_fprintf(hFile, "FILE SYSTEM INFORMATION\n"); |
1952 | 0 | tsk_fprintf(hFile, "--------------------------------------------\n"); |
1953 | 0 |
|
1954 | 0 | tsk_fprintf(hFile, "File System Type: Logical Directory\n"); |
1955 | 0 | tsk_fprintf(hFile, |
1956 | 0 | "Base Directory Path: %" PRIttocTSK "\n", |
1957 | 0 | dirfs->base_path); |
1958 | 0 | return 0; |
1959 | 0 | } |
1960 | | |
1961 | | static uint8_t |
1962 | | logicalfs_fscheck(TSK_FS_INFO * /*fs*/, FILE * /*hFile*/) |
1963 | 0 | { |
1964 | 0 | tsk_error_reset(); |
1965 | 0 | tsk_error_set_errno(TSK_ERR_FS_UNSUPFUNC); |
1966 | 0 | tsk_error_set_errstr("fscheck not supported for logical file systems"); |
1967 | 0 | return 1; |
1968 | 0 | } |
1969 | | |
1970 | | /** |
1971 | | * Print details on a specific file to a file handle. |
1972 | | * |
1973 | | * @param fs File system file is located in |
1974 | | * @param hFile File handle to print text to |
1975 | | * @param inum Address of file in file system |
1976 | | * @param numblock The number of blocks in file to force print (can go beyond file size) |
1977 | | * @param sec_skew Clock skew in seconds to also print times in |
1978 | | * |
1979 | | * @returns 1 on error and 0 on success |
1980 | | */ |
1981 | | static uint8_t |
1982 | | logicalfs_istat( |
1983 | | [[maybe_unused]] TSK_FS_INFO *fs, |
1984 | | [[maybe_unused]] TSK_FS_ISTAT_FLAG_ENUM flags, |
1985 | | [[maybe_unused]] FILE * hFile, |
1986 | | [[maybe_unused]] TSK_INUM_T inum, |
1987 | | [[maybe_unused]] TSK_DADDR_T numblock, |
1988 | | [[maybe_unused]] int32_t sec_skew) |
1989 | 0 | { |
1990 | 0 | tsk_error_reset(); |
1991 | 0 | tsk_error_set_errno(TSK_ERR_FS_UNSUPFUNC); |
1992 | 0 | tsk_error_set_errstr("istat not supported for logical file systems"); |
1993 | 0 | return 1; |
1994 | 0 | } |
1995 | | |
1996 | | /* logicalfs_close - close a logical file system */ |
1997 | | static void |
1998 | | logicalfs_close(TSK_FS_INFO *fs) |
1999 | 0 | { |
2000 | 0 | if (fs != NULL) { |
2001 | 0 | fs->tag = 0; |
2002 | 0 | tsk_fs_free(fs); |
2003 | 0 | } |
2004 | 0 | } |
2005 | | |
2006 | | static uint8_t |
2007 | | logicalfs_jentry_walk(TSK_FS_INFO * /*info*/, int /*entry*/, |
2008 | | TSK_FS_JENTRY_WALK_CB /*cb*/, void * /*fn*/) |
2009 | 0 | { |
2010 | 0 | tsk_error_reset(); |
2011 | 0 | tsk_error_set_errno(TSK_ERR_FS_UNSUPFUNC); |
2012 | 0 | tsk_error_set_errstr("Journal support for logical directory is not implemented"); |
2013 | 0 | return 1; |
2014 | 0 | } |
2015 | | |
2016 | | static uint8_t |
2017 | | logicalfs_jblk_walk(TSK_FS_INFO * /*info*/, TSK_DADDR_T /*daddr*/, |
2018 | | TSK_DADDR_T /*daddrt*/, int /*entry*/, TSK_FS_JBLK_WALK_CB /*cb*/, |
2019 | | void * /*fn*/) |
2020 | 0 | { |
2021 | 0 | tsk_error_reset(); |
2022 | 0 | tsk_error_set_errno(TSK_ERR_FS_UNSUPFUNC); |
2023 | 0 | tsk_error_set_errstr("Journal support for logical directory is not implemented"); |
2024 | 0 | return 1; |
2025 | 0 | } |
2026 | | |
2027 | | static uint8_t |
2028 | | logicalfs_jopen(TSK_FS_INFO * /*info*/, TSK_INUM_T /*inum*/) |
2029 | 0 | { |
2030 | 0 | tsk_error_reset(); |
2031 | 0 | tsk_error_set_errno(TSK_ERR_FS_UNSUPFUNC); |
2032 | 0 | tsk_error_set_errstr("Journal support for logical directory is not implemented"); |
2033 | 0 | return 1; |
2034 | 0 | } |
2035 | | |
2036 | | int |
2037 | | logicalfs_name_cmp(TSK_FS_INFO * a_fs_info, const char *s1, const char *s2) |
2038 | 0 | { |
2039 | | #ifdef TSK_WIN32 |
2040 | | return strcasecmp(s1, s2); |
2041 | | #else |
2042 | 0 | return tsk_fs_unix_name_cmp(a_fs_info, s1, s2); |
2043 | 0 | #endif |
2044 | 0 | } |
2045 | | |
2046 | | TSK_FS_INFO * |
2047 | 0 | logical_fs_open(TSK_IMG_INFO * img_info) { |
2048 | |
|
2049 | 0 | LOGICALFS_INFO *logical_fs_info = NULL; |
2050 | 0 | TSK_FS_INFO *fs = NULL; |
2051 | 0 | IMG_LOGICAL_INFO *logical_img_info = NULL; |
2052 | |
|
2053 | 0 | #ifndef TSK_WIN32 |
2054 | 0 | tsk_error_reset(); |
2055 | 0 | tsk_error_set_errno(TSK_ERR_FS_ARG); |
2056 | 0 | tsk_error_set_errstr("logical_fs_open: logical file systems currently only enabled on Windows"); |
2057 | 0 | return NULL; |
2058 | 0 | #endif |
2059 | | |
2060 | 0 | if (img_info->itype != TSK_IMG_TYPE_LOGICAL) { |
2061 | 0 | tsk_error_reset(); |
2062 | 0 | tsk_error_set_errno(TSK_ERR_FS_ARG); |
2063 | 0 | tsk_error_set_errstr("logical_fs_open: image must be of type TSK_IMG_TYPE_DIR"); |
2064 | 0 | return NULL; |
2065 | 0 | } |
2066 | 0 | logical_img_info = (IMG_LOGICAL_INFO *)img_info; |
2067 | |
|
2068 | 0 | if ((logical_fs_info = (LOGICALFS_INFO *)tsk_fs_malloc(sizeof(LOGICALFS_INFO))) == NULL) |
2069 | 0 | return NULL; |
2070 | | |
2071 | 0 | fs = &(logical_fs_info->fs_info); |
2072 | 0 | logical_fs_info->base_path = logical_img_info->base_path; // To avoid having to always go through TSK_IMG_INFO |
2073 | |
|
2074 | 0 | fs->tag = TSK_FS_INFO_TAG; |
2075 | 0 | fs->ftype = TSK_FS_TYPE_LOGICAL; |
2076 | 0 | fs->flags = (TSK_FS_INFO_FLAG_ENUM)0; |
2077 | 0 | fs->img_info = img_info; |
2078 | 0 | fs->offset = 0; |
2079 | 0 | fs->endian = TSK_LIT_ENDIAN; |
2080 | 0 | fs->duname = "None"; |
2081 | | |
2082 | | // Metadata info |
2083 | 0 | fs->last_inum = 0; // Will set at the end |
2084 | 0 | fs->root_inum = LOGICAL_ROOT_INUM; |
2085 | 0 | fs->first_inum = LOGICAL_ROOT_INUM; |
2086 | 0 | fs->inum_count = 0; |
2087 | | |
2088 | | // Block info |
2089 | 0 | fs->dev_bsize = 0; |
2090 | 0 | fs->block_size = LOGICAL_BLOCK_SIZE; |
2091 | 0 | fs->block_pre_size = 0; |
2092 | 0 | fs->block_post_size = 0; |
2093 | 0 | fs->block_count = 0; |
2094 | 0 | fs->first_block = 0; |
2095 | 0 | fs->last_block = INT64_MAX; |
2096 | 0 | fs->last_block_act = INT64_MAX; |
2097 | | |
2098 | | // Set the generic function pointers. Most will be no-ops for now. |
2099 | 0 | fs->inode_walk = logicalfs_inode_walk; |
2100 | 0 | fs->block_walk = logicalfs_block_walk; |
2101 | 0 | fs->block_getflags = logicalfs_block_getflags; |
2102 | |
|
2103 | 0 | fs->get_default_attr_type = logicalfs_get_default_attr_type; |
2104 | 0 | fs->load_attrs = logicalfs_load_attrs; |
2105 | |
|
2106 | 0 | fs->file_add_meta = logicalfs_file_add_meta; |
2107 | 0 | fs->dir_open_meta = logicalfs_dir_open_meta; |
2108 | 0 | fs->fsstat = logicalfs_fsstat; |
2109 | 0 | fs->fscheck = logicalfs_fscheck; |
2110 | 0 | fs->istat = logicalfs_istat; |
2111 | 0 | fs->name_cmp = logicalfs_name_cmp; |
2112 | |
|
2113 | 0 | fs->close = logicalfs_close; |
2114 | | |
2115 | | // Journal functions - also no-ops. |
2116 | 0 | fs->jblk_walk = logicalfs_jblk_walk; |
2117 | 0 | fs->jentry_walk = logicalfs_jentry_walk; |
2118 | 0 | fs->jopen = logicalfs_jopen; |
2119 | | |
2120 | | // Calculate the last inum |
2121 | 0 | fs->last_inum = find_max_inum(logical_fs_info); |
2122 | | |
2123 | | // We don't really care about the last inum, but if traversing the |
2124 | | // folders to calculate it fails then we're going to encounter |
2125 | | // the same error when using the logical file system. |
2126 | 0 | if (fs->last_inum == LOGICAL_INVALID_INUM) { |
2127 | 0 | logicalfs_close(fs); |
2128 | 0 | return NULL; |
2129 | 0 | } |
2130 | | |
2131 | 0 | return fs; |
2132 | 0 | } |