Coverage Report

Created: 2026-04-01 06:43

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/sleuthkit/tsk/fs/ntfs_dent.cpp
Line
Count
Source
1
/*
2
** ntfs_dent
3
** The Sleuth Kit
4
**
5
** name layer support for the NTFS file system
6
**
7
** Brian Carrier [carrier <at> sleuthkit [dot] org]
8
** Copyright (c) 2006-2011 Brian Carrier, Basis Technology.  All Rights reserved
9
** Copyright (c) 2003-2005 Brian Carrier.  All rights reserved
10
**
11
** TASK
12
** Copyright (c) 2002 Brian Carrier, @stake Inc.  All rights reserved
13
**
14
**
15
** This software is distributed under the Common Public License 1.0
16
**
17
** Unicode added with support from I.D.E.A.L. Technology Corp (Aug '05)
18
**
19
*/
20
#include "tsk_fs_i.h"
21
#include "tsk_ntfs.h"
22
23
/**
24
 * \file ntfs_dent.cpp
25
 * NTFS file name processing internal functions.
26
 */
27
28
#include <memory>
29
#include <unordered_map>
30
#include <vector>
31
32
/**
33
 * Class to hold the pair of MFT entry and sequence.
34
 */
35
class NTFS_META_ADDR {
36
private:
37
    const uint64_t addr; ///< MFT entry
38
    const uint32_t seq; ///< Sequence
39
    const uint32_t hash; ///< Hash of the path
40
41
public:
42
    NTFS_META_ADDR(uint64_t a_addr, uint32_t a_seq, uint32_t a_hash) :
43
11.7k
        addr(a_addr),
44
11.7k
        seq(a_seq),
45
11.7k
        hash(a_hash)
46
11.7k
    {
47
11.7k
    }
48
49
108k
    uint64_t getAddr() const {
50
108k
        return addr;
51
108k
    }
52
53
53.3k
    uint32_t getSeq() const {
54
53.3k
        return seq;
55
53.3k
    }
56
57
55.3k
    uint32_t getHash() const {
58
55.3k
        return hash;
59
55.3k
    }
60
};
61
62
63
/* When we list a directory, we need to also look at MFT entries and what
64
 * they list as their parents. We used to do this only for orphan files, but
65
 * we were pointed to a case whereby allocated files were not in IDX_ALLOC, but were
66
 * shown in Windows (when mounted).  They must have been found via the MFT entry, so
67
 * we now load all parent to child relationships into the map.
68
 *
69
 * One of these classes is created per parent folder */
70
class NTFS_PAR_MAP  {
71
private:
72
        // maps sequence number to list of inums for the folder at that seq.
73
        std::unordered_map <uint32_t, std::vector <NTFS_META_ADDR> > seq2addrs;
74
public:
75
        /**
76
         * Add a child to this parent.
77
         * @param seq Sequence of the parent that this child belonged to
78
         * @param inum Address of child in the folder.
79
         * @param seq Sequence of child in the folder
80
         */
81
11.7k
        void add (uint32_t parSeq, TSK_INUM_T inum, uint32_t seq, uint32_t hash) {
82
11.7k
            seq2addrs[parSeq].emplace_back(inum, seq, hash);
83
11.7k
        }
84
85
        /**
86
         * Test if there are any children for this directory at a given sequence.
87
         * @param seq Sequence to test.
88
         * @returns true if children exist
89
         */
90
33.9k
        bool exists (uint32_t seq) {
91
33.9k
            if (seq2addrs.count(seq) > 0)
92
28.6k
                return true;
93
5.38k
            else
94
5.38k
                return false;
95
33.9k
        }
96
97
        /**
98
         * Get the children for this folder at a given sequence.  Use exists first.
99
         * @param seq Sequence number to retrieve children for.
100
         * @returns list of INUMS for children.
101
         */
102
28.6k
        const std::vector <NTFS_META_ADDR> &get (uint32_t seq) const {
103
28.6k
            return seq2addrs.at(seq);
104
28.6k
        }
105
 };
106
107
108
109
/** \internal
110
* Casts the void * to a map.  This obfuscation is done so that the rest of the library
111
* can remain as C and only this code needs to be C++.
112
*
113
* Assumes that you already have the lock
114
*/
115
90.4k
static std::unordered_map<TSK_INUM_T, NTFS_PAR_MAP> * getParentMap(NTFS_INFO *ntfs) {
116
    // allocate it if it hasn't already been
117
90.4k
    if (ntfs->orphan_map == NULL) {
118
1.87k
        auto inum_hash = [](const TSK_INUM_T& x) { return x; };
119
1.87k
        ntfs->orphan_map = new std::unordered_map<TSK_INUM_T, NTFS_PAR_MAP,decltype(inum_hash)>(0, inum_hash);
120
1.87k
    }
121
90.4k
    return (std::unordered_map<TSK_INUM_T, NTFS_PAR_MAP> *)ntfs->orphan_map;
122
90.4k
}
123
124
125
126
/** \internal
127
 * Add a parent and child pair to the map stored in NTFS_INFO
128
 *
129
 * Note: This routine assumes &ntfs->orphan_map_lock is locked by the caller.
130
 *
131
 * @param ntfs structure to add the pair to
132
 * @param par Parent address
133
 * @param child_meta Child to add
134
 * @returns 1 on error
135
 */
136
static uint8_t
137
ntfs_parent_map_add(NTFS_INFO * ntfs, TSK_FS_META_NAME_LIST *name_list, TSK_FS_META *child_meta)
138
11.7k
{
139
11.7k
    std::unordered_map<TSK_INUM_T, NTFS_PAR_MAP> *tmpParentMap = getParentMap(ntfs);
140
11.7k
    NTFS_PAR_MAP &tmpParMap = (*tmpParentMap)[name_list->par_inode];
141
11.7k
    tmpParMap.add(name_list->par_seq, child_meta->addr, child_meta->seq, tsk_fs_dir_hash(name_list->name));
142
11.7k
    return 0;
143
11.7k
}
144
145
/** \internal
146
 * Returns if a parent has children or not.
147
 *
148
 * Note: This routine assumes &ntfs->orphan_map_lock is locked by the caller.
149
 *
150
 * @param ntfs File system that has already been analyzed
151
 * @param par Parent inode to find child files for
152
 * @seq seq Sequence of parent folder
153
 * @returns true if parent has children.
154
 */
155
static bool
156
ntfs_parent_map_exists(NTFS_INFO *ntfs, TSK_INUM_T par, uint32_t seq)
157
46.3k
{
158
46.3k
    std::unordered_map<TSK_INUM_T, NTFS_PAR_MAP> *tmpParentMap = getParentMap(ntfs);
159
46.3k
    if (tmpParentMap->count(par) > 0) {
160
33.9k
        NTFS_PAR_MAP &tmpParMap = (*tmpParentMap)[par];
161
33.9k
        if (tmpParMap.exists(seq))
162
28.6k
            return true;
163
33.9k
    }
164
17.7k
    return false;
165
46.3k
}
166
167
/** \internal
168
 * Look up a map entry by the parent address. You should call ntfs_parent_map_exists() before this, otherwise
169
 * an empty entry could be created.
170
 *
171
 * Note: This routine assumes &ntfs->orphan_map_lock is locked by the caller.
172
 *
173
 * @param ntfs File system that has already been analyzed
174
 * @param par Parent inode to find child files for
175
 * @param seq Sequence of parent inode
176
 * @returns address of children files in the parent directory
177
 */
178
static const std::vector <NTFS_META_ADDR> &
179
ntfs_parent_map_get(NTFS_INFO * ntfs, TSK_INUM_T par, uint32_t seq)
180
28.6k
{
181
28.6k
    std::unordered_map<TSK_INUM_T, NTFS_PAR_MAP> *tmpParentMap = getParentMap(ntfs);
182
28.6k
    NTFS_PAR_MAP &tmpParMap = (*tmpParentMap)[par];
183
28.6k
    return tmpParMap.get(seq);
184
28.6k
}
185
186
187
188
// note that for consistency, this should be called parent_map_free, but
189
// that would have required an API change in a point release and this better
190
// matches the name in NTFS_INFO
191
void
192
ntfs_orphan_map_free(NTFS_INFO * a_ntfs)
193
1.87k
{
194
    // This routine is only called from ntfs_close, so it wouldn't
195
    // normally need a lock.  However, it's an extern function, so be
196
    // safe in case someone else calls it.  (Perhaps it's extern by
197
    // mistake?)
198
199
1.87k
    tsk_take_lock(&a_ntfs->orphan_map_lock);
200
201
1.87k
    if (a_ntfs->orphan_map == NULL) {
202
0
        tsk_release_lock(&a_ntfs->orphan_map_lock);
203
0
        return;
204
0
    }
205
1.87k
    std::unordered_map<TSK_INUM_T, NTFS_PAR_MAP> *tmpParentMap = getParentMap(a_ntfs);
206
207
1.87k
    delete tmpParentMap;
208
1.87k
    a_ntfs->orphan_map = NULL;
209
1.87k
    tsk_release_lock(&a_ntfs->orphan_map_lock);
210
1.87k
}
211
212
213
/* inode_walk callback that is used to populate the orphan_map
214
 * structure in NTFS_INFO */
215
static TSK_WALK_RET_ENUM
216
ntfs_parent_act(TSK_FS_FILE * fs_file, void * /*ptr*/)
217
15.1k
{
218
15.1k
    NTFS_INFO *ntfs = (NTFS_INFO *) fs_file->fs_info;
219
15.1k
    TSK_FS_META_NAME_LIST *fs_name_list;
220
221
15.1k
    if ((fs_file->meta->flags & TSK_FS_META_FLAG_ALLOC) &&
222
10.1k
        fs_file->meta->type == TSK_FS_META_TYPE_REG) {
223
5.29k
        ++ntfs->alloc_file_count;
224
5.29k
    }
225
226
    /* go through each file name structure */
227
15.1k
    fs_name_list = fs_file->meta->name2;
228
26.9k
    while (fs_name_list) {
229
11.7k
        if (ntfs_parent_map_add(ntfs, fs_name_list,
230
11.7k
                fs_file->meta)) {
231
0
            return TSK_WALK_ERROR;
232
0
        }
233
11.7k
        fs_name_list = fs_name_list->next;
234
11.7k
    }
235
15.1k
    return TSK_WALK_CONT;
236
15.1k
}
237
238
239
240
/****************/
241
242
/**
243
  * @returns 1 on error
244
  */
245
static uint8_t
246
ntfs_dent_copy(NTFS_INFO * ntfs, ntfs_idxentry * idxe, uintptr_t endaddr,
247
    TSK_FS_NAME * fs_name)
248
785k
{
249
785k
    ntfs_attr_fname *fname = (ntfs_attr_fname *) & idxe->stream;
250
785k
    TSK_FS_INFO *fs = (TSK_FS_INFO *) & ntfs->fs_info;
251
785k
    UTF16 *name16;
252
785k
    UTF8 *name8;
253
785k
    int retVal;
254
255
785k
    tsk_fs_name_reset(fs_name);
256
257
785k
    fs_name->meta_addr = tsk_getu48(fs->endian, idxe->file_ref);
258
785k
    fs_name->meta_seq = tsk_getu16(fs->endian, idxe->seq_num);
259
260
785k
    name16 = (UTF16 *) & fname->name;
261
785k
    name8 = (UTF8 *) fs_name->name;
262
263
785k
    const UTF16 * sourceEnd = (UTF16 *) ((uintptr_t) name16 + fname->nlen * 2);
264
785k
    if (((uintptr_t) sourceEnd) >= endaddr) {
265
314
        if (tsk_verbose)
266
0
            tsk_fprintf(stderr,
267
0
                "sourceEnd: %" PRIuINUM " is out of endaddr bounds: %" PRIuINUM,
268
0
                sourceEnd, endaddr);
269
314
        return 1;
270
314
    }
271
272
785k
    retVal = tsk_UTF16toUTF8(fs->endian, (const UTF16 **) &name16,
273
785k
        sourceEnd, &name8,
274
785k
        (UTF8 *) ((uintptr_t) name8 +
275
785k
            fs_name->name_size), TSKlenientConversion);
276
277
785k
    if (retVal != TSKconversionOK) {
278
6.05k
        *name8 = '\0';
279
6.05k
        if (tsk_verbose)
280
0
            tsk_fprintf(stderr,
281
0
                "Error converting NTFS name to UTF8: %d %" PRIuINUM,
282
0
                retVal, fs_name->meta_addr);
283
6.05k
    }
284
285
    /* Make sure it is NULL Terminated */
286
785k
    if ((uintptr_t) name8 > (uintptr_t) fs_name->name + fs_name->name_size)
287
0
        fs_name->name[fs_name->name_size] = '\0';
288
785k
    else
289
785k
        *name8 = '\0';
290
291
785k
    if (tsk_getu64(fs->endian, fname->flags) & NTFS_FNAME_FLAGS_DIR)
292
364k
        fs_name->type = TSK_FS_NAME_TYPE_DIR;
293
421k
    else
294
421k
        fs_name->type = TSK_FS_NAME_TYPE_REG;
295
296
785k
    fs_name->flags = (TSK_FS_NAME_FLAG_ENUM)0;
297
298
785k
    return 0;
299
785k
}
300
301
302
/* Copy the short file name pointed to by idxe into fs_name.
303
 * No other fields are copied.  Just the name into shrt_name. */
304
static uint8_t
305
ntfs_dent_copy_short_only(NTFS_INFO * ntfs, ntfs_idxentry * idxe,
306
    TSK_FS_NAME * fs_name)
307
1.97k
{
308
1.97k
    ntfs_attr_fname *fname = (ntfs_attr_fname *) & idxe->stream;
309
1.97k
    TSK_FS_INFO *fs = (TSK_FS_INFO *) & ntfs->fs_info;
310
1.97k
    UTF16 *name16;
311
1.97k
    UTF8 *name8;
312
1.97k
    int retVal;
313
314
1.97k
    name16 = (UTF16 *) & fname->name;
315
1.97k
    name8 = (UTF8 *) fs_name->shrt_name;
316
317
1.97k
    retVal = tsk_UTF16toUTF8(fs->endian, (const UTF16 **) &name16,
318
1.97k
        (UTF16 *) ((uintptr_t) name16 +
319
1.97k
            fname->nlen * 2), &name8,
320
1.97k
        (UTF8 *) ((uintptr_t) name8 +
321
1.97k
            fs_name->shrt_name_size), TSKlenientConversion);
322
323
1.97k
    if (retVal != TSKconversionOK) {
324
1.48k
        *name8 = '\0';
325
1.48k
        if (tsk_verbose)
326
0
            tsk_fprintf(stderr,
327
0
                "Error converting NTFS 8.3 name to UTF8: %d %" PRIuINUM,
328
0
                retVal, fs_name->meta_addr);
329
1.48k
    }
330
331
    /* Make sure it is NULL Terminated */
332
1.97k
    if ((uintptr_t) name8 > (uintptr_t) fs_name->shrt_name + fs_name->shrt_name_size)
333
0
        fs_name->shrt_name[fs_name->shrt_name_size] = '\0';
334
1.97k
    else
335
1.97k
        *name8 = '\0';
336
337
1.97k
    return 0;
338
1.97k
}
339
340
341
342
343
/* This is a sanity check to see if the time is valid
344
 * it is divided by 100 to keep it in a 32-bit integer
345
 */
346
347
static uint8_t
348
is_time(uint64_t t)
349
237k
{
350
457k
#define SEC_BTWN_1601_1970_DIV100 ((369*365 + 89) * 24 * 36)
351
237k
#define SEC_BTWN_1601_2020_DIV100 (SEC_BTWN_1601_1970_DIV100 + (50*365 + 6) * 24 * 36)
352
353
237k
    t /= 1000000000;            /* put the time in seconds div by additional 100 */
354
355
237k
    if (!t)
356
3.35k
        return 0;
357
358
234k
    if (t < SEC_BTWN_1601_1970_DIV100)
359
11.0k
        return 0;
360
361
223k
    if (t > SEC_BTWN_1601_2020_DIV100)
362
9.27k
        return 0;
363
364
213k
    return 1;
365
223k
}
366
367
368
369
/**
370
 * Process a list of index entries and add to FS_DIR
371
 *
372
 * @param a_is_del Set to 1 if these entries are for a deleted directory
373
 * @param idxe Buffer with index entries to process
374
 * @param idxe_len Length of idxe buffer (in bytes)
375
 * @param used_len Length of data as reported by idexlist header (everything
376
 * after which and less then idxe_len is considered deleted)
377
 *
378
 * @returns 1 to stop, 0 on success, and -1 on error
379
 */
380
381
// @@@ Should make a_idxe const and use internal pointer in function loop
382
static TSK_RETVAL_ENUM
383
ntfs_proc_idxentry(NTFS_INFO * a_ntfs, TSK_FS_DIR * a_fs_dir,
384
    uint8_t a_is_del, ntfs_idxentry * a_idxe, uint32_t a_idxe_len,
385
    uint32_t a_used_len)
386
90.9k
{
387
90.9k
    uintptr_t endaddr, endaddr_alloc;
388
90.9k
    TSK_FS_NAME *fs_name;
389
90.9k
    TSK_FS_NAME *fs_name_preventry = NULL;
390
90.9k
    TSK_FS_INFO *fs = (TSK_FS_INFO *) & a_ntfs->fs_info;
391
392
90.9k
    if ((fs_name = tsk_fs_name_alloc(NTFS_MAXNAMLEN_UTF8, 16)) == NULL) {
393
0
        return TSK_ERR;
394
0
    }
395
396
90.9k
    if (tsk_verbose)
397
0
        tsk_fprintf(stderr,
398
0
            "ntfs_proc_idxentry: Processing index entry: %" PRIu64
399
0
            "  Size: %" PRIu32 "  Len: %" PRIu32 "\n",
400
0
            (uint64_t) ((uintptr_t) a_idxe), a_idxe_len, a_used_len);
401
402
    /* Sanity check */
403
90.9k
    if (a_idxe_len < a_used_len) {
404
121
        tsk_error_reset();
405
121
        tsk_error_set_errno(TSK_ERR_FS_ARG);
406
121
        tsk_error_set_errstr
407
121
            ("ntfs_proc_idxentry: Allocated length of index entries is larger than buffer length");
408
121
        return TSK_ERR;
409
121
    }
410
411
    /* where is the end of the buffer */
412
90.8k
    endaddr = ((uintptr_t) a_idxe + a_idxe_len);
413
414
    /* where is the end of the allocated data */
415
90.8k
    endaddr_alloc = ((uintptr_t) a_idxe + a_used_len);
416
417
    /* cycle through the index entries, based on provided size */
418
709M
    while (((uintptr_t) & (a_idxe->stream) + sizeof(ntfs_attr_fname)) <
419
709M
        endaddr) {
420
421
709M
        ntfs_attr_fname *fname = (ntfs_attr_fname *) & a_idxe->stream;
422
423
424
709M
        if (tsk_verbose)
425
0
            tsk_fprintf(stderr,
426
0
                "ntfs_proc_idxentry: New IdxEnt: %" PRIu64
427
0
                " $FILE_NAME Entry: %" PRIu64 "  File Ref: %" PRIu64
428
0
                "  IdxEnt Len: %" PRIu16 "  StrLen: %" PRIu16 "\n",
429
0
                (uint64_t) ((uintptr_t) a_idxe),
430
0
                (uint64_t) ((uintptr_t) fname),
431
0
                (uint64_t) tsk_getu48(fs->endian, a_idxe->file_ref),
432
0
                tsk_getu16(fs->endian, a_idxe->idxlen),
433
0
                tsk_getu16(fs->endian, a_idxe->strlen));
434
435
        /* perform some sanity checks on index buffer head
436
         * and advance by 4-bytes if invalid
437
         */
438
709M
        if ((tsk_getu48(fs->endian, a_idxe->file_ref) > fs->last_inum) ||
439
327M
            (tsk_getu48(fs->endian, a_idxe->file_ref) < fs->first_inum) ||
440
327M
            (tsk_getu16(fs->endian,
441
327M
                    a_idxe->idxlen) <= tsk_getu16(fs->endian,
442
327M
                    a_idxe->strlen))
443
18.8M
            || (tsk_getu16(fs->endian, a_idxe->idxlen) % 4)
444
699M
            || (tsk_getu16(fs->endian, a_idxe->idxlen) > a_idxe_len)) {
445
699M
            a_idxe = (ntfs_idxentry *) ((uintptr_t) a_idxe + 4);
446
699M
            continue;
447
699M
        }
448
449
#if 0
450
        // @@@ BC: This hid a lot of entries in test images.  They were
451
        // only partial images, but they were not junk and the idea was
452
        // that this check would strip out chunk.  Commented it out and
453
        // keeping it here as a reminder in case I think about doing it
454
        // again.
455
456
        // verify name length would fit in stream
457
        if (fname->nlen > tsk_getu16(fs->endian, a_idxe->strlen)) {
458
            a_idxe = (ntfs_idxentry *) ((uintptr_t) a_idxe + 4);
459
            if (tsk_verbose)
460
                tsk_fprintf(stderr,
461
                    "ntfs_proc_idxentry: Skipping because name is longer than stream\n");
462
            continue;
463
        }
464
#endif
465
466
        // verify it has the correct parent address
467
10.3M
        if (tsk_getu48(fs->endian, fname->par_ref) != a_fs_dir->addr) {
468
9.40M
            a_idxe = (ntfs_idxentry *) ((uintptr_t) a_idxe + 4);
469
9.40M
            if (tsk_verbose)
470
0
                tsk_fprintf(stderr,
471
0
                    "ntfs_proc_idxentry: Skipping because of wrong parent address\n");
472
9.40M
            continue;
473
9.40M
        }
474
475
476
        /* do some sanity checks on the deleted entries
477
         */
478
921k
        if ((tsk_getu16(fs->endian, a_idxe->strlen) == 0) ||
479
914k
            (((uintptr_t) a_idxe + tsk_getu16(fs->endian,
480
914k
                        a_idxe->idxlen)) > endaddr_alloc)) {
481
482
            /* name space checks */
483
171k
            if ((fname->nspace != NTFS_FNAME_POSIX) &&
484
141k
                (fname->nspace != NTFS_FNAME_WIN32) &&
485
137k
                (fname->nspace != NTFS_FNAME_DOS) &&
486
132k
                (fname->nspace != NTFS_FNAME_WINDOS)) {
487
51.2k
                a_idxe = (ntfs_idxentry *) ((uintptr_t) a_idxe + 4);
488
51.2k
                if (tsk_verbose)
489
0
                    tsk_fprintf(stderr,
490
0
                        "ntfs_proc_idxentry: Skipping because of invalid name space\n");
491
51.2k
                continue;
492
51.2k
            }
493
494
120k
            if ((tsk_getu64(fs->endian, fname->alloc_fsize) <
495
120k
                    tsk_getu64(fs->endian, fname->real_fsize))
496
104k
                || (fname->nlen == 0)
497
91.2k
                || (*(uint8_t *) & fname->name == 0)) {
498
499
34.0k
                a_idxe = (ntfs_idxentry *) ((uintptr_t) a_idxe + 4);
500
34.0k
                if (tsk_verbose)
501
0
                    tsk_fprintf(stderr,
502
0
                        "ntfs_proc_idxentry: Skipping because of reported file sizes, name length, or NULL name\n");
503
34.0k
                continue;
504
34.0k
            }
505
506
86.3k
            if ((is_time(tsk_getu64(fs->endian, fname->crtime)) == 0) ||
507
79.3k
                (is_time(tsk_getu64(fs->endian, fname->atime)) == 0) ||
508
71.9k
                (is_time(tsk_getu64(fs->endian, fname->mtime)) == 0)) {
509
510
23.6k
                a_idxe = (ntfs_idxentry *) ((uintptr_t) a_idxe + 4);
511
23.6k
                if (tsk_verbose)
512
0
                    tsk_fprintf(stderr,
513
0
                        "ntfs_proc_idxentry: Skipping because of invalid times\n");
514
23.6k
                continue;
515
23.6k
            }
516
86.3k
        }
517
518
519
        /* For all fname entries, there will exist a DOS style 8.3
520
         * entry.
521
         * If the original name is 8.3 compliant, it will be in
522
         * a WINDOS type.  If it is not compliant, then it will
523
         * exist in a POSIX or WIN32 type and the 8.3 compliant
524
         * one will be in DOS. The DOS entry typically follows
525
         * the WIN32 or POSIX.
526
         *
527
         * Our approach is to stash away the non-compliant names
528
         * for one more entry to see if the next try is its
529
         * corresponding 8.3 entry.
530
         *
531
         * If the 8.3 entry is not for the previous entry, we
532
         * skip it on the theory that it corresponds to a previous
533
         * WIN32 or POSIX entry. Note that we could be missing some info from deleted files
534
         * if the windows version was deleted and the DOS wasn't...
535
         */
536
537
812k
        if (fname->nspace == NTFS_FNAME_DOS) {
538
            // Was the previous entry not 8.3 compliant?
539
26.5k
            if (fs_name_preventry) {
540
                // check its the same entry and if so, add short name
541
20.7k
                if (fs_name_preventry->meta_addr == tsk_getu48(fs->endian, a_idxe->file_ref)) {
542
1.97k
                    ntfs_dent_copy_short_only(a_ntfs, a_idxe, fs_name_preventry);
543
1.97k
                }
544
545
                // regardless, add preventry to dir and move on to next entry.
546
20.7k
                if (tsk_fs_dir_add(a_fs_dir, fs_name_preventry)) {
547
0
                    tsk_fs_name_free(fs_name);
548
0
                    return TSK_ERR;
549
0
                }
550
20.7k
                fs_name_preventry = NULL;
551
20.7k
            }
552
553
26.5k
            goto incr_entry;
554
26.5k
        }
555
        // if we stashed the previous entry and the next wasn't a DOS entry, add it to the list
556
785k
        else if (fs_name_preventry) {
557
428k
            if (tsk_fs_dir_add(a_fs_dir, fs_name_preventry)) {
558
0
                tsk_fs_name_free(fs_name);
559
0
                return TSK_ERR;
560
0
            }
561
428k
            fs_name_preventry = NULL;
562
428k
        }
563
564
        /* Copy it into the generic form */
565
785k
        if (ntfs_dent_copy(a_ntfs, a_idxe, endaddr, fs_name)) {
566
314
            if (tsk_verbose)
567
0
                tsk_fprintf(stderr,
568
0
                    "ntfs_proc_idxentry: Skipping because error copying dent_entry\n");
569
314
            goto incr_entry;
570
314
        }
571
572
        /*
573
         * Check if this entry is deleted
574
         *
575
         * The final check is to see if the end of this entry is
576
         * within the space that the idxallocbuf claimed was valid OR
577
         * if the parent directory is deleted
578
         */
579
785k
        if ((a_is_del == 1) ||
580
784k
            (tsk_getu16(fs->endian, a_idxe->strlen) == 0) ||
581
781k
            (((uintptr_t) a_idxe + tsk_getu16(fs->endian,
582
781k
                        a_idxe->idxlen)) > endaddr_alloc)) {
583
59.9k
            fs_name->flags = TSK_FS_NAME_FLAG_UNALLOC;
584
59.9k
        }
585
725k
        else {
586
725k
            fs_name->flags = TSK_FS_NAME_FLAG_ALLOC;
587
725k
        }
588
589
785k
        if (tsk_verbose)
590
0
            tsk_fprintf(stderr,
591
0
                "ntfs_proc_idxentry: Entry Details of %s: Str Len: %"
592
0
                PRIu16 "  Len to end after current: %" PRIu64
593
0
                "  flags: %x\n", fs_name->name, tsk_getu16(fs->endian,
594
0
                    a_idxe->strlen),
595
0
                (uint64_t) (endaddr_alloc - (uintptr_t) a_idxe -
596
0
                    tsk_getu16(fs->endian, a_idxe->idxlen)),
597
0
                fs_name->flags);
598
599
        // WINDOS entries will not have a short 8.3 version, so add them now.
600
        // otherwise, we stash the name to see if we get the 8.3 next.
601
785k
        if (fname->nspace == NTFS_FNAME_WINDOS) {
602
316k
            if (tsk_fs_dir_add(a_fs_dir, fs_name)) {
603
0
                tsk_fs_name_free(fs_name);
604
0
                return TSK_ERR;
605
0
            }
606
316k
            fs_name_preventry = NULL;
607
316k
        }
608
469k
        else {
609
469k
            fs_name_preventry = fs_name;
610
469k
        }
611
612
812k
      incr_entry:
613
614
        /* the theory here is that deleted entries have strlen == 0 and
615
         * have been found to have idxlen == 16
616
         *
617
         * if the strlen is 0, then guess how much the indexlen was
618
         * before it was deleted
619
         */
620
621
        /* 16: size of idxentry before stream
622
         * 66: size of fname before name
623
         * 2*nlen: size of name (in unicode)
624
         */
625
812k
        if (tsk_getu16(fs->endian, a_idxe->strlen) == 0) {
626
2.32k
            a_idxe =
627
2.32k
                (ntfs_idxentry
628
2.32k
                *) ((((uintptr_t) a_idxe + 16 + 66 + 2 * fname->nlen +
629
2.32k
                        3) / 4) * 4);
630
2.32k
        }
631
810k
        else {
632
810k
            a_idxe =
633
810k
                (ntfs_idxentry *) ((uintptr_t) a_idxe +
634
810k
                tsk_getu16(fs->endian, a_idxe->idxlen));
635
810k
        }
636
637
812k
    }                           /* end of loop of index entries */
638
639
    // final check in case we were looking for the short name, we never saw
640
90.8k
    if (fs_name_preventry) {
641
20.0k
        if (tsk_fs_dir_add(a_fs_dir, fs_name_preventry)) {
642
0
            tsk_fs_name_free(fs_name);
643
0
            return TSK_ERR;
644
0
        }
645
20.0k
        fs_name_preventry = NULL;
646
20.0k
    }
647
648
90.8k
    tsk_fs_name_free(fs_name);
649
90.8k
    return TSK_OK;
650
90.8k
}
651
652
653
654
655
/*
656
 * remove the update sequence values that are changed in the last two
657
 * bytes of each sector
658
 *
659
 * return 1 on error and 0 on success
660
 */
661
static uint8_t
662
ntfs_fix_idxrec(NTFS_INFO * ntfs, ntfs_idxrec * idxrec, uint32_t len)
663
42.1k
{
664
42.1k
    int i;
665
42.1k
    uint16_t orig_seq;
666
42.1k
    TSK_FS_INFO *fs = (TSK_FS_INFO *) & ntfs->fs_info;
667
42.1k
    ntfs_upd *upd;
668
669
42.1k
    if (tsk_verbose)
670
0
        tsk_fprintf(stderr,
671
0
            "ntfs_fix_idxrec: Fixing idxrec: %" PRIu64 "  Len: %"
672
0
            PRIu32 "\n", (uint64_t) ((uintptr_t) idxrec), len);
673
674
    /* sanity check so we don't run over in the next loop */
675
42.1k
    if ((unsigned int) ((tsk_getu16(fs->endian, idxrec->upd_cnt) - 1) *
676
42.1k
            NTFS_UPDATE_SEQ_STRIDE) > len) {
677
380
        tsk_error_reset();
678
380
        tsk_error_set_errno(TSK_ERR_FS_INODE_COR);
679
380
        tsk_error_set_errstr
680
380
            ("ntfs_fix_idxrec: More Update Sequence Entries than idx record size");
681
380
        return 1;
682
380
    }
683
684
41.7k
    uint16_t upd_off = tsk_getu16(fs->endian, idxrec->upd_off);
685
41.7k
    if (upd_off > len || sizeof(ntfs_upd) > (len - upd_off)) {
686
309
        tsk_error_reset();
687
309
        tsk_error_set_errno(TSK_ERR_FS_INODE_COR);
688
309
        tsk_error_set_errstr
689
309
            ("ntfs_fix_idxrec: Corrupt idx record");
690
309
        return 1;
691
309
    }
692
693
    /* Apply the update sequence structure template */
694
41.4k
    upd =
695
41.4k
        (ntfs_upd *) ((uintptr_t) idxrec + tsk_getu16(fs->endian,
696
41.4k
            idxrec->upd_off));
697
698
    /* Get the sequence value that each 16-bit value should be */
699
41.4k
    orig_seq = tsk_getu16(fs->endian, upd->upd_val);
700
701
    /* cycle through each sector */
702
82.5k
    for (i = 1; i < tsk_getu16(fs->endian, idxrec->upd_cnt); i++) {
703
704
        /* The offset into the buffer of the value to analyze */
705
41.4k
        int offset = i * NTFS_UPDATE_SEQ_STRIDE - 2;
706
41.4k
        uint8_t *new_val, *old_val;
707
708
        /* get the current sequence value */
709
41.4k
        uint16_t cur_seq =
710
41.4k
            tsk_getu16(fs->endian, (uintptr_t) idxrec + offset);
711
712
41.4k
        if (cur_seq != orig_seq) {
713
            /* get the replacement value */
714
369
            uint16_t cur_repl =
715
369
                tsk_getu16(fs->endian, &upd->upd_seq + (i - 1) * 2);
716
717
369
            tsk_error_reset();
718
369
            tsk_error_set_errno(TSK_ERR_FS_INODE_COR);
719
369
            tsk_error_set_errstr
720
369
                ("fix_idxrec: Incorrect update sequence value in index buffer\nUpdate Value: 0x%"
721
369
                PRIx16 " Actual Value: 0x%" PRIx16
722
369
                " Replacement Value: 0x%" PRIx16
723
369
                "\nThis is typically because of a corrupted entry",
724
369
                orig_seq, cur_seq, cur_repl);
725
369
            return 1;
726
369
        }
727
728
41.1k
        new_val = &upd->upd_seq + (i - 1) * 2;
729
41.1k
        old_val = (uint8_t *) ((uintptr_t) idxrec + offset);
730
731
41.1k
        if (tsk_verbose)
732
0
            tsk_fprintf(stderr,
733
0
                "ntfs_fix_idxrec: upd_seq %i   Replacing: %.4" PRIx16
734
0
                "   With: %.4" PRIx16 "\n", i, tsk_getu16(fs->endian,
735
0
                    old_val), tsk_getu16(fs->endian, new_val));
736
737
41.1k
        *old_val++ = *new_val++;
738
41.1k
        *old_val = *new_val;
739
41.1k
    }
740
741
41.0k
    return 0;
742
41.4k
}
743
744
745
746
747
748
/** \internal
749
* Process a directory and load up FS_DIR with the entries. If a pointer to
750
* an already allocated FS_DIR structure is given, it will be cleared.  If no existing
751
* FS_DIR structure is passed (i.e. NULL), then a new one will be created. If the return
752
* value is error or corruption, then the FS_DIR structure could
753
* have entries (depending on when the error occurred).
754
*
755
* @param a_fs File system to analyze
756
* @param a_fs_dir Pointer to FS_DIR pointer. Can contain an already allocated
757
* structure or a new structure.
758
* @param a_addr Address of directory to process.
759
* @param recursion_depth Recursion depth to limit the number of self-calls
760
* @returns error, corruption, ok etc.
761
*/
762
TSK_RETVAL_ENUM
763
ntfs_dir_open_meta(
764
  TSK_FS_INFO * a_fs,
765
  TSK_FS_DIR ** a_fs_dir,
766
  TSK_INUM_T a_addr,
767
  [[maybe_unused]] int recursion_depth)
768
80.3k
{
769
80.3k
    NTFS_INFO *ntfs = (NTFS_INFO *) a_fs;
770
80.3k
    TSK_FS_DIR *fs_dir;
771
80.3k
    const TSK_FS_ATTR *fs_attr_root = NULL;
772
80.3k
    const TSK_FS_ATTR *fs_attr_idx;
773
80.3k
    char *idxalloc;
774
80.3k
    ntfs_idxentry *idxe;
775
80.3k
    ntfs_idxroot *idxroot;
776
80.3k
    ntfs_idxelist *idxelist = NULL;
777
80.3k
    ntfs_idxrec *idxrec_p, *idxrec;
778
80.3k
    size_t idxalloc_len;
779
80.3k
    TSK_FS_LOAD_FILE load_file;
780
781
    /* In this function, we will return immediately if we get an error.
782
     * If we get corruption though, we will record that in 'retval_final'
783
     * and continue processing.
784
     */
785
80.3k
    TSK_RETVAL_ENUM retval_final = TSK_OK;
786
80.3k
    TSK_RETVAL_ENUM retval_tmp;
787
788
    /* sanity check */
789
80.3k
    if (a_addr < a_fs->first_inum || a_addr > a_fs->last_inum) {
790
1
        tsk_error_reset();
791
1
        tsk_error_set_errno(TSK_ERR_FS_WALK_RNG);
792
1
        tsk_error_set_errstr("ntfs_dir_open_meta: inode value: %" PRIuINUM
793
1
            "\n", a_addr);
794
1
        return TSK_ERR;
795
1
    }
796
80.3k
    else if (a_fs_dir == NULL) {
797
0
        tsk_error_reset();
798
0
        tsk_error_set_errno(TSK_ERR_FS_ARG);
799
0
        tsk_error_set_errstr
800
0
            ("ntfs_dir_open_meta: NULL fs_attr argument given");
801
0
        return TSK_ERR;
802
0
    }
803
804
80.3k
    if (tsk_verbose)
805
0
        tsk_fprintf(stderr,
806
0
            "ntfs_open_dir: Processing directory %" PRIuINUM "\n", a_addr);
807
808
809
80.3k
    fs_dir = *a_fs_dir;
810
80.3k
    if (fs_dir) {
811
0
        tsk_fs_dir_reset(fs_dir);
812
0
        fs_dir->addr = a_addr;
813
0
    }
814
80.3k
    else {
815
80.3k
        if ((*a_fs_dir = fs_dir =
816
80.3k
                tsk_fs_dir_alloc(a_fs, a_addr, 128)) == NULL) {
817
0
            return TSK_ERR;
818
0
        }
819
80.3k
    }
820
821
    //  handle the orphan directory if its contents were requested
822
80.3k
    if (a_addr == TSK_FS_ORPHANDIR_INUM(a_fs)) {
823
10.6k
        return tsk_fs_dir_find_orphans(a_fs, fs_dir);
824
10.6k
    }
825
826
    /* Get the inode and verify it has attributes */
827
69.6k
    if ((fs_dir->fs_file =
828
69.6k
            tsk_fs_file_open_meta(a_fs, NULL, a_addr)) == NULL) {
829
2.26k
        tsk_error_errstr2_concat("- ntfs_dir_open_meta");
830
2.26k
        return TSK_COR;
831
2.26k
    }
832
833
67.3k
    if (!(fs_dir->fs_file->meta->attr)) {
834
0
        tsk_error_reset();
835
0
        tsk_error_set_errno(TSK_ERR_FS_INODE_COR);
836
0
        tsk_error_set_errstr("dent_walk: Error: Directory address %"
837
0
            PRIuINUM " has no attributes", a_addr);
838
0
        return TSK_COR;
839
0
    }
840
841
    // Update with the sequence number
842
67.3k
    fs_dir->seq = fs_dir->fs_file->meta->seq;
843
844
    /*
845
     * Read the Index Root Attribute  -- we do some sanity checking here
846
     * to report errors before we start to make up data for the "." and ".."
847
     * entries
848
     */
849
67.3k
    fs_attr_root =
850
67.3k
        tsk_fs_attrlist_get(fs_dir->fs_file->meta->attr,
851
67.3k
        TSK_FS_ATTR_TYPE_NTFS_IDXROOT);
852
    // NOTE: We had one error reported on a system that did not have IDX_ROOT, but did have IDX_ALLOC
853
67.3k
    if (fs_attr_root) {
854
59.8k
        if (fs_attr_root->flags & TSK_FS_ATTR_NONRES) {
855
431
            tsk_error_reset();
856
431
            tsk_error_set_errno(TSK_ERR_FS_INODE_COR);
857
431
            tsk_error_set_errstr
858
431
            ("dent_walk: $IDX_ROOT is not resident - it should be");
859
431
            return TSK_COR;
860
431
        }
861
59.3k
        idxroot = (ntfs_idxroot *)fs_attr_root->rd.buf;
862
863
        /* Verify that the attribute type is $FILE_NAME */
864
59.3k
        if (tsk_getu32(a_fs->endian, idxroot->type) == 0) {
865
4.93k
            tsk_error_reset();
866
4.93k
            tsk_error_set_errno(TSK_ERR_FS_INODE_COR);
867
4.93k
            tsk_error_set_errstr
868
4.93k
            ("dent_walk: Attribute type in index root is 0");
869
4.93k
            return TSK_COR;
870
4.93k
        }
871
54.4k
        else if (tsk_getu32(a_fs->endian, idxroot->type) != NTFS_ATYPE_FNAME) {
872
1.33k
            tsk_error_reset();
873
1.33k
            tsk_error_set_errno(TSK_ERR_FS_INODE_COR);
874
1.33k
            tsk_error_set_errstr("ERROR: Directory index is sorted by type: %"
875
1.33k
                PRIu32 ".\nOnly $FNAME is currently supported",
876
1.33k
                tsk_getu32(a_fs->endian, idxroot->type));
877
1.33k
            return TSK_COR;
878
1.33k
        }
879
59.3k
    }
880
881
    /*
882
     * NTFS does not have "." and ".." entries in the index trees
883
     * (except for a "." entry in the root directory)
884
     *
885
     * So, we'll make 'em up by making a TSK_FS_NAME structure for
886
     * a '.' and '..' entry and call the action
887
     */
888
60.6k
    if (a_addr != a_fs->root_inum) {    // && (flags & TSK_FS_NAME_FLAG_ALLOC)) {
889
20.5k
        TSK_FS_NAME *fs_name;
890
20.5k
        TSK_FS_META_NAME_LIST *fs_name_list;
891
892
20.5k
        if (tsk_verbose)
893
0
            tsk_fprintf(stderr,
894
0
                "ntfs_dir_open_meta: Creating . and .. entries\n");
895
896
20.5k
        if ((fs_name = tsk_fs_name_alloc(16, 0)) == NULL) {
897
0
            return TSK_ERR;
898
0
        }
899
        /*
900
         * "."
901
         */
902
903
20.5k
        fs_name->type = TSK_FS_NAME_TYPE_DIR;
904
20.5k
        strcpy(fs_name->name, ".");
905
906
20.5k
        fs_name->meta_addr = a_addr;
907
20.5k
        if (fs_dir->fs_file->meta->flags & TSK_FS_META_FLAG_UNALLOC) {
908
4.24k
            fs_name->flags = TSK_FS_NAME_FLAG_UNALLOC;
909
            /* If the folder was deleted, the MFT entry sequence will have been incremented.
910
             * File name entries are not incremented on delete, so make it one less to
911
             * be consistent. */
912
4.24k
            fs_name->meta_seq = fs_dir->fs_file->meta->seq - 1;
913
4.24k
        }
914
16.3k
        else {
915
16.3k
            fs_name->flags = TSK_FS_NAME_FLAG_ALLOC;
916
16.3k
            fs_name->meta_seq = fs_dir->fs_file->meta->seq;
917
16.3k
        }
918
20.5k
        if (tsk_fs_dir_add(fs_dir, fs_name)) {
919
0
            tsk_fs_name_free(fs_name);
920
0
            return TSK_ERR;
921
0
        }
922
923
924
        /*
925
         * ".."
926
         */
927
20.5k
        strcpy(fs_name->name, "..");
928
20.5k
        fs_name->type = TSK_FS_NAME_TYPE_DIR;
929
930
        /* The fs_name structure holds the parent inode value, so we
931
         * just cycle using those
932
         */
933
20.5k
        for (fs_name_list = fs_dir->fs_file->meta->name2;
934
39.2k
            fs_name_list != NULL; fs_name_list = fs_name_list->next) {
935
18.6k
            fs_name->meta_addr = fs_name_list->par_inode;
936
18.6k
            fs_name->meta_seq = fs_name_list->par_seq;
937
18.6k
            if (tsk_fs_dir_add(fs_dir, fs_name)) {
938
0
                tsk_fs_name_free(fs_name);
939
0
                return TSK_ERR;
940
0
            }
941
18.6k
        }
942
943
20.5k
        tsk_fs_name_free(fs_name);
944
20.5k
        fs_name = NULL;
945
20.5k
    }
946
947
948
    /* Now we return to processing the Index Root Attribute */
949
60.6k
    if (fs_attr_root) {
950
951
53.1k
        if (tsk_verbose)
952
0
            tsk_fprintf(stderr,
953
0
                "ntfs_dir_open_meta: Processing $IDX_ROOT of inum %" PRIuINUM
954
0
                "\n", a_addr);
955
956
        /* Get the header of the index entry list */
957
53.1k
        idxelist = &idxroot->list;
958
959
        /* Verify the offset pointers */
960
53.1k
        if ((tsk_getu32(a_fs->endian, idxelist->seqend_off) <
961
53.1k
            tsk_getu32(a_fs->endian, idxelist->begin_off)) ||
962
52.3k
            (tsk_getu32(a_fs->endian, idxelist->bufend_off) <
963
52.3k
                tsk_getu32(a_fs->endian, idxelist->seqend_off)) ||
964
51.2k
                (((uintptr_t)idxelist + tsk_getu32(a_fs->endian,
965
51.2k
                    idxelist->bufend_off)) >
966
51.2k
                    ((uintptr_t)fs_attr_root->rd.buf +
967
51.2k
                        fs_attr_root->rd.buf_size))) {
968
2.36k
            tsk_error_reset();
969
2.36k
            tsk_error_set_errno(TSK_ERR_FS_INODE_COR);
970
2.36k
            tsk_error_set_errstr
971
2.36k
            ("Error: Index list offsets are invalid on entry: %" PRIuINUM,
972
2.36k
                fs_dir->fs_file->meta->addr);
973
2.36k
            return TSK_COR;
974
2.36k
        }
975
976
        /* Get the offset to the start of the index entry list */
977
50.7k
        idxe = (ntfs_idxentry *)((uintptr_t)idxelist +
978
50.7k
            tsk_getu32(a_fs->endian, idxelist->begin_off));
979
980
50.7k
        retval_tmp = ntfs_proc_idxentry(ntfs, fs_dir,
981
50.7k
            (fs_dir->fs_file->meta->flags & TSK_FS_META_FLAG_UNALLOC) ? 1 : 0,
982
50.7k
            idxe,
983
50.7k
            tsk_getu32(a_fs->endian, idxelist->bufend_off) -
984
50.7k
            tsk_getu32(a_fs->endian, idxelist->begin_off),
985
50.7k
            tsk_getu32(a_fs->endian, idxelist->seqend_off) -
986
50.7k
            tsk_getu32(a_fs->endian, idxelist->begin_off));
987
988
        // stop if we get an error, continue if we got corruption
989
50.7k
        if (retval_tmp == TSK_ERR) {
990
0
            return TSK_ERR;
991
0
        }
992
50.7k
        else if (retval_tmp == TSK_COR) {
993
0
            retval_final = TSK_COR;
994
0
        }
995
50.7k
    }
996
997
    /*
998
     * get the index allocation attribute if it exists (it doesn't for
999
     * small directories
1000
     */
1001
58.2k
    fs_attr_idx =
1002
58.2k
        tsk_fs_attrlist_get(fs_dir->fs_file->meta->attr,
1003
58.2k
        TSK_FS_ATTR_TYPE_NTFS_IDXALLOC);
1004
1005
1006
    /* if we don't have an index alloc then return, we have processed
1007
     * all of the entries
1008
     */
1009
58.2k
    if (!fs_attr_idx) {
1010
6.59k
        if ((idxelist) && (tsk_getu32(a_fs->endian,
1011
1.79k
                idxelist->flags) & NTFS_IDXELIST_CHILD)) {
1012
272
            tsk_error_reset();
1013
272
            tsk_error_set_errno(TSK_ERR_FS_INODE_COR);
1014
272
            tsk_error_set_errstr
1015
272
                ("Error: $IDX_ROOT says there should be children, but there isn't");
1016
272
            return TSK_COR;
1017
272
        }
1018
6.59k
    }
1019
51.6k
    else {
1020
51.6k
        unsigned int off;
1021
1022
51.6k
        if (fs_attr_idx->flags & TSK_FS_ATTR_RES) {
1023
1.15k
            tsk_error_reset();
1024
1.15k
            tsk_error_set_errno(TSK_ERR_FS_INODE_COR);
1025
1.15k
            tsk_error_set_errstr
1026
1.15k
                ("$IDX_ALLOC is Resident - it shouldn't be");
1027
1.15k
            return TSK_COR;
1028
1.15k
        }
1029
1030
        // Taking 128 MiB as an arbitrary upper bound
1031
50.5k
        if (fs_attr_idx->nrd.allocsize > (128 * 1024 * 1024)) {
1032
516
            tsk_error_reset();
1033
516
            tsk_error_set_errno(TSK_ERR_FS_LARGE_DIR_ERROR);
1034
516
            tsk_error_set_errstr("ntfs_dir_open_meta: fs_attr_idx->nrd.allocsize value out of bounds (addr: %" PRIuINUM", fs offset: %" PRIdOFF ")", a_addr, a_fs->offset);
1035
516
            return TSK_COR;
1036
0
           return TSK_COR;
1037
516
        }
1038
1039
        /*
1040
         * Copy the index allocation run into a big buffer
1041
         */
1042
        // default to null unless length is greater than 0
1043
50.0k
        idxalloc = NULL;
1044
50.0k
        idxalloc_len = (size_t) fs_attr_idx->nrd.allocsize;
1045
1046
50.0k
        if (idxalloc_len > 0 && (idxalloc = (char *)tsk_malloc(idxalloc_len)) == NULL) {
1047
20
          return TSK_ERR;
1048
20
        }
1049
1050
        /* Fill in the loading data structure */
1051
50.0k
        load_file.total = load_file.left = idxalloc_len;
1052
50.0k
        load_file.cur = load_file.base = idxalloc;
1053
1054
50.0k
        if (tsk_verbose)
1055
0
            tsk_fprintf(stderr,
1056
0
                "ntfs_dir_open_meta: Copying $IDX_ALLOC into buffer\n");
1057
1058
50.0k
        if (tsk_fs_attr_walk(fs_attr_idx,
1059
50.0k
                TSK_FS_FILE_WALK_FLAG_SLACK, tsk_fs_load_file_action,
1060
50.0k
                (void *) &load_file)) {
1061
5.09k
            free(idxalloc);
1062
5.09k
            tsk_error_errstr2_concat(" - ntfs_dir_open_meta");
1063
5.09k
            return TSK_COR;     // this could be an error though
1064
5.09k
        }
1065
1066
        /* Not all of the directory was copied, so we exit */
1067
44.9k
        if (load_file.left > 0) {
1068
1.92k
            free(idxalloc);
1069
1070
1.92k
            tsk_error_reset();
1071
1.92k
            tsk_error_set_errno(TSK_ERR_FS_FWALK);
1072
1.92k
            tsk_error_set_errstr("Error reading directory contents: %"
1073
1.92k
                PRIuINUM "\n", a_addr);
1074
1.92k
            return TSK_COR;
1075
1.92k
        }
1076
1077
        /*
1078
         * The idxalloc is a big buffer that contains one or more
1079
         * idx buffer structures.  Each idxrec is a node in the B-Tree.
1080
         * We do not process the tree as a tree because then we could
1081
         * not find the deleted file names.
1082
         *
1083
         * Therefore, we scan the big buffer looking for the index record
1084
         * structures.  We save a pointer to the known beginning (idxrec_p).
1085
         * Then we scan for the beginning of the next one (idxrec) and process
1086
         * everything in the middle as an ntfs_idxrec.  We can't use the
1087
         * size given because then we wouldn't see the deleted names
1088
         */
1089
1090
        /* Set the previous pointer to NULL */
1091
42.9k
        idxrec_p = idxrec = NULL;
1092
1093
        /* Loop by cluster size */
1094
1.55M
        for (off = 0; off < idxalloc_len; off += ntfs->csize_b) {
1095
1.51M
            uint32_t list_len, rec_len;
1096
1097
            // Ensure that there is enough data for an idxrec
1098
1.51M
            if ((idxalloc_len < (TSK_OFF_T)sizeof(ntfs_idxrec)) || (off > idxalloc_len - sizeof(ntfs_idxrec))) {
1099
374
                tsk_error_reset();
1100
374
                tsk_error_set_errno(TSK_ERR_FS_INODE_COR);
1101
374
                tsk_error_set_errstr
1102
374
                    ("ntfs_dir_open_meta: Not enough data in idxalloc buffer for an idxrec.");
1103
374
                free(idxalloc);
1104
374
                return TSK_COR;
1105
374
            }
1106
1107
1.51M
            idxrec = (ntfs_idxrec *) & idxalloc[off];
1108
1109
1.51M
            if (tsk_verbose)
1110
0
                tsk_fprintf(stderr,
1111
0
                    "ntfs_dir_open_meta: Index Buffer Offset: %d  Magic: %"
1112
0
                    PRIx32 "\n", off, tsk_getu32(a_fs->endian,
1113
0
                        idxrec->magic));
1114
1115
            /* Is this the beginning of an index record? */
1116
1.51M
            if (tsk_getu32(a_fs->endian,
1117
1.51M
                    idxrec->magic) != NTFS_IDXREC_MAGIC)
1118
1.47M
                continue;
1119
1120
1121
            /* idxrec_p is only NULL for the first time
1122
             * Set it and start again to find the next one */
1123
42.3k
            if (idxrec_p == NULL) {
1124
40.5k
                idxrec_p = idxrec;
1125
40.5k
                continue;
1126
40.5k
            }
1127
1128
            /* Process the previous structure */
1129
1130
            /* idxrec points to the next idxrec structure, idxrec_p
1131
             * points to the one we are going to process
1132
             */
1133
1.81k
            rec_len =
1134
1.81k
                (uint32_t) ((uintptr_t) idxrec - (uintptr_t) idxrec_p);
1135
1136
1.81k
            if (tsk_verbose)
1137
0
                tsk_fprintf(stderr,
1138
0
                    "ntfs_dir_open_meta: Processing previous index record (len: %"
1139
0
                    PRIu32 ")\n", rec_len);
1140
1141
            /* remove the update sequence in the index record */
1142
1.81k
            if (ntfs_fix_idxrec(ntfs, idxrec_p, rec_len)) {
1143
99
                free(idxalloc);
1144
99
                return TSK_COR;
1145
99
            }
1146
1147
            /* Locate the start of the index entry list */
1148
1.71k
            idxelist = &idxrec_p->list;
1149
1.71k
            idxe = (ntfs_idxentry *) ((uintptr_t) idxelist +
1150
1.71k
                tsk_getu32(a_fs->endian, idxelist->begin_off));
1151
1152
            /* the length from the start of the next record to where our
1153
             * list starts.
1154
             * This should be the same as bufend_off in idxelist, but we don't
1155
             * trust it.
1156
             */
1157
1.71k
            list_len = (uint32_t) ((uintptr_t) idxrec - (uintptr_t) idxe);
1158
1159
            /* Verify the offset pointers */
1160
1.71k
            if (((uintptr_t) idxe > (uintptr_t) idxrec) ||
1161
1.67k
                ((uintptr_t) idxelist +
1162
1.67k
                    tsk_getu32(a_fs->endian,
1163
1.67k
                        idxelist->seqend_off) > (uintptr_t) idxrec)) {
1164
96
                tsk_error_reset();
1165
96
                tsk_error_set_errno(TSK_ERR_FS_INODE_COR);
1166
96
                tsk_error_set_errstr
1167
96
                    ("Error: Index list offsets are invalid on entry: %"
1168
96
                    PRIuINUM, fs_dir->fs_file->meta->addr);
1169
96
                free(idxalloc);
1170
96
                return TSK_COR;
1171
96
            }
1172
1173
1174
            /* process the list of index entries */
1175
1.62k
            retval_tmp = ntfs_proc_idxentry(ntfs, fs_dir,
1176
1.62k
                (fs_dir->fs_file->meta->
1177
1.62k
                    flags & TSK_FS_META_FLAG_UNALLOC) ? 1 : 0, idxe,
1178
1.62k
                list_len, tsk_getu32(a_fs->endian,
1179
1.62k
                    idxelist->seqend_off) - tsk_getu32(a_fs->endian,
1180
1.62k
                    idxelist->begin_off));
1181
            // stop if we get an error, record if we get corruption
1182
1.62k
            if (retval_tmp == TSK_ERR) {
1183
27
                free(idxalloc);
1184
27
                return TSK_ERR;
1185
27
            }
1186
1.59k
            else if (retval_tmp == TSK_COR) {
1187
0
                retval_final = TSK_COR;
1188
0
            }
1189
1190
            /* reset the pointer to the next record */
1191
1.59k
            idxrec_p = idxrec;
1192
1193
1.59k
        }                       /* end of cluster loop */
1194
1195
1196
        /* Process the final record */
1197
42.3k
        if (idxrec_p) {
1198
40.3k
            uint32_t list_len, rec_len;
1199
1200
            /* Length from end of attribute to start of this */
1201
40.3k
            rec_len =
1202
40.3k
                (uint32_t) (idxalloc_len - ((uintptr_t) idxrec_p -
1203
40.3k
                (uintptr_t) idxalloc));
1204
1205
40.3k
            if (tsk_verbose)
1206
0
                tsk_fprintf(stderr,
1207
0
                    "ntfs_dir_open_meta: Processing final index record (len: %"
1208
0
                    PRIu32 ")\n", rec_len);
1209
1210
            /* remove the update sequence */
1211
40.3k
            if (ntfs_fix_idxrec(ntfs, idxrec_p, rec_len)) {
1212
959
                free(idxalloc);
1213
959
                return TSK_COR;
1214
959
            }
1215
1216
39.3k
            idxelist = &idxrec_p->list;
1217
39.3k
            if (tsk_getu32(a_fs->endian, idxelist->begin_off) > rec_len) {
1218
319
                tsk_error_reset();
1219
319
                tsk_error_set_errno(TSK_ERR_FS_INODE_COR);
1220
319
                tsk_error_set_errstr
1221
319
                    ("Error: Index list offsets are invalid on entry: %"
1222
319
                    PRIuINUM, fs_dir->fs_file->meta->addr);
1223
319
                free(idxalloc);
1224
319
                return TSK_COR;
1225
319
            }
1226
1227
39.0k
            idxe = (ntfs_idxentry *) ((uintptr_t) idxelist +
1228
39.0k
                tsk_getu32(a_fs->endian, idxelist->begin_off));
1229
1230
            /* This is the length of the idx entries */
1231
39.0k
            list_len =
1232
39.0k
                (uint32_t) (((uintptr_t) idxalloc + idxalloc_len) -
1233
39.0k
                (uintptr_t) idxe);
1234
1235
            /* Verify the offset pointers */
1236
39.0k
            if ((list_len > rec_len) ||
1237
38.9k
                ((uintptr_t) idxelist +
1238
38.9k
                    tsk_getu32(a_fs->endian, idxelist->seqend_off) >
1239
38.9k
                    (uintptr_t) idxalloc + idxalloc_len)) {
1240
478
                tsk_error_reset();
1241
478
                tsk_error_set_errno(TSK_ERR_FS_INODE_COR);
1242
478
                tsk_error_set_errstr
1243
478
                    ("Error: Index list offsets are invalid on entry: %"
1244
478
                    PRIuINUM, fs_dir->fs_file->meta->addr);
1245
478
                free(idxalloc);
1246
478
                return TSK_COR;
1247
478
            }
1248
1249
            /* process the list of index entries */
1250
38.5k
            retval_tmp = ntfs_proc_idxentry(ntfs, fs_dir,
1251
38.5k
                (fs_dir->fs_file->meta->
1252
38.5k
                    flags & TSK_FS_META_FLAG_UNALLOC) ? 1 : 0, idxe,
1253
38.5k
                list_len, tsk_getu32(a_fs->endian,
1254
38.5k
                    idxelist->seqend_off) - tsk_getu32(a_fs->endian,
1255
38.5k
                    idxelist->begin_off));
1256
            // stop if we get an error, record if we get corruption
1257
38.5k
            if (retval_tmp == TSK_ERR) {
1258
94
                free(idxalloc);
1259
94
                return TSK_ERR;
1260
94
            }
1261
38.4k
            else if (retval_tmp == TSK_COR) {
1262
0
                retval_final = TSK_COR;
1263
0
            }
1264
38.5k
        }
1265
1266
40.5k
        free(idxalloc);
1267
40.5k
    }
1268
1269
1270
    // get the orphan files
1271
    // load and cache the map if it has not already been done
1272
46.8k
    tsk_take_lock(&ntfs->orphan_map_lock);
1273
46.8k
    if (ntfs->orphan_map == NULL) {
1274
        // we do this to make it non-NULL. WE had some images that
1275
        // had no orphan files and it repeatedly did inode_walks
1276
        // because orphan_map was always NULL
1277
1.87k
        getParentMap(ntfs);
1278
1279
1.87k
        if (a_fs->inode_walk(a_fs, a_fs->first_inum, a_fs->last_inum,
1280
1.87k
                (TSK_FS_META_FLAG_ENUM)(TSK_FS_META_FLAG_UNALLOC | TSK_FS_META_FLAG_ALLOC), ntfs_parent_act, NULL)) {
1281
500
            tsk_release_lock(&ntfs->orphan_map_lock);
1282
500
            return TSK_ERR;
1283
500
        }
1284
1.87k
    }
1285
1286
1287
    /* see if there are any entries in MFT for this dir that we didn't see.
1288
     * Need to make sure it is for this version (sequence) though.
1289
     * NTFS Updates the sequence when a directory is deleted and not when
1290
     * it is allocated.  So, if we have a deleted directory, then use
1291
     * its previous sequence number to find the files that were in it when
1292
     * it was allocated.
1293
     */
1294
46.3k
    uint16_t seqToSrch = fs_dir->fs_file->meta->seq;
1295
46.3k
    if (fs_dir->fs_file->meta->flags & TSK_FS_META_FLAG_UNALLOC) {
1296
2.64k
        if (seqToSrch > 0)
1297
2.24k
            seqToSrch--;
1298
408
        else
1299
            // I can't imagine how we get here or what we should do except maybe not do the search.
1300
408
            seqToSrch = 0;
1301
2.64k
    }
1302
1303
46.3k
    if (ntfs_parent_map_exists(ntfs, a_addr, seqToSrch)) {
1304
28.6k
        TSK_FS_NAME *fs_name;
1305
1306
28.6k
        const std::vector <NTFS_META_ADDR> &childFiles = ntfs_parent_map_get(ntfs, a_addr, seqToSrch);
1307
1308
28.6k
        if ((fs_name = tsk_fs_name_alloc(256, 0)) == NULL){
1309
0
            tsk_release_lock(&ntfs->orphan_map_lock);
1310
0
            return TSK_ERR;
1311
0
        }
1312
1313
28.6k
        fs_name->type = TSK_FS_NAME_TYPE_UNDEF;
1314
28.6k
        fs_name->par_addr = a_addr;
1315
28.6k
        fs_name->par_seq = fs_dir->fs_file->meta->seq;
1316
1317
55.3k
        for(const NTFS_META_ADDR& childFile: childFiles) {
1318
55.3k
            TSK_FS_FILE *fs_file_orp = NULL;
1319
1320
            /* Check if fs_dir already has an allocated entry for this
1321
             * file.  If so, ignore it. We used to rely on fs_dir_add
1322
             * to get rid of this, but it wasted a lot of lookups. If
1323
             * We have only unalloc for this same entry (from idx entries),
1324
             * then try to add it.   If we got an allocated entry from
1325
             * the idx entries, then assume we have everything. */
1326
55.3k
            if (tsk_fs_dir_contains(fs_dir, childFile.getAddr(), childFile.getHash()) == TSK_FS_NAME_FLAG_ALLOC) {
1327
2.00k
                continue;
1328
2.00k
            }
1329
1330
            /* Fill in the basics of the fs_name entry
1331
             * so we can print in the fls formats */
1332
53.3k
            fs_name->meta_addr = childFile.getAddr();
1333
53.3k
            fs_name->meta_seq = childFile.getSeq();
1334
1335
            // lookup the file to get more info (we did not cache that)
1336
53.3k
            fs_file_orp =
1337
53.3k
                tsk_fs_file_open_meta(a_fs, fs_file_orp, fs_name->meta_addr);
1338
53.3k
            if (fs_file_orp) {
1339
50.1k
                if (fs_file_orp->meta) {
1340
50.1k
                    if (fs_file_orp->meta->flags & TSK_FS_META_FLAG_ALLOC) {
1341
44.2k
                        fs_name->flags = TSK_FS_NAME_FLAG_ALLOC;
1342
44.2k
                    }
1343
5.88k
                    else {
1344
5.88k
                        fs_name->flags = TSK_FS_NAME_FLAG_UNALLOC;
1345
                        /* This sequence is the MFT entry, which gets
1346
                         * incremented when it is unallocated.  So,
1347
                         * decrement it back down so that it is more
1348
                         * similar to the usual situation, where the
1349
                         * name sequence is 1 smaller than the meta
1350
                         * sequence. */
1351
5.88k
                        fs_name->meta_seq--;
1352
5.88k
                    }
1353
1354
50.1k
                    if (fs_file_orp->meta->name2) {
1355
50.1k
                        TSK_FS_META_NAME_LIST *n2 = fs_file_orp->meta->name2;
1356
1357
101k
                        while (n2) {
1358
51.1k
                            if (n2->par_inode == a_addr) {
1359
37.1k
                                strncpy(fs_name->name, n2->name, fs_name->name_size);
1360
37.1k
                                tsk_fs_dir_add(fs_dir, fs_name);
1361
37.1k
                            }
1362
51.1k
                            n2 = n2->next;
1363
51.1k
                        }
1364
50.1k
                    }
1365
50.1k
                }
1366
50.1k
                tsk_fs_file_close(fs_file_orp);
1367
50.1k
            }
1368
53.3k
        }
1369
28.6k
        tsk_fs_name_free(fs_name);
1370
28.6k
    }
1371
46.3k
    tsk_release_lock(&ntfs->orphan_map_lock);
1372
1373
    // if we are listing the root directory, add the Orphan directory entry
1374
46.3k
    if (a_addr == a_fs->root_inum) {
1375
39.3k
        TSK_FS_NAME *fs_name;
1376
1377
39.3k
        if ((fs_name = tsk_fs_name_alloc(256, 0)) == NULL)
1378
0
            return TSK_ERR;
1379
1380
39.3k
        if (tsk_fs_dir_make_orphan_dir_name(a_fs, fs_name)) {
1381
0
            tsk_fs_name_free(fs_name);
1382
0
            return TSK_ERR;
1383
0
        }
1384
1385
39.3k
        if (tsk_fs_dir_add(fs_dir, fs_name)) {
1386
0
            tsk_fs_name_free(fs_name);
1387
0
            return TSK_ERR;
1388
0
        }
1389
39.3k
        tsk_fs_name_free(fs_name);
1390
39.3k
    }
1391
1392
1393
46.3k
    return retval_final;
1394
46.3k
}
1395
1396
1397
1398
/****************************************************************************
1399
 * FIND_FILE ROUTINES
1400
 *
1401
 */
1402
1403
0
#define MAX_DEPTH   128
1404
0
#define DIR_STRSZ   4096
1405
1406
typedef struct {
1407
    /* Recursive path stuff */
1408
1409
    /* how deep in the directory tree are we */
1410
    unsigned int depth;
1411
1412
    /* pointer in dirs string to where '/' is for given depth */
1413
    char *didx[MAX_DEPTH];
1414
1415
    /* The current directory name string */
1416
    char dirs[DIR_STRSZ];
1417
1418
} NTFS_DINFO;
1419
1420
1421
/*
1422
 * Looks up the parent inode described in fs_name.
1423
 *
1424
 * fs_name was filled in by ntfs_find_file and will get the final path
1425
 * added to it before action is called
1426
 *
1427
 * return 1 on error and 0 on success
1428
 */
1429
static uint8_t
1430
ntfs_find_file_rec(TSK_FS_INFO * fs, NTFS_DINFO * dinfo,
1431
    TSK_FS_FILE * fs_file, TSK_FS_META_NAME_LIST * fs_name_list,
1432
    TSK_FS_DIR_WALK_CB action, void *ptr)
1433
0
{
1434
0
    TSK_FS_META_NAME_LIST *fs_name_list_par;
1435
0
    uint8_t decrem = 0;
1436
0
    size_t len = 0, i;
1437
0
    char *begin = NULL;
1438
1439
1440
0
    if (fs_name_list->par_inode < fs->first_inum ||
1441
0
        fs_name_list->par_inode > fs->last_inum) {
1442
0
        tsk_error_reset();
1443
0
        tsk_error_set_errno(TSK_ERR_FS_ARG);
1444
0
        tsk_error_set_errstr("invalid inode value: %" PRIuINUM "\n",
1445
0
            fs_name_list->par_inode);
1446
0
        return 1;
1447
0
    }
1448
1449
0
    std::unique_ptr<TSK_FS_FILE, decltype(&tsk_fs_file_close)> fs_file_par{
1450
0
        tsk_fs_file_open_meta(fs, NULL, fs_name_list->par_inode),
1451
0
        tsk_fs_file_close
1452
0
    };
1453
1454
0
    if (!fs_file_par) {
1455
0
        tsk_error_errstr2_concat(" - ntfs_find_file_rec");
1456
0
        return 1;
1457
0
    }
1458
1459
    /*
1460
     * Orphan File
1461
     * This occurs when the file is deleted and either:
1462
     * - The parent is no longer a directory
1463
     * - The sequence number of the parent is no longer correct
1464
     */
1465
0
    if (( ! TSK_FS_IS_DIR_META(fs_file_par->meta->type))
1466
0
        || (fs_file_par->meta->seq != fs_name_list->par_seq)) {
1467
0
        const char *str = TSK_FS_ORPHAN_STR;
1468
0
        int retval;
1469
0
        len = strlen(str);
1470
1471
        /* @@@ There should be a sanity check here to verify that the
1472
         * previous name was unallocated ... but how do I get it again?
1473
         */
1474
0
        if ((((uintptr_t) dinfo->didx[dinfo->depth - 1] - len) >=
1475
0
                (uintptr_t) & dinfo->dirs[0])
1476
0
            && (dinfo->depth < MAX_DEPTH)) {
1477
0
            begin = dinfo->didx[dinfo->depth] =
1478
0
                (char *) ((uintptr_t) dinfo->didx[dinfo->depth - 1] - len);
1479
1480
0
            dinfo->depth++;
1481
0
            decrem = 1;
1482
1483
0
            for (i = 0; i < len; i++)
1484
0
                begin[i] = str[i];
1485
0
        }
1486
1487
0
        retval = action(fs_file, begin, ptr);
1488
1489
0
        if (decrem)
1490
0
            dinfo->depth--;
1491
1492
0
        return (retval == TSK_WALK_ERROR) ? 1 : 0;
1493
0
    }
1494
1495
0
    for (fs_name_list_par = fs_file_par->meta->name2;
1496
0
        fs_name_list_par != NULL;
1497
0
        fs_name_list_par = fs_name_list_par->next) {
1498
1499
0
        len = strlen(fs_name_list_par->name);
1500
1501
        /* do some length checks on the dir structure
1502
         * if we can't fit it then forget about it */
1503
0
        if ((((uintptr_t) dinfo->didx[dinfo->depth - 1] - len - 1) >=
1504
0
                (uintptr_t) & dinfo->dirs[0])
1505
0
            && (dinfo->depth < MAX_DEPTH)) {
1506
0
            begin = dinfo->didx[dinfo->depth] =
1507
0
                (char *) ((uintptr_t) dinfo->didx[dinfo->depth - 1] - len -
1508
0
                1);
1509
1510
0
            dinfo->depth++;
1511
0
            decrem = 1;
1512
1513
0
            *begin = '/';
1514
0
            for (i = 0; i < len; i++)
1515
0
                begin[i + 1] = fs_name_list_par->name[i];
1516
0
        }
1517
0
        else {
1518
0
            begin = dinfo->didx[dinfo->depth];
1519
0
            decrem = 0;
1520
0
        }
1521
1522
1523
        /* if we are at the root, then fill out the rest of fs_name with
1524
         * the full path and call the action
1525
         */
1526
0
        if (fs_name_list_par->par_inode == NTFS_ROOTINO) {
1527
            /* increase the path by one so that we do not pass the '/'
1528
             * if we do then the printed result will have '//' at
1529
             * the beginning
1530
             */
1531
0
            if (TSK_WALK_ERROR == action(fs_file,
1532
0
                    (const char *) ((uintptr_t) begin + 1), ptr)) {
1533
0
                return 1;
1534
0
            }
1535
0
        }
1536
1537
        /* otherwise, recurse some more */
1538
0
        else {
1539
0
            if (ntfs_find_file_rec(fs, dinfo, fs_file, fs_name_list_par,
1540
0
                    action, ptr)) {
1541
0
                return 1;
1542
0
            }
1543
0
        }
1544
1545
        /* if we incremented before, then decrement the depth now */
1546
0
        if (decrem)
1547
0
            dinfo->depth--;
1548
0
    }
1549
1550
0
    return 0;
1551
0
}
1552
1553
/* \ingroup fslib
1554
 * NTFS can map a meta address to its name much faster than in other file systems
1555
 * because each entry stores the address of its parent.
1556
 *
1557
 * This can not be called with dent_walk because the path
1558
 * structure will get messed up!
1559
 *
1560
 * @param fs File system being analyzed
1561
 * @param inode_toid Address of file to find the name for.
1562
 * @param type_toid Attribute type to find the more specific name for (if you want more than just the base file name)
1563
 * @param type_used 1 if the type_toid value was passed a valid value.  0 otherwise.
1564
 * @param id_toid Attribute id to find the more specific name for (if you want more than just the base file name)
1565
 * @param id_used 1 if the id_toid value was passed a valid value. 0 otherwise.
1566
 * @param dir_walk_flags Flags to use during search
1567
 * @param action Callback that will be called for each name that uses the specified addresses.
1568
 * @param ptr Pointer that will be passed into action when it is called (so that you can pass in other data)
1569
 * @returns 1 on error, 0 on success
1570
 */
1571
1572
uint8_t
1573
ntfs_find_file(TSK_FS_INFO * fs, TSK_INUM_T inode_toid, uint32_t type_toid,
1574
    uint8_t type_used, uint16_t id_toid, uint8_t id_used,
1575
    TSK_FS_DIR_WALK_FLAG_ENUM dir_walk_flags, TSK_FS_DIR_WALK_CB action,
1576
    void *ptr)
1577
0
{
1578
0
    TSK_FS_META_NAME_LIST *fs_name_list;
1579
0
    char *attr = NULL;
1580
0
    NTFS_DINFO dinfo;
1581
0
    TSK_RETVAL_ENUM r_enum;
1582
0
    NTFS_INFO *ntfs = (NTFS_INFO *) fs;
1583
1584
    /* sanity check */
1585
0
    if (inode_toid < fs->first_inum || inode_toid > fs->last_inum) {
1586
0
        tsk_error_reset();
1587
0
        tsk_error_set_errno(TSK_ERR_FS_ARG);
1588
0
        tsk_error_set_errstr("ntfs_find_file: invalid inode value: %"
1589
0
            PRIuINUM "\n", inode_toid);
1590
0
        return 1;
1591
0
    }
1592
1593
0
    std::unique_ptr<ntfs_mft, decltype(&free)> mft{
1594
0
        (ntfs_mft *) tsk_malloc(ntfs->mft_rsize_b),
1595
0
        free
1596
0
    };
1597
1598
0
    if (!mft) {
1599
0
        return 1;
1600
0
    }
1601
1602
0
    r_enum = ntfs_dinode_lookup(ntfs, (char *) mft.get(), inode_toid, 0);
1603
0
    if (r_enum == TSK_ERR) {
1604
0
        return 1;
1605
0
    }
1606
1607
    // open the file to ID
1608
0
    std::unique_ptr<TSK_FS_FILE, decltype(&tsk_fs_file_close)> fs_file{
1609
0
        tsk_fs_file_open_meta(fs, NULL, inode_toid),
1610
0
        tsk_fs_file_close
1611
0
    };
1612
1613
0
    if (!fs_file) {
1614
0
        tsk_error_errstr2_concat("- ntfs_find_file");
1615
0
        return 1;
1616
0
    }
1617
1618
    // see if its allocation status meets the callback needs
1619
0
    if ((fs_file->meta->flags & TSK_FS_META_FLAG_ALLOC)
1620
0
        && ((dir_walk_flags & TSK_FS_DIR_WALK_FLAG_ALLOC) == 0)) {
1621
0
        return 1;
1622
0
    }
1623
0
    else if ((fs_file->meta->flags & TSK_FS_META_FLAG_UNALLOC)
1624
0
        && ((dir_walk_flags & TSK_FS_DIR_WALK_FLAG_UNALLOC) == 0)) {
1625
0
        return 1;
1626
0
    }
1627
1628
    /* Allocate a name and fill in some details  */
1629
0
    if ((fs_file->name =
1630
0
            tsk_fs_name_alloc(NTFS_MAXNAMLEN_UTF8, 0)) == NULL) {
1631
0
        return 1;
1632
0
    }
1633
0
    fs_file->name->meta_addr = inode_toid;
1634
0
    fs_file->name->meta_seq = 0;
1635
0
    fs_file->name->flags =
1636
0
        ((tsk_getu16(fs->endian,
1637
0
                mft->flags) & NTFS_MFT_INUSE) ? TSK_FS_NAME_FLAG_ALLOC :
1638
0
        TSK_FS_NAME_FLAG_UNALLOC);
1639
1640
0
    memset(&dinfo, 0, sizeof(NTFS_DINFO));
1641
1642
    /* in this function, we use the dinfo->dirs array in the opposite order.
1643
     * we set the end of it to NULL and then prepend the
1644
     * directories to it
1645
     *
1646
     * dinfo->didx[dinfo->depth] will point to where the current level started their
1647
     * dir name
1648
     */
1649
0
    dinfo.dirs[DIR_STRSZ - 2] = '/';
1650
0
    dinfo.dirs[DIR_STRSZ - 1] = '\0';
1651
0
    dinfo.didx[0] = &dinfo.dirs[DIR_STRSZ - 2];
1652
0
    dinfo.depth = 1;
1653
1654
1655
    /* Get the name for the attribute - if specified */
1656
0
    if (type_used) {
1657
0
        const TSK_FS_ATTR *fs_attr;
1658
1659
0
        if (id_used)
1660
0
            fs_attr =
1661
0
                tsk_fs_attrlist_get_id(fs_file->meta->attr, (TSK_FS_ATTR_TYPE_ENUM)type_toid,
1662
0
                id_toid);
1663
0
        else
1664
0
            fs_attr = tsk_fs_attrlist_get(fs_file->meta->attr, (TSK_FS_ATTR_TYPE_ENUM)type_toid);
1665
1666
0
        if (!fs_attr) {
1667
0
            tsk_error_reset();
1668
0
            tsk_error_set_errno(TSK_ERR_FS_INODE_COR);
1669
0
            tsk_error_set_errstr("find_file: Type %" PRIu32 " Id %" PRIu16
1670
0
                " not found in MFT %" PRIuINUM "", type_toid, id_toid,
1671
0
                inode_toid);
1672
0
            return 1;
1673
0
        }
1674
1675
        /* only add the attribute name if it is the non-default data stream */
1676
0
        if (fs_attr->name != NULL)
1677
0
            attr = fs_attr->name;
1678
0
    }
1679
1680
    /* loop through all the names it may have */
1681
0
    for (fs_name_list = fs_file->meta->name2; fs_name_list != NULL;
1682
0
        fs_name_list = fs_name_list->next) {
1683
0
        int retval;
1684
1685
        /* Append on the attribute name, if it exists */
1686
0
        if (attr != NULL) {
1687
0
            snprintf(fs_file->name->name, fs_file->name->name_size,
1688
0
                "%s:%s", fs_name_list->name, attr);
1689
0
        }
1690
0
        else {
1691
0
            strncpy(fs_file->name->name, fs_name_list->name,
1692
0
                fs_file->name->name_size);
1693
0
        }
1694
1695
        /* if this is in the root directory, then call back */
1696
0
        if (fs_name_list->par_inode == NTFS_ROOTINO) {
1697
1698
0
            retval = action(fs_file.get(), dinfo.didx[0], ptr);
1699
0
            if (retval == TSK_WALK_STOP) {
1700
0
                return 0;
1701
0
            }
1702
0
            else if (retval == TSK_WALK_ERROR) {
1703
0
                return 1;
1704
0
            }
1705
0
        }
1706
        /* call the recursive function on the parent to get the full path */
1707
0
        else {
1708
0
            if (ntfs_find_file_rec(fs, &dinfo, fs_file.get(), fs_name_list,
1709
0
                    action, ptr)) {
1710
0
                return 1;
1711
0
            }
1712
0
        }
1713
0
    }                           /* end of name loop */
1714
1715
0
    return 0;
1716
0
}
1717
1718
1719
int
1720
ntfs_name_cmp(TSK_FS_INFO * /*a_fs_info*/, const char *s1, const char *s2)
1721
0
{
1722
0
    return strcasecmp(s1, s2);
1723
0
}