Coverage Report

Created: 2025-07-12 06:14

/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
}